From b03844211f5feb9c0cc3216c533a229808e32ee4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 3 Oct 2016 17:15:26 +0800 Subject: [PATCH 0001/1384] =?UTF-8?q?readme=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 89 ++----------------------------------------------------- base.php | 2 +- 2 files changed, 4 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index ee0676a0..a6911dd4 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,9 @@ -ThinkPHP 5.0 +ThinkPHP 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) -[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) +ThinkPHP5.1 Alpha -ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,对已有的CBD模式做了更深的强化,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: - - + 基于命名空间和众多PHP新特性 - + 核心功能组件化 - + 强化路由功能 - + 更灵活的控制器 - + 重构的模型和数据库类 - + 配置文件可分离 - + 重写的自动验证和完成 - + 简化扩展机制 - + API支持完善 - + 改进的Log类 - + 命令行访问支持 - + REST支持 - + 引导文件支持 - + 方便的自动生成定义 - + 真正惰性加载 - + 分布式环境支持 - + 支持Composer - -> ThinkPHP5的运行环境要求PHP5.4以上。 - -详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) - -## 目录结构 - -初始的目录结构如下: - -~~~ -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 命令行入口文件 -~~~ - -> router.php用于php自带webserver支持,可用于快速测试 -> 切换到public目录后,启动命令:php -S localhost:8888 router.php -> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 +> ThinkPHP5.1的运行环境要求PHP5.6以上。 ## 命名规范 diff --git a/base.php b/base.php index 1184dc51..ff2395b4 100644 --- a/base.php +++ b/base.php @@ -9,7 +9,7 @@ // | Author: liu21st // +---------------------------------------------------------------------- -define('THINK_VERSION', '5.0.2dev'); +define('THINK_VERSION', '5.1.0alpha'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); -- Gitee From 8518a668e42576f2c98095e92683174e802ad1a8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 26 Jan 2017 13:35:20 +0800 Subject: [PATCH 0002/1384] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=87=B3=E6=9C=80?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 2 - base.php | 2 +- composer.json | 6 +- console.php | 2 +- convention.php | 58 +- helper.php | 57 +- lang/zh-cn.php | 4 +- library/think/App.php | 89 +- library/think/Build.php | 2 +- library/think/Cache.php | 43 +- library/think/Collection.php | 8 +- library/think/Config.php | 11 +- library/think/Console.php | 11 +- library/think/Controller.php | 11 +- library/think/Cookie.php | 20 +- library/think/Db.php | 23 +- library/think/Debug.php | 10 +- library/think/Env.php | 2 +- library/think/Error.php | 2 +- library/think/Exception.php | 2 +- library/think/File.php | 4 +- library/think/Hook.php | 222 +++-- library/think/Lang.php | 10 +- library/think/Loader.php | 61 +- library/think/Log.php | 14 +- library/think/Model.php | 898 ++++++++++++----- library/think/Paginator.php | 153 ++- library/think/Process.php | 112 +-- library/think/Request.php | 185 ++-- library/think/Response.php | 39 +- library/think/Route.php | 335 ++++--- library/think/Session.php | 76 +- library/think/Template.php | 103 +- library/think/Url.php | 152 ++- library/think/Validate.php | 200 ++-- library/think/View.php | 38 +- library/think/cache/Driver.php | 25 +- library/think/cache/driver/File.php | 4 +- library/think/cache/driver/Lite.php | 2 +- library/think/cache/driver/Memcache.php | 3 +- library/think/cache/driver/Memcached.php | 2 +- library/think/cache/driver/Redis.php | 2 +- library/think/cache/driver/Sqlite.php | 3 +- library/think/cache/driver/Wincache.php | 5 +- library/think/cache/driver/Xcache.php | 3 +- library/think/config/driver/Ini.php | 2 +- library/think/config/driver/Json.php | 2 +- library/think/config/driver/Xml.php | 2 +- library/think/console/Input.php | 2 +- library/think/console/command/Help.php | 3 +- library/think/console/command/Lists.php | 2 +- .../console/command/optimize/Autoload.php | 2 +- .../think/console/command/optimize/Schema.php | 2 +- library/think/console/output/Ask.php | 2 +- .../think/console/output/driver/Console.php | 16 +- library/think/controller/Rest.php | 2 +- library/think/db/Builder.php | 217 ++-- library/think/db/Connection.php | 242 +++-- library/think/db/Query.php | 939 +++++++++++++----- library/think/db/builder/Mysql.php | 13 +- library/think/db/builder/Pgsql.php | 13 +- library/think/db/builder/Sqlite.php | 23 +- library/think/db/builder/Sqlsrv.php | 21 +- library/think/db/connector/Mysql.php | 11 +- library/think/db/connector/Pgsql.php | 2 +- library/think/db/connector/Sqlite.php | 2 +- .../think/db/exception/BindParamException.php | 4 +- .../db/exception/DataNotFoundException.php | 10 +- .../db/exception/ModelNotFoundException.php | 10 +- library/think/exception/DbException.php | 2 +- library/think/exception/ErrorException.php | 2 +- library/think/exception/PDOException.php | 4 +- .../exception/RouteNotFoundException.php | 22 + library/think/log/driver/File.php | 50 +- library/think/log/driver/Socket.php | 45 +- library/think/model/Collection.php | 79 ++ library/think/model/Merge.php | 84 +- library/think/model/Pivot.php | 2 +- library/think/model/Relation.php | 668 +------------ library/think/model/relation/BelongsTo.php | 132 +++ .../think/model/relation/BelongsToMany.php | 360 +++++++ library/think/model/relation/HasMany.php | 272 +++++ .../think/model/relation/HasManyThrough.php | 124 +++ library/think/model/relation/HasOne.php | 159 +++ library/think/model/relation/MorphMany.php | 239 +++++ library/think/model/relation/MorphTo.php | 198 ++++ library/think/model/relation/OneToOne.php | 316 ++++++ library/think/paginator/driver/Bootstrap.php | 6 +- library/think/process/Builder.php | 7 +- library/think/process/Utils.php | 4 +- library/think/process/exception/Failed.php | 42 + library/think/process/exception/Timeout.php | 2 +- library/think/process/pipes/Pipes.php | 3 +- library/think/process/pipes/Unix.php | 20 +- library/think/process/pipes/Windows.php | 8 +- library/think/response/Json.php | 24 +- library/think/response/Jsonp.php | 32 +- library/think/response/Redirect.php | 10 +- library/think/response/View.php | 2 +- library/think/response/Xml.php | 2 +- library/think/session/driver/Memcache.php | 7 +- library/think/session/driver/Memcached.php | 7 +- library/think/session/driver/Redis.php | 10 +- library/think/template/TagLib.php | 2 +- library/think/template/driver/File.php | 2 +- library/think/template/taglib/Cx.php | 12 +- library/think/view/driver/Php.php | 39 +- library/think/view/driver/Think.php | 39 +- library/traits/controller/Jump.php | 2 +- library/traits/model/SoftDelete.php | 39 +- library/traits/think/Instance.php | 4 +- start.php | 2 +- tests/thinkphp/library/think/appTest.php | 1 - tests/thinkphp/library/think/behavior/One.php | 4 +- .../thinkphp/library/think/behavior/Three.php | 2 +- tests/thinkphp/library/think/behavior/Two.php | 2 +- .../thinkphp/library/think/controllerTest.php | 8 +- tests/thinkphp/library/think/dbTest.php | 329 +++++- tests/thinkphp/library/think/debugTest.php | 6 +- tests/thinkphp/library/think/lang/lang.php | 2 +- tests/thinkphp/library/think/paginateTest.php | 6 +- tests/thinkphp/library/think/requestTest.php | 22 +- tests/thinkphp/library/think/routeTest.php | 12 +- .../library/think/template/taglib/cxTest.php | 14 +- tests/thinkphp/library/think/templateTest.php | 10 +- tests/thinkphp/library/think/urlTest.php | 19 +- tests/thinkphp/library/think/validateTest.php | 2 +- 127 files changed, 5758 insertions(+), 2326 deletions(-) create mode 100644 library/think/exception/RouteNotFoundException.php create mode 100644 library/think/model/Collection.php create mode 100644 library/think/model/relation/BelongsTo.php create mode 100644 library/think/model/relation/BelongsToMany.php create mode 100644 library/think/model/relation/HasMany.php create mode 100644 library/think/model/relation/HasManyThrough.php create mode 100644 library/think/model/relation/HasOne.php create mode 100644 library/think/model/relation/MorphMany.php create mode 100644 library/think/model/relation/MorphTo.php create mode 100644 library/think/model/relation/OneToOne.php create mode 100644 library/think/process/exception/Failed.php diff --git a/.travis.yml b/.travis.yml index 96a9dffc..f74ffca1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,6 @@ install: script: ## LINT - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; - ## PHP_CodeSniffer - - vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 --standard=PSR2 --ignore="vendor/*" ./ ## PHP Copy/Paste Detector - vendor/bin/phpcpd --verbose --exclude vendor ./ || true ## PHPLOC diff --git a/base.php b/base.php index ff2395b4..eb55391a 100644 --- a/base.php +++ b/base.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/composer.json b/composer.json index 444d6889..c546e114 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,11 @@ "mikey179/vfsStream": "~1.6", "phploc/phploc": "2.*", "sebastian/phpcpd": "2.*", - "squizlabs/php_codesniffer": "2.*", "phpdocumentor/reflection-docblock": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\": "library/think" + } } } diff --git a/console.php b/console.php index 3d518e78..fa777801 100644 --- a/console.php +++ b/console.php @@ -17,4 +17,4 @@ require __DIR__ . '/base.php'; // 执行应用 App::initCommon(); -Console::init(); \ No newline at end of file +Console::init(); diff --git a/convention.php b/convention.php index f7486a48..c66ef583 100644 --- a/convention.php +++ b/convention.php @@ -58,6 +58,8 @@ return [ 'default_validate' => '', // 默认的空控制器名 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, // 操作方法后缀 'action_suffix' => '', // 自动搜索控制器 @@ -83,6 +85,8 @@ return [ 'url_route_on' => true, // 路由配置文件(支持配置多个) 'route_config_file' => ['route'], + // 路由使用完整匹配 + 'route_complete_match' => false, // 是否强制使用路由 'url_route_must' => false, // 域名部署 @@ -99,6 +103,10 @@ return [ 'var_ajax' => '_ajax', // 表单pjax伪装变量 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, // +---------------------------------------------------------------------- // | 模板设置 @@ -107,7 +115,9 @@ return [ 'template' => [ // 模板引擎类型 支持 php think 支持扩展 'type' => 'Think', - // 模板路径 + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 'view_path' => '', // 模板后缀 'view_suffix' => 'html', @@ -193,6 +203,8 @@ return [ 'type' => '', // 是否自动开启 SESSION 'auto_start' => true, + 'httponly' => true, + 'secure' => true, ], // +---------------------------------------------------------------------- @@ -221,39 +233,49 @@ return [ 'database' => [ // 数据库类型 - 'type' => 'mysql', + 'type' => 'mysql', // 数据库连接DSN配置 - 'dsn' => '', + 'dsn' => '', // 服务器地址 - 'hostname' => 'localhost', + 'hostname' => '127.0.0.1', // 数据库名 - 'database' => '', + 'database' => '', // 数据库用户名 - 'username' => 'root', + 'username' => 'root', // 数据库密码 - 'password' => '', + 'password' => '', // 数据库连接端口 - 'hostport' => '', + 'hostport' => '', // 数据库连接参数 - 'params' => [], + 'params' => [], // 数据库编码默认采用utf8 - 'charset' => 'utf8', + 'charset' => 'utf8', // 数据库表前缀 - 'prefix' => '', + 'prefix' => '', // 数据库调试模式 - 'debug' => false, + 'debug' => false, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, + 'deploy' => 0, // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, + 'rw_separate' => false, // 读写分离后 主服务器数量 - 'master_num' => 1, + 'master_num' => 1, // 指定从服务器序号 - 'slave_no' => '', + 'slave_no' => '', // 是否严格检查字段是否存在 - 'fields_strict' => true, + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', // 自动写入时间戳字段 - 'auto_timestamp' => false, + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', ], //分页配置 diff --git a/helper.php b/helper.php index 6ab2cb34..543942be 100644 --- a/helper.php +++ b/helper.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -23,6 +23,7 @@ use think\exception\HttpResponseException; use think\Lang; use think\Loader; use think\Log; +use think\Model; use think\Request; use think\Response; use think\Session; @@ -117,7 +118,7 @@ if (!function_exists('input')) { * @param string $filter 过滤方法 * @return mixed */ - function input($key = '', $default = null, $filter = null) + function input($key = '', $default = null, $filter = '') { if (0 === strpos($key, '?')) { $key = substr($key, 1); @@ -125,10 +126,9 @@ if (!function_exists('input')) { } if ($pos = strpos($key, '.')) { // 指定参数来源 - $method = substr($key, 0, $pos); - if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { - $key = substr($key, $pos + 1); - } else { + list($method, $key) = explode('.', $key, 2); + if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = $method . '.' . $key; $method = 'param'; } } else { @@ -298,7 +298,7 @@ if (!function_exists('session')) { Session::init($name); } elseif (is_null($name)) { // 清除 - Session::clear($value); + Session::clear('' === $value ? null : $value); } elseif ('' === $value) { // 判断或获取 return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); @@ -330,7 +330,7 @@ if (!function_exists('cookie')) { Cookie::clear($value); } elseif ('' === $value) { // 获取 - return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); } elseif (is_null($value)) { // 删除 return Cookie::delete($name); @@ -359,12 +359,17 @@ if (!function_exists('cache')) { // 缓存初始化 return Cache::connect($name); } - if ('' === $value) { + if (is_null($name)) { + return Cache::clear($value); + } elseif ('' === $value) { // 获取缓存 return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); } elseif (is_null($value)) { // 删除缓存 return Cache::rm($name); + } elseif (0 === strpos($name, '?') && '' !== $value) { + $expire = is_numeric($options) ? $options : null; + return Cache::remember(substr($name, 1), $value, $expire); } else { // 缓存数据 if (is_array($options)) { @@ -544,3 +549,37 @@ if (!function_exists('token')) { return ''; } } + +if (!function_exists('load_relation')) { + /** + * 延迟预载入关联查询 + * @param mixed $resultSet 数据集 + * @param mixed $relation 关联 + * @return array + */ + function load_relation($resultSet, $relation) + { + $item = current($resultSet); + if ($item instanceof Model) { + $item->eagerlyResultSet($resultSet, $relation); + } + return $resultSet; + } +} + +if (!function_exists('collection')) { + /** + * 数组转换为数据集对象 + * @param array $resultSet 数据集数组 + * @return \think\model\Collection|\think\Collection + */ + function collection($resultSet) + { + $item = current($resultSet); + if ($item instanceof Model) { + return \think\model\Collection::make($resultSet); + } else { + return \think\Collection::make($resultSet); + } + } +} diff --git a/lang/zh-cn.php b/lang/zh-cn.php index db43a1c4..b837bc41 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -62,4 +62,6 @@ return [ 'sae mc write error' => 'SAE mc 写入错误', 'route name not exists' => '路由标识不存在(或参数不够)', 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', ]; diff --git a/library/think/App.php b/library/think/App.php index 7e140ca9..77721112 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,18 +11,9 @@ namespace think; -use think\Config; -use think\Env; -use think\Exception; use think\exception\HttpException; use think\exception\HttpResponseException; -use think\Hook; -use think\Lang; -use think\Loader; -use think\Log; -use think\Request; -use think\Response; -use think\Route; +use think\exception\RouteNotFoundException; /** * App 应用管理 @@ -126,6 +117,8 @@ class App // 监听app_begin Hook::listen('app_begin', $dispatch); + // 请求缓存检查 + $request->cache($config['request_cache'], $config['request_cache_expire']); switch ($dispatch['type']) { case 'redirect': @@ -138,11 +131,13 @@ class App break; case 'controller': // 执行控制器操作 - $data = Loader::action($dispatch['controller']); + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']); break; case 'method': // 执行回调方法 - $data = self::invokeMethod($dispatch['method']); + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); break; case 'function': // 执行闭包 @@ -217,7 +212,7 @@ class App public static function invokeMethod($method, $vars = []) { if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : new $method[0](Request::instance()); + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); $reflect = new \ReflectionMethod($class, $method[1]); } else { // 静态方法 @@ -225,7 +220,7 @@ class App } $args = self::bindParams($reflect, $vars); - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); return $reflect->invokeArgs(isset($class) ? $class : null, $args); } @@ -245,8 +240,6 @@ class App } else { $args = []; } - - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); return $reflect->newInstanceArgs($args); } @@ -254,7 +247,7 @@ class App * 绑定参数 * @access public * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 + * @param array $vars 变量 * @return array */ private static function bindParams($reflect, $vars = []) @@ -282,7 +275,14 @@ class App if ($bind instanceof $className) { $args[] = $bind; } else { - $args[] = method_exists($className, 'instance') ? $className::instance() : new $className(); + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + if ($method->isPublic() && $method->isStatic()) { + $args[] = $className::invoke(Request::instance()); + continue; + } + } + $args[] = method_exists($className, 'instance') ? $className::instance() : new $className; } } elseif (1 == $type && !empty($vars)) { $args[] = array_shift($vars); @@ -294,8 +294,6 @@ class App throw new \InvalidArgumentException('method param miss:' . $name); } } - // 全局过滤 - array_walk_recursive($args, [Request::instance(), 'filterExp']); } return $args; } @@ -337,6 +335,8 @@ class App // 初始化模块 $request->module($module); $config = self::init($module); + // 模块请求缓存检查 + $request->cache($config['request_cache'], $config['request_cache_expire']); } else { throw new HttpException(404, 'module not exists:' . $module); } @@ -364,34 +364,29 @@ class App // 监听module_init Hook::listen('module_init', $request); - try { - $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']); - if (is_null($instance)) { - throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); - } - // 获取当前操作名 - $action = $actionName . $config['action_suffix']; - if (!preg_match('/^[A-Za-z](\w)*$/', $action)) { - // 非法操作 - throw new \ReflectionException('illegal action name:' . $actionName); - } + $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']); + if (is_null($instance)) { + throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); + } + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + $vars = []; + if (is_callable([$instance, $action])) { // 执行操作方法 $call = [$instance, $action]; - Hook::listen('action_begin', $call); - - $data = self::invokeMethod($call); - } catch (\ReflectionException $e) { + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { // 操作不存在 - if (method_exists($instance, '_empty')) { - $reflect = new \ReflectionMethod($instance, '_empty'); - $data = $reflect->invokeArgs($instance, [$action]); - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); - } else { - throw new HttpException(404, 'method not exists:' . (new \ReflectionClass($instance))->getName() . '->' . $action); - } + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); } - return $data; + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); } /** @@ -443,9 +438,9 @@ class App // 监听app_init Hook::listen('app_init'); - self::$init = $config; + self::$init = true; } - return self::$init; + return Config::get(); } /** @@ -547,7 +542,7 @@ class App $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; if ($must && false === $result) { // 路由无效 - throw new HttpException(404, 'Route Not Found'); + throw new RouteNotFoundException(); } } if (false === $result) { diff --git a/library/think/Build.php b/library/think/Build.php index dd2bd550..13b7bfdc 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/Cache.php b/library/think/Cache.php index a5ac0b32..9e4b30ef 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,7 @@ namespace think; -use think\App; +use think\cache\Driver; class Cache { @@ -31,7 +31,7 @@ class Cache * @access public * @param array $options 配置数组 * @param bool|string $name 缓存连接标识 true 强制重新连接 - * @return \think\cache\Driver + * @return Driver */ public static function connect(array $options = [], $name = false) { @@ -79,11 +79,11 @@ class Cache * 切换缓存类型 需要配置 cache.type 为 complex * @access public * @param string $name 缓存标识 - * @return \think\cache\Driver + * @return Driver */ - public static function store($name) + public static function store($name = '') { - if ('complex' == Config::get('cache.type')) { + if ('' !== $name && 'complex' == Config::get('cache.type')) { self::connect(Config::get('cache.' . $name), strtolower($name)); } return self::$handler; @@ -185,13 +185,42 @@ class Cache return self::$handler->clear($tag); } + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public static function pull($name) + { + self::init(); + self::$readTimes++; + self::$writeTimes++; + return self::$handler->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::init(); + self::$readTimes++; + return self::$handler->remember($name, $value, $expire); + } + /** * 缓存标签 * @access public * @param string $name 标签名 * @param string|array $keys 缓存标识 * @param bool $overlay 是否覆盖 - * @return \think\cache\Driver + * @return Driver */ public static function tag($name, $keys = null, $overlay = false) { diff --git a/library/think/Collection.php b/library/think/Collection.php index 8315268a..41b42759 100644 --- a/library/think/Collection.php +++ b/library/think/Collection.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -225,11 +225,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria $result = []; foreach ($this->items as $row) { - $key = $value = null; + $key = $value = null; $keySet = $valueSet = false; if (null !== $index_key && array_key_exists($index_key, $row)) { $keySet = true; - $key = (string)$row[$index_key]; + $key = (string) $row[$index_key]; } if (null === $column_key) { $valueSet = true; @@ -368,6 +368,6 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria if ($items instanceof self) { return $items->all(); } - return (array)$items; + return (array) $items; } } diff --git a/library/think/Config.php b/library/think/Config.php index 5ab032fd..fc6c50ac 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -59,11 +59,14 @@ class Config self::$config[$range] = []; } if (is_file($file)) { + $name = strtolower($name); $type = pathinfo($file, PATHINFO_EXTENSION); - if ('php' != $type) { - return self::parse($file, $type, $name, $range); - } else { + if ('php' == $type) { return self::set(include $file, $name, $range); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return self::set(yaml_parse_file($file), $name, $range); + } else { + return self::parse($file, $type, $name, $range); } } else { return self::$config[$range]; diff --git a/library/think/Console.php b/library/think/Console.php index ab8e0211..1d97ab2b 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -88,18 +88,19 @@ class Console } /** - * @param $command - * @param array $parameters + * @param $command + * @param array $parameters + * @param string $driver * @return Output|Buffer */ - public static function call($command, array $parameters = []) + public static function call($command, array $parameters = [], $driver = 'buffer') { $console = self::init(false); array_unshift($parameters, $command); $input = new Input($parameters); - $output = new Output('buffer'); + $output = new Output($driver); $console->setCatchExceptions(false); $console->find($command)->run($input, $output); @@ -317,7 +318,7 @@ class Console if (!$command->isEnabled()) { $command->setConsole(null); - return null; + return; } if (null === $command->getDefinition()) { diff --git a/library/think/Controller.php b/library/think/Controller.php index a889e4d9..69db0a1d 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,16 +13,19 @@ namespace think; \think\Loader::import('controller/Jump', TRAIT_PATH, EXT); -use think\Exception; use think\exception\ValidateException; class Controller { use \traits\controller\Jump; - // 视图类实例 + /** + * @var \think\View 视图类实例 + */ protected $view; - // Request实例 + /** + * @var \think\Request Request实例 + */ protected $request; // 验证失败是否抛出异常 protected $failException = false; diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 648b2c60..93edb8d7 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -99,6 +99,22 @@ class Cookie $_COOKIE[$name] = $value; } + /** + * 永久保存Cookie数据 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + $option['expire'] = 315360000; + self::set($name, $value, $option); + } + /** * 判断Cookie数据 * @param string $name cookie名称 @@ -133,7 +149,7 @@ class Cookie } return $value; } else { - return null; + return; } } diff --git a/library/think/Db.php b/library/think/Db.php index b00aee61..00f719e0 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,10 +11,8 @@ namespace think; -use think\App; -use think\Collection; +use think\db\Connection; use think\db\Query; -use think\paginator\Collection as PaginatorCollection; /** * Class Db @@ -26,22 +24,25 @@ use think\paginator\Collection as PaginatorCollection; * @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 = true , integer $expire = null) static 设置查询缓存 + * @method Query cache(mixed $key = null , integer $expire = 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 = []) static 查询单个记录 - * @method mixed select(mixed $data = []) static 查询多个记录 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID * @method integer insertAll(array $dataSet) static 插入多条记录 * @method integer update(array $data) static 更新记录 - * @method integer delete(mixed $data = []) static 删除记录 + * @method integer delete(mixed $data = null) static 删除记录 * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 - * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = false) static SQL查询 + * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = null) static SQL查询 * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 - * @method PaginatorCollection paginate(integer $listRows = 15, mixed $simple = false, array $config = []) static 分页查询 + * @method 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语句 */ class Db @@ -59,7 +60,7 @@ class Db * @access public * @param mixed $config 连接配置 * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Connection + * @return Connection * @throws Exception */ public static function connect($config = [], $name = false) diff --git a/library/think/Debug.php b/library/think/Debug.php index e36a764e..844dfb89 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,11 +11,7 @@ namespace think; -use think\Config; use think\exception\ClassNotFoundException; -use think\Log; -use think\Request; -use think\Response; use think\response\Redirect; class Debug @@ -178,8 +174,8 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); - return null; + echo($output); + return; } else { return $output; } diff --git a/library/think/Env.php b/library/think/Env.php index a23d9925..6f31841d 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/Error.php b/library/think/Error.php index 28f75882..c17292bf 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/Exception.php b/library/think/Exception.php index ac648764..034c85b6 100644 --- a/library/think/Exception.php +++ b/library/think/Exception.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/File.php b/library/think/File.php index 43f38575..ba59273f 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -39,7 +39,7 @@ class File extends SplFileObject public function __construct($filename, $mode = 'r') { parent::__construct($filename, $mode); - $this->filename = $this->getRealPath(); + $this->filename = $this->getRealPath() ?: $this->getPathname(); } /** diff --git a/library/think/Hook.php b/library/think/Hook.php index 062ac962..f06196e4 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -1,9 +1,8 @@ $behavior) { + self::add($tag, $behavior); + } + } else { + self::$tags = $tags + self::$tags; + } + } - /** - * 批量导入插件 - * @param array $tags 插件信息 - * @param boolean $recursive 是否递归合并 - */ - public static function import(array $tags, $recursive = true) - { - if ($recursive) { - foreach ($tags as $tag => $behavior) { - self::add($tag, $behavior); - } - } else { - self::$tags = $tags + self::$tags; - } - } + /** + * 获取插件信息 + * @param string $tag 插件位置 留空获取全部 + * @return array + */ + public static function get($tag = '') + { + if (empty($tag)) { + //获取全部的插件信息 + return self::$tags; + } else { + return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + } + } - /** - * 获取插件信息 - * @param string $tag 插件位置 留空获取全部 - * @return array - */ - public static function get($tag = '') - { - if (empty($tag)) {//获取全部的插件信息 - return self::$tags; - } else { - return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; - } - } + /** + * 监听标签的行为 + * @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) + { + $results = []; + $tags = static::get($tag); + foreach ($tags as $key => $name) { + $results[$key] = self::exec($name, $tag, $params, $extra); + if (false === $results[$key]) { + // 如果返回false 则中断行为执行 + break; + } elseif (!is_null($results[$key]) && $once) { + break; + } + } + return $once ? end($results) : $results; + } - /** - * 监听标签的行为 - * @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) - { - $results = []; - $tags = static::get($tag); - foreach ($tags as $key => $name) { - $results[$key] = self::exec($name, $tag, $params, $extra); - if (false === $results[$key]) {// 如果返回false 则中断行为执行 - break; - } elseif (!is_null($results[$key]) && $once) { - break; - } - } - return $once ? end($results) : $results; - } + /** + * 执行某个行为 + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param Mixed $params 传人的参数 + * @param mixed $extra 额外参数 + * @return mixed + */ + public static function exec($class, $tag = '', &$params = null, $extra = null) + { + App::$debug && Debug::remark('behavior_start', 'time'); + $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; - /** - * 执行某个行为 - * @param mixed $class 要执行的行为 - * @param string $tag 方法名(标签名) - * @param Mixed $params 传人的参数 - * @param mixed $extra 额外参数 - * @return mixed - */ - public static function exec($class, $tag = '', &$params = null, $extra = null) - { - App::$debug && Debug::remark('behavior_start', 'time'); - if (is_callable($class)) { - $result = call_user_func_array($class, [ & $params, $extra]); - $class = 'Closure'; - } elseif (is_object($class)) { - $result = call_user_func_array([$class, $tag], [ & $params, $extra]); - $class = get_class($class); - } else { - $obj = new $class(); - $result = ($tag && is_callable([$obj, $tag])) ? $obj->$tag($params, $extra) : $obj->run($params, $extra); - } - if (App::$debug) { - Debug::remark('behavior_end', 'time'); - Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); - } - return $result; - } + $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]); + } else { + $obj = new $class(); + $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; + $result = $obj->$method($params, $extra); + } + if (App::$debug) { + Debug::remark('behavior_end', 'time'); + Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + } + return $result; + } } diff --git a/library/think/Lang.php b/library/think/Lang.php index 16e7f545..d52f1947 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,10 +11,6 @@ namespace think; -use think\App; -use think\Cookie; -use think\Log; - class Lang { // 语言数据 @@ -83,7 +79,9 @@ class Lang // 记录加载信息 App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); $_lang = include $_file; - $lang = array_change_key_case($_lang) + $lang; + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } } } if (!empty($lang)) { diff --git a/library/think/Loader.php b/library/think/Loader.php index cfa0a217..70de3175 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -365,15 +365,20 @@ class Loader */ public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') { - if (isset(self::$instance[$name . $layer])) { - return self::$instance[$name . $layer]; + $guid = $name . $layer; + if (isset(self::$instance[$guid])) { + return self::$instance[$guid]; } - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); + if (strpos($name, '\\')) { + $class = $name; } else { - $module = Request::instance()->module(); + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = Request::instance()->module(); + } + $class = self::parseClass($module, $layer, $name, $appendSuffix); } - $class = self::parseClass($module, $layer, $name, $appendSuffix); if (class_exists($class)) { $model = new $class(); } else { @@ -384,7 +389,7 @@ class Loader throw new ClassNotFoundException('class not exists:' . $class, $class); } } - self::$instance[$name . $layer] = $model; + self::$instance[$guid] = $model; return $model; } @@ -399,12 +404,16 @@ class Loader */ public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name); + if (strpos($name, '\\')) { + $class = $name; } else { - $module = Request::instance()->module(); + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name); + } else { + $module = Request::instance()->module(); + } + $class = self::parseClass($module, $layer, $name, $appendSuffix); } - $class = self::parseClass($module, $layer, $name, $appendSuffix); if (class_exists($class)) { return App::invokeClass($class); } elseif ($empty && class_exists($emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix))) { @@ -427,16 +436,20 @@ class Loader if (empty($name)) { return new Validate; } - - if (isset(self::$instance[$name . $layer])) { - return self::$instance[$name . $layer]; + $guid = $name . $layer; + if (isset(self::$instance[$guid])) { + return self::$instance[$guid]; } - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name); + if (strpos($name, '\\')) { + $class = $name; } else { - $module = Request::instance()->module(); + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name); + } else { + $module = Request::instance()->module(); + } + $class = self::parseClass($module, $layer, $name, $appendSuffix); } - $class = self::parseClass($module, $layer, $name, $appendSuffix); if (class_exists($class)) { $validate = new $class; } else { @@ -447,7 +460,7 @@ class Loader throw new ClassNotFoundException('class not exists:' . $class, $class); } } - self::$instance[$name . $layer] = $validate; + self::$instance[$guid] = $validate; return $validate; } @@ -493,14 +506,16 @@ class Loader * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 * @param string $name 字符串 * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) * @return string */ - public static function parseName($name, $type = 0) + public static function parseName($name, $type = 0, $ucfirst = true) { if ($type) { - return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { return strtoupper($match[1]); - }, $name)); + }, $name); + return $ucfirst ? ucfirst($name) : lcfirst($name); } else { return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); } diff --git a/library/think/Log.php b/library/think/Log.php index 5fd1631a..4a8b0b40 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -32,13 +32,14 @@ class Log const SQL = 'sql'; const NOTICE = 'notice'; const ALERT = 'alert'; + const DEBUG = 'debug'; // 日志信息 protected static $log = []; // 配置参数 protected static $config = []; // 日志类型 - protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert']; + protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; // 日志写入驱动 protected static $driver; @@ -83,6 +84,10 @@ class Log public static function record($msg, $type = 'log') { self::$log[$type][] = $msg; + if (IS_CLI && count(self::$log[$type]) > 100) { + // 命令行下面日志写入改进 + self::save(); + } } /** @@ -136,6 +141,9 @@ class Log if (empty(self::$config['level'])) { // 获取全部日志 $log = self::$log; + if (!App::$debug && isset($log['debug'])) { + unset($log['debug']); + } } else { // 记录允许级别 $log = []; @@ -180,7 +188,7 @@ class Log self::init(Config::get('log')); } // 写入日志 - return self::$driver->save($log); + return self::$driver->save($log, false); } /** diff --git a/library/think/Model.php b/library/think/Model.php index f062e3c5..085ca053 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,31 +12,22 @@ namespace think; use InvalidArgumentException; -use think\Cache; -use think\Config; -use think\Db; use think\db\Query; -use think\Exception; use think\Exception\ValidateException; -use think\Loader; +use think\model\Collection; use think\model\Relation; -use think\paginator\Collection as PaginatorCollection; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphTo; /** * Class Model * @package think - * @method static PaginatorCollection paginate(integer $listRows = 15, boolean $simple = false, array $config = []) 分页查询 - * @method static mixed value($field, $default = null) 得到某个字段的值 - * @method static array column($field, $key = '') 得到某个列的数组 - * @method static integer count($field = '*') COUNT查询 - * @method static integer sum($field = '*') SUM查询 - * @method static integer min($field = '*') MIN查询 - * @method static integer max($field = '*') MAX查询 - * @method static integer avg($field = '*') AVG查询 - * @method static setField($field, $value = '') - * @method static Query where($field, $op = null, $condition = null) 指定AND查询条件 - * @method static static findOrFail($data = null) 查找单条记录 如果不存在则抛出异常 - * + * @mixin Query */ abstract class Model implements \JsonSerializable, \ArrayAccess { @@ -88,7 +79,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 更新时间字段 protected $updateTime = 'update_time'; // 时间字段取出后的默认时间格式 - protected $dateFormat = 'Y-m-d H:i:s'; + protected $dateFormat; // 字段类型或者格式转换 protected $type = []; // 是否为更新数据 @@ -101,6 +92,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $failException = false; // 全局查询范围 protected $useGlobalScope = true; + // 是否采用批量验证 + protected $batchValidate = false; + // 查询数据集对象 + protected $resultSetType; + // 关联自动写入 + protected $relationWrite; + // + protected static $db; /** * 初始化过的模型. @@ -137,9 +136,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = $this->db()->getConfig('auto_timestamp'); + $this->autoWriteTimestamp = $this->db(false)->getConfig('auto_timestamp'); } + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $this->db(false)->getConfig('datetime_format'); + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->db(false)->getConfig('resultset_type'); + } // 执行初始化操作 $this->initialize(); } @@ -154,8 +161,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { $model = $this->class; if (!isset(self::$links[$model])) { + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); + } else { + $connection = $this->connection; + } + } else { + $connection = []; + } // 设置当前模型 确保查询返回模型对象 - $query = Db::connect($this->connection)->model($model, $this->query); + $query = Db::connect($connection)->getQuery($model, $this->query); // 设置当前数据表和模型名 if (!empty($this->table)) { @@ -172,32 +189,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 全局作用域 if ($baseQuery && method_exists($this, 'base')) { - call_user_func_array([$this, 'base'], [ & self::$links[$model]]); + call_user_func_array([$this, 'base'], [& self::$links[$model]]); } // 返回当前模型的数据库查询对象 return self::$links[$model]; } - /** - * 获取关联模型实例 - * @access protected - * @param string|array $relation 关联查询 - * @return Relation|Query - */ - protected function relation($relation = null) - { - if (!is_null($relation)) { - // 执行关联查询 - return $this->db()->relation($relation); - } - - // 获取关联对象实例 - if (is_null($this->relation)) { - $this->relation = new Relation($this); - } - return $this->relation; - } - /** * 初始化模型 * @access protected @@ -218,12 +215,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @return void */ protected static function init() - {} + { + } /** * 设置数据对象值 * @access public - * @param mixed $data 数据或者属性名 + * @param mixed $data 数据或者属性名 * @param mixed $value 值 * @return $this */ @@ -270,9 +268,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 修改器 设置数据对象值 * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @param array $data 数据 + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 * @return $this */ public function setAttr($name, $value, $data = []) @@ -292,7 +290,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 标记字段更改 - if (!isset($this->data[$name]) || ($this->data[$name] != $value && !in_array($name, $this->change))) { + if (isset($this->data[$name]) && is_scalar($this->data[$name]) && is_scalar($value) && 0 !== strcmp($this->data[$name], $value)) { + $this->change[] = $name; + } elseif (!isset($this->data[$name]) || $value != $this->data[$name]) { $this->change[] = $name; } // 设置数据对象属性 @@ -303,7 +303,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 自动写入时间戳 * @access public - * @param string $name 时间戳字段 + * @param string $name 时间戳字段 * @return mixed */ protected function autoWriteTimestamp($name) @@ -317,26 +317,50 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'datetime': case 'date': $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, $_SERVER['REQUEST_TIME']); + $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $format); break; case 'timestamp': - case 'int': + case 'integer': + default: $value = $_SERVER['REQUEST_TIME']; break; } - } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), ['datetime', 'date', 'timestamp'])) { - $value = date($this->dateFormat, $_SERVER['REQUEST_TIME']); + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp' + ]) + ) { + $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat); } else { - $value = $_SERVER['REQUEST_TIME']; + $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat, true); } return $value; } + /** + * 时间日期字段格式化处理 + * @access public + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp) { + $time = date($format, $time); + } + return $time; + } + /** * 数据写入 类型转换 * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 + * @param mixed $value 值 + * @param string|array $type 要转换的类型 * @return mixed */ protected function writeTransform($value, $type) @@ -367,7 +391,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess break; case 'datetime': $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, is_numeric($value) ? $value : strtotime($value)); + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); break; case 'object': if (is_object($value)) { @@ -383,6 +408,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'serialize': $value = serialize($value); break; + } return $value; } @@ -411,11 +437,24 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } elseif (isset($this->type[$name])) { // 类型转换 $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp' + ]) + ) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } } elseif ($notFound) { - $method = Loader::parseName($name, 1); - if (method_exists($this, $method) && !method_exists('\think\Model', $method)) { + $method = Loader::parseName($name, 1, false); + if (method_exists($this, $method) && $this->$method() instanceof Relation) { + // 清空之前的查询参数 + $this->$method()->removeOption(); // 不存在该字段 获取关联数据 - $value = $this->relation()->getRelation($method); + $value = $this->$method()->getRelation(); // 保存关联对象值 $this->data[$name] = $value; } else { @@ -428,8 +467,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 数据读取 类型转换 * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 + * @param mixed $value 值 + * @param string|array $type 要转换的类型 * @return mixed */ protected function readTransform($value, $type) @@ -456,13 +495,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'timestamp': if (!is_null($value)) { $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, $value); + $value = $this->formatDateTime($value, $format); } break; case 'datetime': if (!is_null($value)) { $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, strtotime($value)); + $value = $this->formatDateTime(strtotime($value), $format); } break; case 'json': @@ -477,6 +516,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'serialize': $value = unserialize($value); break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } } return $value; } @@ -484,38 +528,119 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置需要追加的输出属性 * @access public - * @param array $append 属性列表 + * @param array $append 属性列表 + * @param bool $override 是否覆盖 * @return $this */ - public function append($append = []) + public function append($append = [], $override = false) { - $this->append = $append; + $this->append = $override ? $append : array_merge($this->append, $append); + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($relation, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + $model = $this->getAttr($relation); + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if ($this->__isset($key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->setAttr($key, $model->$attr); + } + } + } return $this; } /** * 设置需要隐藏的输出属性 * @access public - * @param array $hidden 属性列表 + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 * @return $this */ - public function hidden($hidden = []) + public function hidden($hidden = [], $override = false) { - $this->hidden = $hidden; + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); return $this; } /** * 设置需要输出的属性 + * @access public * @param array $visible + * @param bool $override 是否覆盖 * @return $this */ - public function visible($visible = []) + public function visible($visible = [], $override = false) { - $this->visible = $visible; + $this->visible = $override ? $visible : array_merge($this->visible, $visible); return $this; } + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|Collection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + /** * 转换当前模型对象为数组 * @access public @@ -523,13 +648,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function toArray() { - $item = []; - - //过滤属性 + $item = []; + $visible = []; + $hidden = []; + // 过滤属性 if (!empty($this->visible)) { - $data = array_intersect_key($this->data, array_flip($this->visible)); + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($this->data, array_flip($array)); } elseif (!empty($this->hidden)) { - $data = array_diff_key($this->data, array_flip($this->hidden)); + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($this->data, array_flip($array)); } else { $data = $this->data; } @@ -537,12 +665,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof Collection) { // 关联模型对象 - $item[$key] = $val->toArray(); + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); } elseif (is_array($val) && reset($val) instanceof Model) { // 关联模型数据集 $arr = []; foreach ($val as $k => $value) { - $arr[$k] = $value->toArray(); + $arr[$k] = $this->subToArray($value, $visible, $hidden, $k); } $item[$key] = $arr; } else { @@ -552,8 +680,19 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 追加属性(必须定义获取器) if (!empty($this->append)) { - foreach ($this->append as $name) { - $item[$name] = $this->getAttr($name); + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $item[$name] = $this->getAttr($name); + } } } return !empty($item) ? $item : []; @@ -562,7 +701,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 转换当前模型对象为JSON字符串 * @access public - * @param integer $options json参数 + * @param integer $options json参数 * @return string */ public function toJson($options = JSON_UNESCAPED_UNICODE) @@ -570,6 +709,40 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return json_encode($this->toArray(), $options); } + /** + * 转换当前模型数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @return Collection + */ + public function toCollection($collection) + { + if ($this->resultSetType) { + if ('collection' == $this->resultSetType) { + $collection = new Collection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { + $class = $this->resultSetType; + $collection = new $class($collection); + } + } + return $collection; + } + + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + /** * 获取模型对象的主键 * @access public @@ -579,10 +752,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public function getPk($name = '') { if (!empty($name)) { - $table = $this->db()->getTable($name); - return $this->db()->getPk($table); + $table = $this->db(false)->getTable($name); + return $this->db(false)->getPk($table); } elseif (empty($this->pk)) { - $this->pk = $this->db()->getPk(); + $this->pk = $this->db(false)->getPk(); } return $this->pk; } @@ -607,9 +780,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 保存当前数据对象 * @access public - * @param array $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 * @return integer|false */ public function save($data = [], $where = [], $sequence = null) @@ -628,8 +801,32 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } + // 自动关联写入 + if (!empty($this->relationWrite)) { + $relation = []; + foreach ($this->relationWrite as $key => $name) { + if (!is_numeric($key)) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } + } + } elseif (isset($this->data[$name])) { + $relation[$name] = $this->data[$name]; + if (!$this->isUpdate) { + unset($this->data[$name]); + } + } + } + } + // 检测字段 if (!empty($this->field)) { + if (true === $this->field) { + $this->field = $this->db(false)->getTableInfo('', 'fields'); + } foreach ($this->data as $key => $val) { if (!in_array($key, $this->field)) { unset($this->data[$key]); @@ -641,7 +838,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->autoCompleteData($this->auto); // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime) { + if ($this->autoWriteTimestamp && $this->updateTime && (empty($this->change) || !in_array($this->updateTime, $this->change))) { $this->setAttr($this->updateTime, null); } @@ -649,7 +846,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (false === $this->trigger('before_write', $this)) { return false; } - + $pk = $this->getPk(); if ($this->isUpdate) { // 自动更新 $this->autoCompleteData($this->update); @@ -680,18 +877,41 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $where = $this->updateWhere; } - if (!empty($where)) { - $pk = $this->getPk(); - if (is_string($pk) && isset($data[$pk])) { - if (!isset($where[$pk])) { - unset($where); - $where[$pk] = $data[$pk]; + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + + // 关联更新 + if (isset($relation)) { + foreach ($relation as $name => $val) { + if (isset($data[$name])) { + unset($data[$name]); } - unset($data[$pk]); } } + // 模型更新 $result = $this->db()->where($where)->update($data); + + // 关联更新 + if (isset($relation)) { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + // 清空change $this->change = []; // 更新回调 @@ -701,7 +921,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->autoCompleteData($this->insert); // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime) { + if ($this->autoWriteTimestamp && $this->createTime && (empty($this->change) || !in_array($this->createTime, $this->change))) { $this->setAttr($this->createTime, null); } @@ -712,13 +932,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $result = $this->db()->insert($this->data); // 获取自动增长主键 - if ($result) { + if ($result && is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { $insertId = $this->db()->getLastInsID($sequence); - $pk = $this->getPk(); - if (is_string($pk) && $insertId) { + if ($insertId) { $this->data[$pk] = $insertId; } } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + // 标记为更新 $this->isUpdate = true; // 清空change @@ -735,9 +963,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 保存多个数据到当前数据对象 * @access public - * @param array $dataSet 数据 - * @param boolean $replace 是否自动识别更新和写入 + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 * @return array|false + * @throws \Exception */ public function saveAll($dataSet, $replace = true) { @@ -745,7 +974,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 数据批量验证 $validate = $this->validate; foreach ($dataSet as $data) { - if (!$this->validate($validate)->validateData($data)) { + if (!$this->validateData($data, $validate)) { return false; } } @@ -761,9 +990,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } foreach ($dataSet as $key => $data) { if (!empty($auto) && isset($data[$pk])) { - $result[$key] = self::update($data); + $result[$key] = self::update($data, [], $this->field); } else { - $result[$key] = self::create($data); + $result[$key] = self::create($data, $this->field); } } $db->commit(); @@ -777,13 +1006,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置允许写入的字段 * @access public - * @param bool|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @param mixed $field 允许写入的字段 如果为true只允许写入数据表字段 * @return $this */ public function allowField($field) { - if (true === $field) { - $field = $this->db()->getTableInfo('', 'fields'); + if (is_string($field)) { + $field = explode(',', $field); } $this->field = $field; return $this; @@ -835,7 +1064,29 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->db()->delete($this->data); + // 删除条件 + $pk = $this->getPk(); + if (isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + + // 删除当前模型数据 + $result = $this->db()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } $this->trigger('after_delete', $this); return $result; @@ -856,11 +1107,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置字段验证 * @access public - * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 - * @param array $msg 提示信息 + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 + * @param bool $batch 批量验证 * @return $this */ - public function validate($rule = true, $msg = []) + public function validate($rule = true, $msg = [], $batch = false) { if (is_array($rule)) { $this->validate = [ @@ -870,6 +1122,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } else { $this->validate = true === $rule ? $this->name : $rule; } + $this->batchValidate = $batch; return $this; } @@ -888,13 +1141,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 自动验证数据 * @access protected - * @param array $data 验证数据 + * @param array $data 验证数据 + * @param mixed $rule 验证规则 + * @param bool $batch 批量验证 * @return bool */ - protected function validateData($data) + protected function validateData($data, $rule = null, $batch = null) { - if (!empty($this->validate)) { - $info = $this->validate; + $info = is_null($rule) ? $this->validate : $rule; + + if (!empty($info)) { if (is_array($info)) { $validate = Loader::validate(); $validate->rule($info['rule']); @@ -909,7 +1165,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $validate->scene($scene); } } - if (!$validate->check($data)) { + $batch = is_null($batch) ? $this->batchValidate : $batch; + + if (!$validate->batch($batch)->check($data)) { $this->error = $validate->getError(); if ($this->failException) { throw new ValidateException($this->error); @@ -935,9 +1193,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 注册回调方法 * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @param bool $override 是否覆盖 + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 * @return void */ public static function event($event, $callback, $override = false) @@ -952,8 +1210,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 触发事件 * @access protected - * @param string $event 事件名 - * @param mixed $params 传入参数(引用) + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) * @return bool */ protected function trigger($event, &$params) @@ -961,7 +1219,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (isset(self::$event[$this->class][$event])) { foreach (self::$event[$this->class][$event] as $callback) { if (is_callable($callback)) { - $result = call_user_func_array($callback, [ & $params]); + $result = call_user_func_array($callback, [& $params]); if (false === $result) { return false; } @@ -974,12 +1232,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 写入数据 * @access public - * @param array $data 数据数组 + * @param array $data 数据数组 + * @param array|true $field 允许字段 * @return $this */ - public static function create($data = []) + public static function create($data = [], $field = null) { $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } $model->isUpdate(false)->save($data, []); return $model; } @@ -987,13 +1249,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 更新数据 * @access public - * @param array $data 数据数组 - * @param array $where 更新条件 + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 * @return $this */ - public static function update($data = [], $where = []) + public static function update($data = [], $where = [], $field = null) { - $model = new static(); + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } $result = $model->isUpdate(true)->save($data, $where); return $model; } @@ -1031,9 +1297,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 分析查询表达式 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 * @return Query */ protected static function parseQuery(&$data, $with, $cache) @@ -1043,7 +1309,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $result = $result->where($data); $data = null; } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $result]); + call_user_func_array($data, [& $result]); $data = null; } elseif ($data instanceof Query) { $result = $data->with($with)->cache($cache); @@ -1066,7 +1332,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query->where($data); $data = null; } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $query]); + call_user_func_array($data, [& $query]); $data = null; } elseif (is_null($data)) { return 0; @@ -1085,9 +1351,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 命名范围 * @access public - * @param string|array|Closure $name 命名范围名称 逗号分隔 - * @param mixed ...$params 参数调用 - * @return Model + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Model|Query */ public static function scope($name) { @@ -1115,72 +1381,46 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置是否使用全局查询范围 - * @param bool $use 是否启用全局查询范围 + * @param bool $use 是否启用全局查询范围 * @access public * @return Model */ public static function useGlobalScope($use) { - $model = new static(); - $model->useGlobalScope = $use; + $model = new static(); + static::$db = $model->db($use); return $model; } /** * 根据关联条件查询当前模型 * @access public - * @param string $relation 关联方法名 - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 * @return Model */ public static function has($relation, $operator = '>=', $count = 1, $id = '*') { $model = new static(); - $info = $model->$relation()->getRelationInfo(); - $table = $info['model']::getTable(); - switch ($info['type']) { - case Relation::HAS_MANY: - return $model->db()->alias('a') - ->join($table . ' b', 'a.' . $info['localKey'] . '=b.' . $info['foreignKey'], $info['joinType']) - ->group('b.' . $info['foreignKey']) - ->having('count(' . $id . ')' . $operator . $count); - case Relation::HAS_MANY_THROUGH: - // TODO + if (is_array($operator) || $operator instanceof \Closure) { + return $model->$relation()->hasWhere($operator); } + return $model->$relation()->has($operator, $count, $id); } /** * 根据关联条件查询当前模型 * @access public - * @param string $relation 关联方法名 - * @param mixed $where 查询条件(数组或者闭包) + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) * @return Model */ public static function hasWhere($relation, $where = []) { $model = new static(); - $info = $model->$relation()->getRelationInfo(); - switch ($info['type']) { - case Relation::HAS_ONE: - case Relation::HAS_MANY: - $table = $info['model']::getTable(); - if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where['b.' . $key] = $val; - unset($where[$key]); - } - } - } - return $model->db()->alias('a') - ->field('a.*') - ->join($table . ' b', 'a.' . $info['localKey'] . '=b.' . $info['foreignKey'], $info['joinType']) - ->where($where); - case Relation::HAS_MANY_THROUGH: - // TODO - } + return $model->$relation()->hasWhere($where); } /** @@ -1211,9 +1451,20 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_string($relations)) { $relations = explode(',', $relations); } - $this->relation(); - foreach ($relations as $relation) { - $this->data[$relation] = $this->relation->getRelation($relation); + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + if (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); } return $this; } @@ -1221,130 +1472,245 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 预载入关联查询 返回数据集 * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 + * @param array $resultSet 数据集 + * @param string $relation 关联名 * @return array */ - public function eagerlyResultSet($resultSet, $relation) - { - return $this->relation()->eagerlyResultSet($resultSet, $relation); + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } } /** * 预载入关联查询 返回模型对象 * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 + * @param Model $result 数据对象 + * @param string $relation 关联名 * @return Model */ - public function eagerlyResult($result, $relation) + public function eagerlyResult(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param string|array $relation 关联名 + * @return void + */ + public function relationCount(&$result, $relation) { - return $this->relation()->eagerlyResult($result, $relation); + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure); + $result->setAttr(Loader::parseName($relation) . '_count', $count); + } + } + + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; } /** * HAS ONE 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return Relation + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return HasOne */ public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') { // 记录当前关联信息 $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->hasOne($model, $foreignKey, $localKey, $alias, $joinType); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); } /** * BELONGS TO 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $otherKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return Relation + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return BelongsTo */ - public function belongsTo($model, $foreignKey = '', $otherKey = '', $alias = [], $joinType = 'INNER') + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') { // 记录当前关联信息 $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: Loader::parseName(basename(str_replace('\\', '/', $model))) . '_id'; - $otherKey = $otherKey ?: (new $model)->getPk(); - return $this->relation()->belongsTo($model, $foreignKey, $otherKey, $alias, $joinType); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType); } /** * HAS MANY 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return Relation + * @param string $localKey 关联主键 + * @return HasMany */ - public function hasMany($model, $foreignKey = '', $localKey = '', $alias = []) + public function hasMany($model, $foreignKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->hasMany($model, $foreignKey, $localKey, $alias); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasMany($this, $model, $foreignKey, $localKey); } /** * HAS MANY 远程关联定义 * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 + * @param string $model 模型名 + * @param string $through 中间模型名 * @param string $foreignKey 关联外键 * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return Relation + * @param string $localKey 关联主键 + * @return HasManyThrough */ - public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '', $alias = []) + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $through = $this->parseModel($through); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - $name = Loader::parseName(basename(str_replace('\\', '/', $through))); - $throughKey = $throughKey ?: $name . '_id'; - return $this->relation()->hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); } /** * BELONGS TO MANY 关联定义 * @access public - * @param string $model 模型名 - * @param string $table 中间表名 + * @param string $model 模型名 + * @param string $table 中间表名 * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 - * @return Relation + * @param string $localKey 当前模型关联键 + * @return BelongsToMany */ - public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '', $alias = []) + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $name = Loader::parseName(basename(str_replace('\\', '/', $model))); - $table = $table ?: $this->db()->getTable(Loader::parseName($this->name) . '_' . $name); + $table = $table ?: $this->db(false)->getTable(Loader::parseName($this->name) . '_' . $name); $foreignKey = $foreignKey ?: $name . '_id'; - $localKey = $localKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->belongsToMany($model, $table, $foreignKey, $localKey, $alias); + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: Loader::parseName($this->name); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphTo($this, $morphType, $foreignKey, $alias); } public function __call($method, $args) { - $query = $this->db(); + if (isset(static::$db)) { + $query = static::$db; + static::$db = null; + } else { + $query = $this->db(); + } + if (method_exists($this, 'scope' . $method)) { // 动态调用命名范围 $method = 'scope' . $method; @@ -1358,24 +1724,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public static function __callStatic($method, $params) { - $query = self::getDb(); - return call_user_func_array([$query, $method], $params); - } - - protected static function getDb() - { - $model = get_called_class(); - if (!isset(self::$links[$model])) { - self::$links[$model] = (new static())->db(); + if (isset(static::$db)) { + $query = static::$db; + static::$db = null; + } else { + $query = (new static())->db(); } - return self::$links[$model]; + + return call_user_func_array([$query, $method], $params); } /** * 修改器 设置数据对象的值 * @access public - * @param string $name 名称 - * @param mixed $value 值 + * @param string $name 名称 + * @param mixed $value 值 * @return void */ public function __set($name, $value) @@ -1466,4 +1829,49 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->initialize(); } + /** + * 模型事件快捷方法 + * @param $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + } diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 7385ebb0..0c1bea8a 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,15 +11,19 @@ namespace think; -use think\paginator\Collection as PaginatorCollection; -use think\Request; +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; -abstract class Paginator +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { /** @var bool 是否为简洁模式 */ protected $simple = false; - /** @var PaginatorCollection 数据集 */ + /** @var Collection 数据集 */ protected $items; /** @var integer 当前页 */ @@ -42,33 +46,33 @@ abstract class Paginator 'var_page' => 'page', 'path' => '/', 'query' => [], - 'fragment' => '' + 'fragment' => '', ]; - protected function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { $this->options = array_merge($this->options, $options); - $this->options['path'] = $this->options['path'] != '/' ? rtrim($this->options['path'], '/') : $this->options['path']; + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; $this->simple = $simple; $this->listRows = $listRows; + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + if ($simple) { - if (!$items instanceof Collection) { - $items = Collection::make($items); - } $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = count($items) > ($this->listRows); $items = $items->slice(0, $this->listRows); } else { $this->total = $total; - $this->lastPage = (int)ceil($total / $listRows); + $this->lastPage = (int) ceil($total / $listRows); $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = $this->currentPage < $this->lastPage; } - - $this->items = PaginatorCollection::make($items, $this); + $this->items = $items; } /** @@ -78,12 +82,11 @@ abstract class Paginator * @param bool $simple * @param null $total * @param array $options - * @return PaginatorCollection + * @return Paginator */ public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { - $paginator = new static($items, $listRows, $currentPage, $total, $simple, $options); - return $paginator->items; + return new static($items, $listRows, $currentPage, $total, $simple, $options); } protected function setCurrentPage($currentPage) @@ -134,7 +137,7 @@ abstract class Paginator { $page = Request::instance()->request($varPage); - if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) { + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { return $page; } @@ -182,7 +185,7 @@ abstract class Paginator */ public function hasPages() { - return !($this->currentPage == 1 && !$this->hasMore); + return !(1 == $this->currentPage && !$this->hasMore); } /** @@ -239,7 +242,6 @@ abstract class Paginator return $this; } - /** * 构造锚点字符串 * @@ -255,4 +257,113 @@ abstract class Paginator * @return mixed */ abstract public function render(); -} \ No newline at end of file + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Retrieve an external iterator + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + try { + $total = $this->total(); + } catch (Exception $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray() + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + return call_user_func_array([$this->getCollection(), $name], $arguments); + } + +} diff --git a/library/think/Process.php b/library/think/Process.php index 1982de24..6f3faa31 100644 --- a/library/think/Process.php +++ b/library/think/Process.php @@ -14,9 +14,9 @@ namespace think; use think\process\exception\Failed as ProcessFailedException; use think\process\exception\Timeout as ProcessTimeoutException; use think\process\pipes\Pipes; -use think\process\Utils; use think\process\pipes\Unix as UnixPipes; use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; class Process { @@ -47,10 +47,10 @@ class Process private $exitcode; private $fallbackExitcode; private $processInformation; - private $outputDisabled = false; + private $outputDisabled = false; private $stdout; private $stderr; - private $enhanceWindowsCompatibility = true; + private $enhanceWindowsCompatibility = true; private $enhanceSigchildCompatibility; private $process; private $status = self::STATUS_READY; @@ -147,7 +147,7 @@ class Process $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); $this->options = array_replace([ 'suppress_errors' => true, - 'binary_pipes' => true + 'binary_pipes' => true, ], $options); } @@ -490,7 +490,7 @@ class Process public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { - return null; + return; } return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; @@ -586,7 +586,7 @@ class Process */ public function isStarted() { - return $this->status != self::STATUS_READY; + return self::STATUS_READY != $this->status; } /** @@ -597,7 +597,7 @@ class Process { $this->updateStatus(false); - return $this->status == self::STATUS_TERMINATED; + return self::STATUS_TERMINATED == $this->status; } /** @@ -645,7 +645,7 @@ class Process * @param string $line */ public function addOutput($line) - { +{ $this->lastOutputTime = microtime(true); $this->stdout .= $line; } @@ -655,7 +655,7 @@ class Process * @param string $line */ public function addErrorOutput($line) - { +{ $this->lastOutputTime = microtime(true); $this->stderr .= $line; } @@ -665,7 +665,7 @@ class Process * @return string */ public function getCommandLine() - { +{ return $this->commandline; } @@ -675,7 +675,7 @@ class Process * @return self */ public function setCommandLine($commandline) - { +{ $this->commandline = $commandline; return $this; @@ -686,7 +686,7 @@ class Process * @return float|null */ public function getTimeout() - { +{ return $this->timeout; } @@ -695,7 +695,7 @@ class Process * @return float|null */ public function getIdleTimeout() - { +{ return $this->idleTimeout; } @@ -705,7 +705,7 @@ class Process * @return self */ public function setTimeout($timeout) - { +{ $this->timeout = $this->validateTimeout($timeout); return $this; @@ -717,7 +717,7 @@ class Process * @return self */ public function setIdleTimeout($timeout) - { +{ if (null !== $timeout && $this->outputDisabled) { throw new \LogicException('Idle timeout can not be set while the output is disabled.'); } @@ -733,7 +733,7 @@ class Process * @return self */ public function setTty($tty) - { +{ if ('\\' === DS && $tty) { throw new \RuntimeException('TTY mode is not supported on Windows platform.'); } @@ -741,7 +741,7 @@ class Process throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); } - $this->tty = (bool)$tty; + $this->tty = (bool) $tty; return $this; } @@ -751,7 +751,7 @@ class Process * @return bool */ public function isTty() - { +{ return $this->tty; } @@ -761,8 +761,8 @@ class Process * @return self */ public function setPty($bool) - { - $this->pty = (bool)$bool; +{ + $this->pty = (bool) $bool; return $this; } @@ -772,7 +772,7 @@ class Process * @return bool */ public function isPty() - { +{ return $this->pty; } @@ -781,7 +781,7 @@ class Process * @return string|null */ public function getWorkingDirectory() - { +{ if (null === $this->cwd) { return getcwd() ?: null; } @@ -795,7 +795,7 @@ class Process * @return self */ public function setWorkingDirectory($cwd) - { +{ $this->cwd = $cwd; return $this; @@ -806,7 +806,7 @@ class Process * @return array */ public function getEnv() - { +{ return $this->env; } @@ -816,14 +816,14 @@ class Process * @return self */ public function setEnv(array $env) - { +{ $env = array_filter($env, function ($value) { return !is_array($value); }); $this->env = []; foreach ($env as $key => $value) { - $this->env[(binary)$key] = (binary)$value; + $this->env[(binary) $key] = (binary) $value; } return $this; @@ -834,7 +834,7 @@ class Process * @return null|string */ public function getInput() - { +{ return $this->input; } @@ -844,7 +844,7 @@ class Process * @return self */ public function setInput($input) - { +{ if ($this->isRunning()) { throw new \LogicException('Input can not be set while the process is running.'); } @@ -859,7 +859,7 @@ class Process * @return array */ public function getOptions() - { +{ return $this->options; } @@ -869,7 +869,7 @@ class Process * @return self */ public function setOptions(array $options) - { +{ $this->options = $options; return $this; @@ -880,7 +880,7 @@ class Process * @return bool */ public function getEnhanceWindowsCompatibility() - { +{ return $this->enhanceWindowsCompatibility; } @@ -890,8 +890,8 @@ class Process * @return self */ public function setEnhanceWindowsCompatibility($enhance) - { - $this->enhanceWindowsCompatibility = (bool)$enhance; +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; return $this; } @@ -901,7 +901,7 @@ class Process * @return bool */ public function getEnhanceSigchildCompatibility() - { +{ return $this->enhanceSigchildCompatibility; } @@ -911,8 +911,8 @@ class Process * @return self */ public function setEnhanceSigchildCompatibility($enhance) - { - $this->enhanceSigchildCompatibility = (bool)$enhance; +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; return $this; } @@ -921,8 +921,8 @@ class Process * 是否超时 */ public function checkTimeout() - { - if ($this->status !== self::STATUS_STARTED) { +{ + if (self::STATUS_STARTED !== $this->status) { return; } @@ -944,7 +944,7 @@ class Process * @return bool */ public static function isPtySupported() - { +{ static $result; if (null !== $result) { @@ -970,7 +970,7 @@ class Process * @return array */ private function getDescriptors() - { +{ if ('\\' === DS) { $this->processPipes = WindowsPipes::create($this, $this->input); } else { @@ -994,7 +994,7 @@ class Process * @return callable */ protected function buildCallback($callback) - { +{ $out = self::OUT; $callback = function ($type, $data) use ($callback, $out) { if ($out == $type) { @@ -1016,7 +1016,7 @@ class Process * @param bool $blocking */ protected function updateStatus($blocking) - { +{ if (self::STATUS_STARTED !== $this->status) { return; } @@ -1036,7 +1036,7 @@ class Process * @return bool */ protected function isSigchildEnabled() - { +{ if (null !== self::$sigchild) { return self::$sigchild; } @@ -1057,8 +1057,8 @@ class Process * @return float|null */ private function validateTimeout($timeout) - { - $timeout = (float)$timeout; +{ + $timeout = (float) $timeout; if (0.0 === $timeout) { $timeout = null; @@ -1075,15 +1075,15 @@ class Process * @param bool $close */ private function readPipes($blocking, $close) - { +{ $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 == $type) { - $this->fallbackExitcode = (int)$data; + $this->fallbackExitcode = (int) $data; } else { - $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); } } } @@ -1092,7 +1092,7 @@ class Process * 捕获退出码 */ private function captureExitCode() - { +{ if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { $this->exitcode = $this->processInformation['exitcode']; } @@ -1103,7 +1103,7 @@ class Process * @return int 退出码 */ private function close() - { +{ $this->processPipes->close(); if (is_resource($this->process)) { $exitcode = proc_close($this->process); @@ -1117,7 +1117,7 @@ class Process if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { $this->exitcode = $this->fallbackExitcode; } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] - && 0 < $this->processInformation['termsig'] + && 0 < $this->processInformation['termsig'] ) { $this->exitcode = 128 + $this->processInformation['termsig']; } @@ -1129,7 +1129,7 @@ class Process * 重置数据 */ private function resetProcessData() - { +{ $this->starttime = null; $this->callback = null; $this->exitcode = null; @@ -1151,7 +1151,7 @@ class Process * @return bool */ private function doSignal($signal, $throwException) - { +{ if (!$this->isRunning()) { if ($throwException) { throw new \LogicException('Can not send signal on a non running process.'); @@ -1186,7 +1186,7 @@ class Process * @param string $functionName */ private function requireProcessIsStarted($functionName) - { +{ if (!$this->isStarted()) { throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); } @@ -1197,9 +1197,9 @@ class Process * @param string $functionName */ private function requireProcessIsTerminated($functionName) - { +{ if (!$this->isTerminated()) { throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); } } -} \ No newline at end of file +} diff --git a/library/think/Request.php b/library/think/Request.php index 077e190d..b72a5aaf 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,11 +11,6 @@ namespace think; -use think\Config; -use think\Exception; -use think\File; -use think\Session; - class Request { /** @@ -25,7 +20,7 @@ class Request protected $method; /** - * @var string 域名 + * @var string 域名(含协议和端口) */ protected $domain; @@ -121,13 +116,15 @@ class Request protected $input; // 请求缓存 protected $cache; + // 缓存是否检查 + protected $isCheckCache; /** * 架构函数 - * @access public + * @access protected * @param array $options 参数 */ - public function __construct($options = []) + protected function __construct($options = []) { foreach ($options as $name => $item) { if (property_exists($this, $name)) { @@ -224,8 +221,9 @@ class Request if (!isset($info['path'])) { $info['path'] = '/'; } - $options = []; - $queryString = ''; + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; if (isset($info['query'])) { parse_str(html_entity_decode($info['query']), $query); if (!empty($params)) { @@ -238,6 +236,11 @@ class Request } elseif (!empty($params)) { $queryString = http_build_query($params, '', '&'); } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); $server['QUERY_STRING'] = $queryString; $options['cookie'] = $cookie; @@ -248,14 +251,14 @@ class Request $options['baseUrl'] = $info['path']; $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); $options['method'] = $server['REQUEST_METHOD']; - $options['domain'] = $info['scheme'] . '://' . $server['HTTP_HOST']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; $options['content'] = $content; self::$instance = new self($options); return self::$instance; } /** - * 获取当前包含协议的域名 + * 设置或获取当前包含协议的域名 * @access public * @param string $domain 域名 * @return string @@ -272,7 +275,7 @@ class Request } /** - * 获取当前完整URL 包括QUERY_STRING + * 设置或获取当前完整URL 包括QUERY_STRING * @access public * @param string|true $url URL地址 true 带域名获取 * @return string @@ -299,7 +302,7 @@ class Request } /** - * 获取当前URL 不含QUERY_STRING + * 设置或获取当前URL 不含QUERY_STRING * @access public * @param string $url URL地址 * @return string @@ -317,7 +320,7 @@ class Request } /** - * 获取当前执行的文件 SCRIPT_NAME + * 设置或获取当前执行的文件 SCRIPT_NAME * @access public * @param string $file 当前执行的文件 * @return string @@ -349,7 +352,7 @@ class Request } /** - * 获取URL访问根地址 + * 设置或获取URL访问根地址 * @access public * @param string $url URL地址 * @return string @@ -453,7 +456,7 @@ class Request */ public function type() { - $accept = isset($this->server['HTTP_ACCEPT']) ? $this->server['HTTP_ACCEPT'] : $_SERVER['HTTP_ACCEPT']; + $accept = $this->server('HTTP_ACCEPT'); if (empty($accept)) { return false; } @@ -600,14 +603,14 @@ class Request } /** - * 设置获取获取当前请求的参数 + * 获取获取当前请求的参数 * @access public * @param string|array $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed */ - public function param($name = '', $default = null, $filter = null) + public function param($name = '', $default = null, $filter = '') { if (empty($this->param)) { $method = $this->method(true); @@ -644,7 +647,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function route($name = '', $default = null, $filter = null) + public function route($name = '', $default = null, $filter = '') { if (is_array($name)) { $this->param = []; @@ -661,7 +664,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function get($name = '', $default = null, $filter = null) + public function get($name = '', $default = null, $filter = '') { if (empty($this->get)) { $this->get = $_GET; @@ -681,10 +684,15 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function post($name = '', $default = null, $filter = null) + public function post($name = '', $default = null, $filter = '') { if (empty($this->post)) { - $this->post = $_POST; + $content = $this->input; + if (empty($_POST) && 'application/json' == $this->contentType()) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } } if (is_array($name)) { $this->param = []; @@ -701,12 +709,12 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function put($name = '', $default = null, $filter = null) + public function put($name = '', $default = null, $filter = '') { if (is_null($this->put)) { $content = $this->input; - if (strpos($content, '":')) { - $this->put = json_decode($content, true); + if ('application/json' == $this->contentType()) { + $this->put = (array) json_decode($content, true); } else { parse_str($content, $this->put); } @@ -727,7 +735,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function delete($name = '', $default = null, $filter = null) + public function delete($name = '', $default = null, $filter = '') { return $this->put($name, $default, $filter); } @@ -740,7 +748,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function patch($name = '', $default = null, $filter = null) + public function patch($name = '', $default = null, $filter = '') { return $this->put($name, $default, $filter); } @@ -752,7 +760,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function request($name = '', $default = null, $filter = null) + public function request($name = '', $default = null, $filter = '') { if (empty($this->request)) { $this->request = $_REQUEST; @@ -772,7 +780,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function session($name = '', $default = null, $filter = null) + public function session($name = '', $default = null, $filter = '') { if (empty($this->session)) { $this->session = Session::get(); @@ -791,7 +799,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function cookie($name = '', $default = null, $filter = null) + public function cookie($name = '', $default = null, $filter = '') { if (empty($this->cookie)) { $this->cookie = $_COOKIE; @@ -810,7 +818,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function server($name = '', $default = null, $filter = null) + public function server($name = '', $default = null, $filter = '') { if (empty($this->server)) { $this->server = $_SERVER; @@ -845,7 +853,7 @@ class Request $keys = array_keys($file); $count = count($file['name']); for ($i = 0; $i < $count; $i++) { - if (empty($file['tmp_name'][$i])) { + if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { continue; } $temp['key'] = $key; @@ -859,7 +867,7 @@ class Request if ($file instanceof File) { $array[$key] = $file; } else { - if (empty($file['tmp_name'])) { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { continue; } $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); @@ -878,7 +886,7 @@ class Request return $array[$name]; } } - return null; + return; } /** @@ -888,7 +896,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function env($name = '', $default = null, $filter = null) + public function env($name = '', $default = null, $filter = '') { if (empty($this->env)) { $this->env = $_ENV; @@ -910,18 +918,22 @@ class Request { if (empty($this->header)) { $header = []; - $server = $this->server ?: $_SERVER; - foreach ($server as $key => $val) { - if (0 === strpos($key, 'HTTP_')) { - $key = str_replace('_', '-', strtolower(substr($key, 5))); - $header[$key] = $val; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server ?: $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; } - } - if (isset($server['CONTENT_TYPE'])) { - $header['content-type'] = $server['CONTENT_TYPE']; - } - if (isset($server['CONTENT_LENGTH'])) { - $header['content-length'] = $server['CONTENT_LENGTH']; } $this->header = array_change_key_case($header); } @@ -943,7 +955,7 @@ class Request * @param string|array $filter 过滤函数 * @return mixed */ - public function input($data = [], $name = '', $default = null, $filter = null) + public function input($data = [], $name = '', $default = null, $filter = '') { if (false === $name) { // 获取原始数据 @@ -972,13 +984,17 @@ class Request } // 解析过滤器 - $filter = $filter ?: $this->filter; - - if (is_string($filter)) { - $filter = explode(',', $filter); + if (is_null($filter)) { + $filter = []; } else { - $filter = (array) $filter; + $filter = $filter ?: $this->filter; + if (is_string($filter)) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } } + $filter[] = $default; if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); @@ -1237,8 +1253,7 @@ class Request if (false !== $pos) { unset($arr[$pos]); } - - $ip = trim($arr[0]); + $ip = trim(current($arr)); } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif (isset($_SERVER['REMOTE_ADDR'])) { @@ -1249,7 +1264,7 @@ class Request } // IP地址合法验证 $long = sprintf("%u", ip2long($ip)); - $ip = $long ? array($ip, $long) : array('0.0.0.0', 0); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; return $ip[$type]; } @@ -1333,6 +1348,21 @@ class Request return $this->server('REMOTE_PORT'); } + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + list($type) = explode(';', $contentType); + return trim($type); + } + return ''; + } + /** * 获取当前请求的路由信息 * @access public @@ -1366,7 +1396,7 @@ class Request * 设置或者获取当前的模块名 * @access public * @param string $module 模块名 - * @return string|$this + * @return string|Request */ public function module($module = null) { @@ -1382,7 +1412,7 @@ class Request * 设置或者获取当前的控制器名 * @access public * @param string $controller 控制器名 - * @return string|$this + * @return string|Request */ public function controller($controller = null) { @@ -1398,7 +1428,7 @@ class Request * 设置或者获取当前的操作名 * @access public * @param string $action 操作名 - * @return string + * @return string|Request */ public function action($action = null) { @@ -1414,7 +1444,7 @@ class Request * 设置或者获取当前的语言 * @access public * @param string $lang 语言名 - * @return string + * @return string|Request */ public function langset($lang = null) { @@ -1468,15 +1498,34 @@ class Request } /** - * 读取或者设置缓存 + * 设置当前地址的请求缓存 * @access public * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id * @param mixed $expire 缓存有效期 - * @return mixed + * @return void */ public function cache($key, $expire = null) { - if ($this->isGet()) { + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url())], $key); + } + if (false !== strpos($key, ':')) { $param = $this->param(); foreach ($param as $item => $val) { @@ -1484,9 +1533,6 @@ class Request $key = str_replace(':' . $item, $val, $key); } } - } elseif ('__URL__' == $key) { - // 当前URL地址作为缓存标识 - $key = md5($this->url()); } elseif (strpos($key, ']')) { if ('[' . $this->ext() . ']' == $key) { // 缓存某个后缀的请求 @@ -1495,6 +1541,9 @@ class Request return; } } + if (isset($fun)) { + $key = $fun($key); + } if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { // 读取缓存 @@ -1511,7 +1560,7 @@ class Request } /** - * 读取缓存设置 + * 读取请求缓存设置 * @access public * @return array */ diff --git a/library/think/Response.php b/library/think/Response.php index 8b62e419..656198f0 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,11 +11,6 @@ namespace think; -use think\Cache; -use think\Config; -use think\Debug; -use think\Env; -use think\Request; use think\response\Json as JsonResponse; use think\response\Jsonp as JsonpResponse; use think\response\Redirect as RedirectResponse; @@ -103,6 +98,16 @@ class Response Debug::inject($this, $data); } + if (200 == $this->code) { + $cache = Request::instance()->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::set($cache[0], [$data, $this->header], $cache[1]); + } + } + if (!headers_sent() && !empty($this->header)) { // 发送状态码 http_response_code($this->code); @@ -111,16 +116,7 @@ class Response header($name . ':' . $val); } } - if (200 == $this->code) { - $cache = Request::instance()->getCache(); - if ($cache) { - header('Cache-Control: max-age=' . $cache[1] . ',must-revalidate'); - header('Last-Modified:' . gmdate('D, d M Y H:i:s') . ' GMT'); - header('Expires:' . gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'); - $header['Content-Type'] = $this->header['Content-Type']; - Cache::set($cache[0], [$data, $header], $cache[1]); - } - } + echo $data; if (function_exists('fastcgi_finish_request')) { @@ -130,6 +126,11 @@ class Response // 监听response_end Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } } /** @@ -278,7 +279,11 @@ class Response */ public function getHeader($name = '') { - return !empty($name) ? $this->header[$name] : $this->header; + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } } /** diff --git a/library/think/Route.php b/library/think/Route.php index c18a2d67..1629d636 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,26 +11,19 @@ namespace think; -use think\App; -use think\Config; use think\exception\HttpException; -use think\Hook; -use think\Loader; -use think\Log; -use think\Request; -use think\Response; class Route { // 路由规则 private static $rules = [ - 'GET' => [], - 'POST' => [], - 'PUT' => [], - 'DELETE' => [], - 'PATCH' => [], - 'HEAD' => [], - 'OPTIONS' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], '*' => [], 'alias' => [], 'domain' => [], @@ -40,21 +33,22 @@ class Route // REST路由操作方法定义 private static $rest = [ - 'index' => ['GET', '', 'index'], - 'create' => ['GET', '/create', 'create'], - 'edit' => ['GET', '/:id/edit', 'edit'], - 'read' => ['GET', '/:id', 'read'], - 'save' => ['POST', '', 'save'], - 'update' => ['PUT', '/:id', 'update'], - 'delete' => ['DELETE', '/:id', 'delete'], + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '/:id/edit', 'edit'], + 'read' => ['get', '/:id', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/:id', 'update'], + 'delete' => ['delete', '/:id', 'delete'], ]; // 不同请求类型的方法前缀 private static $methodPrefix = [ - 'GET' => 'get', - 'POST' => 'post', - 'PUT' => 'put', - 'DELETE' => 'delete', + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', ]; // 子域名 @@ -68,6 +62,8 @@ class Route private static $domainRule; // 当前域名 private static $domain; + // 当前路由执行过程中的参数 + private static $option = []; /** * 注册变量规则 @@ -126,7 +122,7 @@ class Route * 设置路由绑定 * @access public * @param mixed $bind 绑定信息 - * @param string $type 绑定类型 默认为module 支持 namespace class + * @param string $type 绑定类型 默认为module 支持 namespace class controller * @return mixed */ public static function bind($bind, $type = 'module') @@ -148,8 +144,9 @@ class Route } elseif ('' === $name) { return self::$rules['name']; } elseif (!is_null($value)) { - self::$rules['name'][$name][] = $value; + self::$rules['name'][strtolower($name)][] = $value; } else { + $name = strtolower($name); return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; } } @@ -198,7 +195,7 @@ class Route unset($rule['__rest__']); } - self::registerRules($rule, strtoupper($type)); + self::registerRules($rule, strtolower($type)); } // 批量注册路由 @@ -241,7 +238,7 @@ class Route $pattern = array_merge(self::getGroup('pattern'), $pattern); } - $type = strtoupper($type); + $type = strtolower($type); if (strpos($type, '|')) { $option['method'] = $type; @@ -292,20 +289,23 @@ class Route } elseif ('$' == substr($rule, -1, 1)) { // 是否完整匹配 $option['complete_match'] = true; - $rule = substr($rule, 0, -1); } } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { // 是否完整匹配 $option['complete_match'] = true; - $rule = substr($rule, 0, -1); } - if ('/' != $rule) { + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { $rule = trim($rule, '/'); } $vars = self::parseVar($rule); if (isset($name)) { - self::name($name, [$rule, $vars, self::$domain]); + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + self::name($name, [$key, $vars, self::$domain]); } if ($group) { if ('*' != $type) { @@ -327,7 +327,7 @@ class Route } if ('*' == $type) { // 注册路由快捷方式 - foreach (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as $method) { + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { if (self::$domain) { self::$rules['domain'][self::$domain][$method][$rule] = true; } else { @@ -338,6 +338,27 @@ class Route } } + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected static function setOption($options = []) + { + self::$option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public static function getOption() + { + return self::$option; + } + /** * 获取当前的分组信息 * @access public @@ -422,15 +443,16 @@ class Route $options['complete_match'] = true; $key = substr($key, 0, -1); } + $key = trim($key, '/'); $vars = self::parseVar($key); $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; // 设置路由标识 - self::name($route, [$key, $vars, self::$domain]); + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain]); } self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; } - foreach (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as $method) { + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { if (!isset(self::$rules[$method][$name])) { self::$rules[$method][$name] = true; } elseif (is_array(self::$rules[$method][$name])) { @@ -575,7 +597,8 @@ class Route } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); } - $item = ltrim($rule . $val[1], '/'); + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); } } @@ -624,23 +647,23 @@ class Route public static function setMethodPrefix($method, $prefix = '') { if (is_array($method)) { - self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method, CASE_UPPER)); + self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); } else { - self::$methodPrefix[strtoupper($method)] = $prefix; + self::$methodPrefix[strtolower($method)] = $prefix; } } /** * rest方法定义和修改 * @access public - * @param string $name 方法名称 - * @param array $resourece 资源 + * @param string $name 方法名称 + * @param array|bool $resource 资源 * @return void */ public static function rest($name, $resource = []) { if (is_array($name)) { - self::$rest = array_merge(self::$rest, $name); + self::$rest = $resource ? $name : array_merge(self::$rest, $name); } else { self::$rest[$name] = $resource; } @@ -681,7 +704,7 @@ class Route if (is_array($rules)) { self::$rules = $rules; } elseif ($rules) { - return true === $rules ? self::$rules : self::$rules[$rules]; + return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; } else { $rules = self::$rules; unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); @@ -697,7 +720,7 @@ class Route * @param string $method 请求类型 * @return void */ - public static function checkDomain($request, &$currentRules, $method = 'GET') + public static function checkDomain($request, &$currentRules, $method = 'get') { // 域名规则 $rules = self::$rules['domain']; @@ -742,6 +765,10 @@ class Route } } if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } if (isset($item['[bind]'])) { // 解析子域名部署规则 list($rule, $option, $pattern) = $item['[bind]']; @@ -798,18 +825,16 @@ class Route public static function check($request, $url, $depr = '/', $checkDomain = false) { // 分隔符替换 确保路由定义使用统一的分隔符 - if ('/' != $depr) { - $url = str_replace($depr, '/', $url); - } + $url = str_replace($depr, '|', $url); - if (strpos($url, '/') && isset(self::$rules['alias'][strstr($url, '/', true)])) { + if (strpos($url, '|') && isset(self::$rules['alias'][strstr($url, '|', true)])) { // 检测路由别名 $result = self::checkRouteAlias($request, $url, $depr); if (false !== $result) { return $result; } } - $method = $request->method(); + $method = strtolower($request->method()); // 获取当前请求类型的路由规则 $rules = self::$rules[$method]; // 检测域名部署 @@ -821,17 +846,19 @@ class Route if (false !== $return) { return $return; } - if ('/' != $url) { - $url = rtrim($url, '/'); + if ('|' != $url) { + $url = rtrim($url, '|'); } - if (isset($rules[$url])) { + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { // 静态路由规则检测 - $rule = $rules[$url]; + $rule = $rules[$item]; if (true === $rule) { - $rule = self::getRouteExpress($url); + $rule = self::getRouteExpress($item); } - if (!empty($rule['route']) && self::checkOption($rule['option'], $url, $request)) { - return self::parseRule($url, $rule['route'], $url, $rule['option']); + if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { + self::setOption($rule['option']); + return self::parseRule($item, $rule['route'], $url, $rule['option']); } } @@ -874,7 +901,7 @@ class Route $pattern = $item['pattern']; // 检查参数有效性 - if (!self::checkOption($option, $url, $request)) { + if (!self::checkOption($option, $request)) { continue; } @@ -891,10 +918,10 @@ class Route } else { $str = $key; } - if (is_string($str) && $str && 0 !== strpos($url, $str)) { + if (is_string($str) && $str && 0 !== strpos(str_replace('|', '/', $url), $str)) { continue; } - + self::setOption($option); $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); if (false !== $result) { return $result; @@ -909,10 +936,12 @@ class Route if ($group) { $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); } + + self::setOption($option); if (isset($options['bind_model']) && isset($option['bind_model'])) { $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); } - $result = self::checkRule($rule, $route, $url, $pattern, $option); + $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); if (false !== $result) { return $result; } @@ -938,27 +967,40 @@ class Route */ private static function checkRouteAlias($request, $url, $depr) { - $array = explode('/', $url, 2); - $item = self::$rules['alias'][$array[0]]; + $array = explode('|', $url); + $alias = array_shift($array); + $item = self::$rules['alias'][$alias]; if (is_array($item)) { list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } } else { $rule = $item; } + $bind = implode('|', $array); // 参数有效性检查 - if (isset($option) && !self::checkOption($option, $url, $request)) { + if (isset($option) && !self::checkOption($option, $request)) { // 路由不匹配 return false; } elseif (0 === strpos($rule, '\\')) { // 路由到类 - return self::bindToClass($array[1], substr($rule, 1), $depr); - } elseif (0 === strpos($url, '@')) { + return self::bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { // 路由到控制器类 - return self::bindToController($array[1], substr($rule, 1), $depr); + return self::bindToController($bind, substr($rule, 1), $depr); } else { // 路由到模块/控制器 - return self::bindToModule($array[1], $rule, $depr); + return self::bindToModule($bind, $rule, $depr); } } @@ -982,6 +1024,9 @@ class Route case 'class': // 绑定到类 return self::bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return self::bindToController($url, $bind, $depr); case 'namespace': // 绑定到命名空间 return self::bindToNamespace($url, $bind, $depr); @@ -1000,7 +1045,8 @@ class Route */ public static function bindToClass($url, $class, $depr = '/') { - $array = explode($depr, $url, 2); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); @@ -1018,13 +1064,14 @@ class Route */ public static function bindToNamespace($url, $namespace, $depr = '/') { - $array = explode($depr, $url, 3); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); if (!empty($array[2])) { self::parseUrlParams($array[2]); } - return ['type' => 'method', 'method' => [$namespace . '\\' . $class, $method]]; + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method]]; } /** @@ -1037,7 +1084,8 @@ class Route */ public static function bindToController($url, $controller, $depr = '/') { - $array = explode($depr, $url, 2); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); @@ -1049,13 +1097,14 @@ class Route * 绑定到模块/控制器 * @access public * @param string $url URL地址 - * @param string $class 控制器类名(带命名空间) + * @param string $controller 控制器类名(带命名空间) * @param string $depr URL分隔符 * @return array */ public static function bindToModule($url, $controller, $depr = '/') { - $array = explode($depr, $url, 2); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); @@ -1067,19 +1116,22 @@ class Route * 路由参数有效性检查 * @access private * @param array $option 路由参数 - * @param string $url URL地址 * @param Request $request Request对象 * @return bool */ - private static function checkOption($option, $url, $request) + private static function checkOption($option, $request) { - // 请求类型检测 - if ((isset($option['method']) && false === stripos($option['method'], $request->method())) - || (isset($option['ext']) && false === stripos($option['ext'], $request->ext())) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos($option['deny_ext'], $request->ext())) + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', $request->ext() ? '|' . $request->ext() . '|' : '')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', $request->ext() ? '|' . $request->ext() . '|' : '')) || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 - || (!empty($option['https']) && !$request->isSsl()) // https检测 - || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'], '', $url)) // 行为检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 ) { return false; @@ -1095,24 +1147,28 @@ class Route * @param string $url URL地址 * @param array $pattern 变量规则 * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) * @return array|false */ - private static function checkRule($rule, $route, $url, $pattern, $option) + private static function checkRule($rule, $route, $url, $pattern, $option, $depr) { // 检查完整规则定义 - if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', $url)) { + if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { return false; } - // 检测是否设置了参数分隔符 - if ($depr = Config::get('url_params_depr')) { - $url = str_replace($depr, '/', $url); - $rule = str_replace($depr, '/', $rule); + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); } - $len1 = substr_count($url, '/'); + $len1 = substr_count($url, '|'); $len2 = substr_count($rule, '/'); // 多余参数是否合并 $merge = !empty($option['merge_extra_vars']) ? true : false; + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } if ($len1 >= $len2 || strpos($rule, '[')) { if (!empty($option['complete_match'])) { @@ -1140,12 +1196,14 @@ class Route */ public static function parseUrl($url, $depr = '/', $autoSearch = false) { + if (isset(self::$bind['module'])) { + $bind = str_replace('/', $depr, self::$bind['module']); // 如果有模块/控制器绑定 - $url = self::$bind['module'] . '/' . ltrim($url, '/'); + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); } - - list($path, $var) = self::parseUrlPath($url, $depr); + $url = str_replace($depr, '|', $url); + list($path, $var) = self::parseUrlPath($url); $route = [null, null, null]; if (isset($path)) { // 解析模块 @@ -1155,15 +1213,24 @@ class Route $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; $item = []; + $find = false; foreach ($path as $val) { - $item[] = array_shift($path); - if (is_file($dir . DS . $val . $suffix . EXT)) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; break; } else { - $dir .= DS . $val; + $dir .= DS . Loader::parseName($val); } } - $controller = implode('.', $item); + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } } else { // 解析控制器 $controller = !empty($path) ? array_shift($path) : null; @@ -1171,11 +1238,18 @@ class Route // 解析操作 $action = !empty($path) ? array_shift($path) : null; // 解析额外参数 - self::parseUrlParams(empty($path) ? '' : implode('/', $path)); + self::parseUrlParams(empty($path) ? '' : implode('|', $path)); // 封装路由 $route = [$module, $controller, $action]; - if (isset(self::$rules['name'][implode($depr, $route)])) { - throw new HttpException(404, 'invalid request:' . $url); + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); } } return ['type' => 'module', 'module' => $route]; @@ -1185,15 +1259,12 @@ class Route * 解析URL的pathinfo参数和变量 * @access private * @param string $url URL地址 - * @param string $depr URL分隔符 * @return array */ - private static function parseUrlPath($url, $depr = '/') + private static function parseUrlPath($url) { // 分隔符替换 确保路由定义使用统一的分隔符 - if ('/' != $depr) { - $url = str_replace($depr, '/', $url); - } + $url = str_replace('|', '/', $url); $url = trim($url, '/'); $var = []; if (false !== strpos($url, '?')) { @@ -1219,13 +1290,12 @@ class Route * @param string $url URL地址 * @param string $rule 路由规则 * @param array $pattern 变量规则 - * @param bool $merge 合并额外变量 * @return array|false */ - private static function match($url, $rule, $pattern, $merge) + private static function match($url, $rule, $pattern) { $m2 = explode('/', $rule); - $m1 = $merge ? explode('/', $url, count($m2)) : explode('/', $url); + $m1 = explode('|', $url); $var = []; foreach ($m2 as $key => $val) { @@ -1269,9 +1339,16 @@ class Route if (!$optional && !isset($m1[$key])) { return false; } - if (isset($m1[$key]) && isset($pattern[$name]) && !preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { + if (isset($m1[$key]) && isset($pattern[$name])) { // 检查变量规则 - return false; + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } } $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { @@ -1290,17 +1367,16 @@ class Route * @param string $pathinfo URL地址 * @param array $option 路由参数 * @param array $matches 匹配的变量 - * @param bool $merge 合并额外变量 * @return array */ - private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $merge = false) + private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = []) { $request = Request::instance(); // 解析路由规则 if ($rule) { $rule = explode('/', $rule); // 获取URL地址中的参数 - $paths = $merge ? explode('/', $pathinfo, count($rule)) : explode('/', $pathinfo); + $paths = explode('|', $pathinfo); foreach ($rule as $item) { $fun = ''; if (0 === strpos($item, '[:')) { @@ -1315,7 +1391,7 @@ class Route } } } else { - $paths = explode('/', $pathinfo); + $paths = explode('|', $pathinfo); } // 获取路由地址规则 @@ -1328,7 +1404,6 @@ class Route foreach ($matches as $key => $val) { if (false !== strpos($route, ':' . $key)) { $route = str_replace(':' . $key, $val, $route); - unset($matches[$key]); } } } @@ -1372,7 +1447,7 @@ class Route } // 解析额外参数 - self::parseUrlParams(empty($paths) ? '' : implode('/', $paths), $matches); + self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); // 记录匹配的路由信息 $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); @@ -1399,28 +1474,35 @@ class Route if ($route instanceof \Closure) { // 执行闭包 $result = ['type' => 'function', 'function' => $route]; - } elseif (0 === strpos($route, '/') || 0 === strpos($route, 'http')) { + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; } elseif (false !== strpos($route, '\\')) { // 路由到方法 - $route = str_replace('/', '@', $route); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = ['type' => 'method', 'method' => $method]; + list($path, $var) = self::parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; } elseif (0 === strpos($route, '@')) { // 路由到控制器 - $result = ['type' => 'controller', 'controller' => substr($route, 1)]; + $route = substr($route, 1); + list($route, $var) = self::parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); } else { // 路由到模块/控制器/操作 $result = self::parseModule($route); } // 开启请求缓存 - if ($request->isGet() && !empty($option['cache'])) { + if ($request->isGet() && isset($option['cache'])) { $cache = $option['cache']; if (is_array($cache)) { list($key, $expire) = $cache; } else { - $key = $pathinfo; + $key = str_replace('|', '/', $pathinfo); $expire = $cache; } $request->cache($key, $expire); @@ -1432,12 +1514,11 @@ class Route * 解析URL地址为 模块/控制器/操作 * @access private * @param string $url URL地址 - * @param string $depr URL分隔符 * @return array */ - private static function parseModule($url, $depr = '/') + private static function parseModule($url) { - list($path, $var) = self::parseUrlPath($url, $depr); + list($path, $var) = self::parseUrlPath($url); $action = array_pop($path); $controller = !empty($path) ? array_pop($path) : null; $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; @@ -1463,9 +1544,9 @@ class Route { if ($url) { if (Config::get('url_param_type')) { - $var += explode('/', $url); + $var += explode('|', $url); } else { - preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$var) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { $var[$match[1]] = strip_tags($match[2]); }, $url); } diff --git a/library/think/Session.php b/library/think/Session.php index e8f56fd9..4b66284c 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,6 @@ namespace think; -use think\App; use think\exception\ClassNotFoundException; class Session @@ -78,7 +77,12 @@ class Session 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); } @@ -114,7 +118,9 @@ class Session if (is_null(self::$init)) { self::init(); } elseif (false === self::$init) { - session_start(); + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } self::$init = true; } } @@ -191,13 +197,49 @@ class Session self::delete($name, $prefix); return $result; } else { - return null; + return; } } /** - * 删除session数据 + * session设置 下一次请求有效 * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function flash($name, $value) + { + self::set($name, $value); + if (!self::has('__flash__.__time__')) { + self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + self::push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public static function flush() + { + if (self::$init) { + $item = self::get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + self::delete($item); + self::set('__flash__', []); + } + } + } + } + + /** + * 删除session数据 + * @param string|array $name session名称 * @param string|null $prefix 作用域(前缀) * @return void */ @@ -205,7 +247,11 @@ class Session { empty(self::$init) && self::boot(); $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (strpos($name, '.')) { + if (is_array($name)) { + foreach ($name as $key) { + self::delete($key, $prefix); + } + } elseif (strpos($name, '.')) { list($name1, $name2) = explode('.', $name); if ($prefix) { unset($_SESSION[$prefix][$name1][$name2]); @@ -256,6 +302,22 @@ class Session } } + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public static function push($key, $value) + { + $array = self::get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + self::set($key, $array); + } + /** * 启动session * @return void diff --git a/library/think/Template.php b/library/think/Template.php index 036b367e..66ba9e18 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -25,6 +25,7 @@ class Template // 引擎配置 protected $config = [ 'view_path' => '', // 模板路径 + 'view_base' => '', 'view_suffix' => 'html', // 默认模板文件后缀 'view_depr' => DS, 'cache_suffix' => 'php', // 默认模板缓存后缀 @@ -128,7 +129,7 @@ class Template } elseif (isset($this->config[$config])) { return $this->config[$config]; } else { - return null; + return; } } @@ -667,7 +668,7 @@ class Template $content = str_replace($matches[0], '', $content); return explode(',', $matches['name']); } - return null; + return; } /** @@ -959,78 +960,66 @@ class Template * @param array $vars 变量数组 * @return string */ - public function parseThinkVar(&$vars) + public function parseThinkVar($vars) { - $vars[0] = strtoupper(trim($vars[0])); - $parseStr = ''; - if (count($vars) >= 2) { - $vars[1] = trim($vars[1]); - switch ($vars[0]) { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + if ($vars) { + switch ($type) { case 'SERVER': - $parseStr = '$_SERVER[\'' . strtoupper($vars[1]) . '\']'; + $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; break; case 'GET': - $parseStr = '$_GET[\'' . $vars[1] . '\']'; + $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; break; case 'POST': - $parseStr = '$_POST[\'' . $vars[1] . '\']'; + $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; break; case 'COOKIE': - if (isset($vars[2])) { - $parseStr = '\\think\\Cookie::get(\'' . $vars[1] . '.' . $vars[2] . '\')'; - } else { - $parseStr = '\\think\\Cookie::get(\'' . $vars[1] . '\')'; - } + $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; break; case 'SESSION': - if (isset($vars[2])) { - $parseStr = '\\think\\Session::get(\'' . $vars[1] . '.' . $vars[2] . '\')'; - } else { - $parseStr = '\\think\\Session::get(\'' . $vars[1] . '\')'; - } + $parseStr = '\\think\\Session::get(\'' . $param . '\')'; break; case 'ENV': - $parseStr = '$_ENV[\'' . strtoupper($vars[1]) . '\']'; + $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; break; case 'REQUEST': - $parseStr = '$_REQUEST[\'' . $vars[1] . '\']'; + $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; break; case 'CONST': - $parseStr = strtoupper($vars[1]); + $parseStr = strtoupper($param); break; case 'LANG': - $parseStr = '\\think\\Lang::get(\'' . $vars[1] . '\')'; + $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; break; case 'CONFIG': - if (isset($vars[2])) { - $vars[1] .= '.' . $vars[2]; - } - $parseStr = '\\think\\Config::get(\'' . $vars[1] . '\')'; + $parseStr = '\\think\\Config::get(\'' . $param . '\')'; break; default: $parseStr = '\'\''; break; } } else { - if (count($vars) == 1) { - switch ($vars[0]) { - case 'NOW': - $parseStr = "date('Y-m-d g:i a',time())"; - break; - case 'VERSION': - $parseStr = 'THINK_VERSION'; - break; - case 'LDELIM': - $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; - break; - case 'RDELIM': - $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; - break; - default: - if (defined($vars[0])) { - $parseStr = $vars[0]; - } - } + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } } } return $parseStr; @@ -1073,14 +1062,20 @@ class Template { if ('' == pathinfo($template, PATHINFO_EXTENSION)) { if (strpos($template, '@')) { - // 跨模块调用模板 + list($module, $template) = explode('@', $template); + } + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $this->config['view_depr'], $template); - $template = APP_PATH . str_replace('@', '/' . basename($this->config['view_path']) . '/', $template); } else { - $template = str_replace(['/', ':'], $this->config['view_depr'], $template); - $template = $this->config['view_path'] . $template; + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + if ($this->config['view_base']) { + $module = isset($module) ? $module : Request::instance()->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; } - $template .= '.' . ltrim($this->config['view_suffix'], '.'); + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); } if (is_file($template)) { diff --git a/library/think/Url.php b/library/think/Url.php index 4e145702..51c846d7 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,15 +11,11 @@ namespace think; -use think\Config; -use think\Loader; -use think\Request; -use think\Route; - class Url { // 生成URL地址的root protected static $root; + protected static $bindCheck; /** * URL生成 支持路由反射 @@ -31,7 +27,7 @@ class Url */ public static function build($url = '', $vars = '', $suffix = true, $domain = false) { - if (false === $domain && Config::get('url_domain_deploy')) { + if (false === $domain && Route::rules('domain')) { $domain = true; } // 解析URL @@ -40,22 +36,24 @@ class Url $name = substr($url, 1, $pos - 1); $url = 'name' . substr($url, $pos + 1); } - $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, '@')) { + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { // 解析域名 - list($anchor, $domain) = explode('@', $anchor, 2); + list($url, $domain) = explode('@', $url, 2); } - } elseif (strpos($url, '@')) { - // 解析域名 - list($url, $domain) = explode('@', $url, 2); } // 解析参数 @@ -77,27 +75,49 @@ class Url if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { // 匹配路由命名标识 $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); if (!empty($match[1])) { $domain = $match[1]; } } elseif (!empty($rule) && isset($name)) { throw new \InvalidArgumentException('route name not exists:' . $name); } else { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = self::parseUrl($url, $domain); + } if (isset($info['query'])) { // 解析地址里面参数 合并到vars parse_str($info['query'], $params); $vars = array_merge($params, $vars); } - // 路由标识不存在 直接解析 - $url = self::parseUrl($url, $domain); } // 检测URL绑定 - $type = Route::getBind('type'); - if ($type) { - $bind = Route::getBind($type); - if (0 === strpos($url, $bind)) { - $url = substr($url, strlen($bind) + 1); + if (!self::$bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if (0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } } } // 还原URL分隔符 @@ -115,9 +135,14 @@ class Url $vars = urldecode(http_build_query($vars)); $url .= $suffix . '?' . $vars . $anchor; } else { + $paramType = Config::get('url_param_type'); foreach ($vars as $var => $val) { if ('' !== trim($val)) { - $url .= $depr . $var . $depr . urlencode($val); + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } } } $url .= $suffix . $anchor; @@ -128,12 +153,13 @@ class Url // 检测域名 $domain = self::parseDomain($url, $domain); // URL组装 - $url = $domain . (self::$root ?: Request::instance()->root()) . '/' . ltrim($url, '/'); + $url = $domain . (self::$root ?: Request::instance()->root()) . '/' . ltrim($url, '/'); + self::$bindCheck = false; return $url; } // 直接解析URL地址 - protected static function parseUrl($url, $domain) + protected static function parseUrl($url, &$domain) { $request = Request::instance(); if (0 === strpos($url, '/')) { @@ -149,14 +175,36 @@ class Url // 解析到 模块/控制器/操作 $module = $request->module(); $domains = Route::rules('domain'); - if (isset($domains[$domain]['[bind]'][0])) { - $bindModule = $domains[$domain]['[bind]'][0]; - if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { - $module = ''; + 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 = ''; + } } - } else { - $module = $module ? $module . '/' : ''; } + $module = $module ? $module . '/' : ''; $controller = Loader::parseName($request->controller()); if ('' == $url) { @@ -179,15 +227,15 @@ class Url if (!$domain) { return ''; } - $request = Request::instance(); + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); if (true === $domain) { // 自动判断域名 $domain = $request->host(); - if (Config::get('url_domain_deploy')) { - // 根域名 - $urlDomainRoot = Config::get('url_domain_root'); - $domains = Route::rules('domain'); - $route_domain = array_keys($domains); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); foreach ($route_domain as $domain_prefix) { if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { foreach ($domains as $key => $rule) { @@ -196,13 +244,13 @@ class Url $url = ltrim($url, $rule); $domain = $key; // 生成对应子域名 - if (!empty($urlDomainRoot)) { - $domain .= $urlDomainRoot; + if (!empty($rootDomain)) { + $domain .= $rootDomain; } break; - } else if (false !== strpos($key, '*')) { - if (!empty($urlDomainRoot)) { - $domain .= $urlDomainRoot; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; } break; } @@ -210,13 +258,15 @@ class Url } } } - } elseif (!strpos($domain, '.')) { - $rootDomain = Config::get('url_domain_root'); + + } else { if (empty($rootDomain)) { $host = $request->host(); $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; } - $domain .= '.' . $rootDomain; + if (!strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } } return ($request->isSsl() ? 'https://' : 'http://') . $domain; } diff --git a/library/think/Validate.php b/library/think/Validate.php index 5fa15424..9a6f58ab 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,9 +11,7 @@ namespace think; -use think\File; -use think\Request; -use think\Session; +use think\exception\ClassNotFoundException; class Validate { @@ -33,6 +31,8 @@ class Validate // 验证提示信息 protected $message = []; + // 验证字段描述 + protected $field = []; // 验证规则默认提示信息 protected static $typeMsg = [ @@ -69,8 +69,8 @@ class Validate 'expire' => '不在有效期内 :rule', 'allowIp' => '不允许的IP访问', 'denyIp' => '禁止的IP访问', - 'confirm' => ':attribute和字段 :rule 不一致', - 'different' => ':attribute和字段 :rule 不能相同', + 'confirm' => ':attribute和确认字段:2不一致', + 'different' => ':attribute和比较字段:2不能相同', 'egt' => ':attribute必须大于等于 :rule', 'gt' => ':attribute必须大于 :rule', 'elt' => ':attribute必须小于等于 :rule', @@ -106,11 +106,13 @@ class Validate * @access public * @param array $rules 验证规则 * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 */ - public function __construct(array $rules = [], $message = []) + public function __construct(array $rules = [], $message = [], $field = []) { $this->rule = array_merge($this->rule, $rules); $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); } /** @@ -118,12 +120,13 @@ class Validate * @access public * @param array $rules 验证规则 * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 * @return Validate */ - public static function make($rules = [], $message = []) + public static function make($rules = [], $message = [], $field = []) { if (is_null(self::$instance)) { - self::$instance = new self($rules, $message); + self::$instance = new self($rules, $message, $field); } return self::$instance; } @@ -215,6 +218,17 @@ class Validate return $this; } + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]); + } + /** * 设置批量验证 * @access public @@ -279,7 +293,7 @@ class Validate // 字段|描述 用于指定属性名称 list($key, $title) = explode('|', $key); } else { - $title = $key; + $title = isset($this->field[$key]) ? $this->field[$key] : $key; } // 场景检测 @@ -300,7 +314,12 @@ class Validate $value = $this->getDataValue($data, $key); // 字段验证 - $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + if ($rule instanceof \Closure) { + // 匿名函数验证 支持传入当前字段和所有字段两个数据 + $result = call_user_func_array($rule, [$value, $data]); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + } if (true !== $result) { // 没有返回true 则表示验证失败 @@ -333,74 +352,90 @@ class Validate */ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) { - if ($rules instanceof \Closure) { - // 匿名函数验证 支持传入当前字段和所有字段两个数据 - $result = call_user_func_array($rules, [$value, $data]); - } else { - // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] - if (is_string($rules)) { - $rules = explode('|', $rules); - } - $i = 0; - foreach ($rules as $key => $rule) { - if ($rule instanceof \Closure) { - $result = call_user_func_array($rule, [$value, $data]); - } else { - // 判断验证类型 - if (is_numeric($key) && strpos($rule, ':')) { + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + $i = 0; + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + if (is_numeric($key)) { + if (strpos($rule, ':')) { list($type, $rule) = explode(':', $rule, 2); if (isset($this->alias[$type])) { // 判断别名 $type = $this->alias[$type]; } $info = $type; - } elseif (is_numeric($key)) { - $type = 'is'; + } elseif (method_exists($this, $rule)) { + $type = $rule; $info = $rule; + $rule = ''; } else { - $info = $type = $key; + $type = 'is'; + $info = $rule; } + } else { + $info = $type = $key; + } - // 如果不是require 有数据才会行验证 - if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { - // 验证类型 - $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; - // 验证数据 - $result = call_user_func_array($callback, [$value, $rule, $data, $field]); - } else { - $result = true; - } + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证类型 + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + } else { + $result = true; } + } - if (false === $result) { - // 验证失败 返回错误信息 - if (isset($msg[$i])) { - $message = $msg[$i]; - } else { - $message = $this->getRuleMsg($field, $title, $info, $rule); + if (false === $result) { + // 验证失败 返回错误信息 + if (isset($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = Lang::get(substr($message, 2, -1)); } - return $message; - } elseif (true !== $result) { - // 返回自定义错误信息 - return $result; + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); } - $i++; + return $result; } + $i++; } - return true !== $result ? $result : true; + return $result; } /** * 验证是否和某个字段的值一致 * @access protected - * @param mixed $value 字段值 + * @param mixed $value 字段值 * @param mixed $rule 验证规则 * @param array $data 数据 + * @param string $field 字段名 * @return bool */ - protected function confirm($value, $rule, $data) + protected function confirm($value, $rule, $data, $field = '') { - return $this->getDataValue($data, $rule) == $value; + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + return $this->getDataValue($data, $rule) === $value; } /** @@ -533,7 +568,7 @@ class Validate break; case 'ip': // 是否为IP地址 - $result = $this->filter($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6); + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); break; case 'url': // 是否为一个URL地址 @@ -556,7 +591,7 @@ class Validate break; case 'boolean': // 是否为布尔值 - $result = in_array($value, [0, 1, true, false]); + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; case 'array': // 是否为数组 @@ -603,6 +638,9 @@ class Validate */ protected function activeUrl($value, $rule) { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } return checkdnsrr($value, $rule); } @@ -711,19 +749,24 @@ class Validate if (!($file instanceof File)) { return false; } - $rule = explode(',', $rule); - list($width, $height, $type) = getimagesize($file->getRealPath()); - if (isset($rule[2])) { - $imageType = strtolower($rule[2]); - if ('jpeg' == $imageType) { - $imageType = 'jpg'; - } - if (image_type_to_extension($type, false) != $imageType) { - return false; + if ($rule) { + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + if (image_type_to_extension($type, false) != $imageType) { + return false; + } } + + list($w, $h) = $rule; + return $w == $width && $h == $height; + } else { + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); } - list($w, $h) = $rule; - return $w == $width && $h == $height; } /** @@ -766,7 +809,16 @@ class Validate if (is_string($rule)) { $rule = explode(',', $rule); } - $db = Db::name($rule[0]); + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Loader::model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } $key = isset($rule[1]) ? $rule[1] : $field; if (strpos($key, '^')) { @@ -1156,6 +1208,8 @@ class Validate { if (isset($this->message[$attribute . '.' . $type])) { $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; } elseif (isset($this->message[$attribute])) { $msg = $this->message[$attribute]; } elseif (isset(self::$typeMsg[$type])) { @@ -1163,10 +1217,14 @@ class Validate } else { $msg = $title . '规则错误'; } - // TODO 多语言支持 - if (is_string($msg) && false !== strpos($msg, ':')) { + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = Lang::get(substr($msg, 2, -1)); + } + + if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { // 变量替换 - if (strpos($rule, ',')) { + if (is_string($rule) && strpos($rule, ',')) { $array = array_pad(explode(',', $rule), 3, ''); } else { $array = array_pad([], 3, ''); @@ -1206,7 +1264,7 @@ class Validate public static function __callStatic($method, $params) { - $class = new static; + $class = self::make(); if (method_exists($class, $method)) { return call_user_func_array([$class, $method], $params); } else { diff --git a/library/think/View.php b/library/think/View.php index da087036..020e5789 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -19,6 +19,8 @@ class View public $engine; // 模板变量 protected $data = []; + // 用于静态赋值的模板变量 + protected static $var = []; // 视图输出替换 protected $replace = []; @@ -32,7 +34,21 @@ class View { // 初始化模板引擎 $this->engine((array) $engine); - $this->replace = $replace; + // 基础替换字符串 + $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); } /** @@ -50,6 +66,22 @@ class View return self::$instance; } + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return void + */ + public static function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + } + /** * 模板变量赋值 * @access public @@ -116,7 +148,7 @@ class View public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) { // 模板变量 - $vars = array_merge($this->data, $vars); + $vars = array_merge(self::$var, $this->data, $vars); // 页面缓存 ob_start(); diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index e0aeb7bc..688507a8 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -105,10 +105,31 @@ abstract class Driver $this->rm($name); return $result; } else { - return null; + return; } } + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + if ($value instanceof \Closure) { + $value = call_user_func($value); + } + $this->set($name, $value, $expire); + } else { + $value = $this->get($name); + } + return $value; + } + /** * 缓存标签 * @access public diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 5c98d799..dba02c3e 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -21,7 +21,7 @@ class File extends Driver { protected $options = [ 'expire' => 0, - 'cache_subdir' => false, + 'cache_subdir' => true, 'prefix' => '', 'path' => CACHE_PATH, 'data_compress' => false, diff --git a/library/think/cache/driver/Lite.php b/library/think/cache/driver/Lite.php index b9d10097..57727651 100644 --- a/library/think/cache/driver/Lite.php +++ b/library/think/cache/driver/Lite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/cache/driver/Memcache.php b/library/think/cache/driver/Memcache.php index bddf7157..d41939d8 100644 --- a/library/think/cache/driver/Memcache.php +++ b/library/think/cache/driver/Memcache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; class Memcache extends Driver { diff --git a/library/think/cache/driver/Memcached.php b/library/think/cache/driver/Memcached.php index 4c4505b0..35fafd07 100644 --- a/library/think/cache/driver/Memcached.php +++ b/library/think/cache/driver/Memcached.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index e7f78762..360f515a 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/cache/driver/Sqlite.php b/library/think/cache/driver/Sqlite.php index 6dbd41fe..76c592d8 100644 --- a/library/think/cache/driver/Sqlite.php +++ b/library/think/cache/driver/Sqlite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; /** * Sqlite缓存驱动 diff --git a/library/think/cache/driver/Wincache.php b/library/think/cache/driver/Wincache.php index a9cc7d22..5be8d0df 100644 --- a/library/think/cache/driver/Wincache.php +++ b/library/think/cache/driver/Wincache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; /** * Wincache缓存驱动 @@ -28,7 +27,7 @@ class Wincache extends Driver /** * 架构函数 * @param array $options 缓存参数 - * @throws Exception + * @throws \BadFunctionCallException * @access public */ public function __construct($options = []) diff --git a/library/think/cache/driver/Xcache.php b/library/think/cache/driver/Xcache.php index 6b18b3fc..317a4ee3 100644 --- a/library/think/cache/driver/Xcache.php +++ b/library/think/cache/driver/Xcache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; /** * Xcache缓存驱动 diff --git a/library/think/config/driver/Ini.php b/library/think/config/driver/Ini.php index d8dc558d..a223a578 100644 --- a/library/think/config/driver/Ini.php +++ b/library/think/config/driver/Ini.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/config/driver/Json.php b/library/think/config/driver/Json.php index ec2419f9..557f75fe 100644 --- a/library/think/config/driver/Json.php +++ b/library/think/config/driver/Json.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/config/driver/Xml.php b/library/think/config/driver/Xml.php index 5bc93015..b573a562 100644 --- a/library/think/config/driver/Xml.php +++ b/library/think/config/driver/Xml.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/console/Input.php b/library/think/console/Input.php index 269196eb..2482dfdc 100644 --- a/library/think/console/Input.php +++ b/library/think/console/Input.php @@ -252,7 +252,7 @@ class Input return $token; } - return null; + return; } /** diff --git a/library/think/console/command/Help.php b/library/think/console/command/Help.php index eb0858a3..bae2c653 100644 --- a/library/think/console/command/Help.php +++ b/library/think/console/command/Help.php @@ -16,7 +16,6 @@ use think\console\Input; use think\console\input\Argument as InputArgument; use think\console\input\Option as InputOption; use think\console\Output; -use think\console\helper\Descriptor as DescriptorHelper; class Help extends Command { @@ -67,4 +66,4 @@ EOF $this->command = null; } -} \ No newline at end of file +} diff --git a/library/think/console/command/Lists.php b/library/think/console/command/Lists.php index ffbee07c..084ddaa2 100644 --- a/library/think/console/command/Lists.php +++ b/library/think/console/command/Lists.php @@ -71,4 +71,4 @@ EOF new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list') ]); } -} \ No newline at end of file +} diff --git a/library/think/console/command/optimize/Autoload.php b/library/think/console/command/optimize/Autoload.php index 0481e179..7311aafb 100644 --- a/library/think/console/command/optimize/Autoload.php +++ b/library/think/console/command/optimize/Autoload.php @@ -278,4 +278,4 @@ EOF; return $classes; } -} \ No newline at end of file +} diff --git a/library/think/console/command/optimize/Schema.php b/library/think/console/command/optimize/Schema.php index a1f40cd9..6ac38a36 100644 --- a/library/think/console/command/optimize/Schema.php +++ b/library/think/console/command/optimize/Schema.php @@ -50,7 +50,7 @@ class Schema extends Command } $output->writeln('Succeed!'); return; - } else if ($input->hasOption('table')) { + } elseif ($input->hasOption('table')) { $table = $input->getOption('table'); if (!strpos($table, '.')) { $dbName = Db::getConfig('database'); diff --git a/library/think/console/output/Ask.php b/library/think/console/output/Ask.php index 4d360bce..3933eb29 100644 --- a/library/think/console/output/Ask.php +++ b/library/think/console/output/Ask.php @@ -337,4 +337,4 @@ class Ask return self::$stty = $exitcode === 0; } -} \ No newline at end of file +} diff --git a/library/think/console/output/driver/Console.php b/library/think/console/output/driver/Console.php index af4341b7..3fbe224f 100644 --- a/library/think/console/output/driver/Console.php +++ b/library/think/console/output/driver/Console.php @@ -196,7 +196,7 @@ class Console private function getSttyColumns() { if (!function_exists('proc_open')) { - return null; + return; } $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; @@ -209,7 +209,7 @@ class Console return $info; } - return null; + return; } /** @@ -219,7 +219,7 @@ class Console private function getMode() { if (!function_exists('proc_open')) { - return null; + return; } $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; @@ -234,7 +234,7 @@ class Console return $matches[2] . 'x' . $matches[1]; } } - return null; + return; } private function stringWidth($string) @@ -356,10 +356,10 @@ class Console { if (DIRECTORY_SEPARATOR === '\\') { return - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); } return function_exists('posix_isatty') && @posix_isatty($stream); diff --git a/library/think/controller/Rest.php b/library/think/controller/Rest.php index a75cfd37..c297f4eb 100644 --- a/library/think/controller/Rest.php +++ b/library/think/controller/Rest.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 15a240ad..380fe239 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,9 +12,6 @@ namespace think\db; use PDO; -use think\Db; -use think\db\Connection; -use think\db\Query; use think\Exception; abstract class Builder @@ -37,22 +34,33 @@ abstract class Builder /** * 架构函数 * @access public - * @param Connection $connection 数据库连接对象实例 + * @param Connection $connection 数据库连接对象实例 + * @param Query $query 数据库查询对象实例 */ - public function __construct(Connection $connection) + public function __construct(Connection $connection, Query $query) { $this->connection = $connection; + $this->query = $query; } /** - * 设置当前的Query对象实例 - * @access protected - * @param Query $query 当前查询对象实例 + * 获取当前的连接对象实例 + * @access public + * @return void + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 获取当前的Query对象实例 + * @access public * @return void */ - public function setQuery(Query $query) + public function getQuery() { - $this->query = $query; + return $this->query; } /** @@ -89,8 +97,8 @@ abstract class Builder $result = []; foreach ($data as $key => $val) { - $item = $this->parseKey($key); - if (!in_array($key, $fields, true)) { + $item = $this->parseKey($key, $options); + if (false === strpos($key, '.') && !in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } @@ -100,12 +108,16 @@ abstract class Builder $result[$item] = 'NULL'; } elseif (is_scalar($val)) { // 过滤非标量数据 - if ($this->query->isBind(substr($val, 1))) { + if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { $result[$item] = $val; } else { - $this->query->bind($key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':' . $key; + $key = str_replace('.', '_', $key); + $this->query->bind('__data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':__data__' . $key; } + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $result[$item] = $val->__toString(); } } return $result; @@ -115,9 +127,10 @@ abstract class Builder * 字段名分析 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { return $key; } @@ -146,10 +159,11 @@ abstract class Builder /** * field分析 * @access protected - * @param mixed $fields + * @param mixed $fields + * @param array $options * @return string */ - protected function parseField($fields) + protected function parseField($fields, $options = []) { if ('*' == $fields || empty($fields)) { $fieldsStr = '*'; @@ -158,9 +172,9 @@ abstract class Builder $array = []; foreach ($fields as $key => $field) { if (!is_numeric($key)) { - $array[] = $this->parseKey($key) . ' AS ' . $this->parseKey($field); + $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options); } else { - $array[] = $this->parseKey($field); + $array[] = $this->parseKey($field, $options); } } $fieldsStr = implode(',', $array); @@ -171,24 +185,30 @@ abstract class Builder /** * table分析 * @access protected - * @param mixed $table + * @param mixed $tables + * @param array $options * @return string */ - protected function parseTable($tables) + protected function parseTable($tables, $options = []) { - if (is_array($tables)) { - // 支持别名定义 - foreach ($tables as $table => $alias) { - $array[] = !is_numeric($table) ? - $this->parseKey($table) . ' ' . $this->parseKey($alias) : - $this->parseKey($alias); + $item = []; + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + if (strpos($key, '@think')) { + $key = strstr($key, '@think', true); + } + $key = $this->parseSqlTable($key); + $item[] = $this->parseKey($key) . ' ' . $this->parseKey($table); + } else { + $table = $this->parseSqlTable($table); + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + } else { + $item[] = $this->parseKey($table); + } } - $tables = $array; - } elseif (is_string($tables)) { - $tables = $this->parseSqlTable($tables); - $tables = array_map([$this, 'parseKey'], explode(',', $tables)); } - return implode(',', $tables); + return implode(',', $item); } /** @@ -263,7 +283,7 @@ abstract class Builder protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) { // 字段分析 - $key = $field ? $this->parseKey($field) : ''; + $key = $field ? $this->parseKey($field, $options) : ''; // 查询规则和条件 if (!is_array($val)) { @@ -296,12 +316,17 @@ abstract class Builder throw new Exception('where express error:' . $exp); } } - $bindName = $bindName ?: 'where_' . str_replace('.', '_', $field); + $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { + // 处理带非单词字符的字段名 + $bindName = md5($bindName); + } + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { if ($this->query->isBind($bindName)) { - $bindName .= '_' . uniqid(); + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); } $this->query->bind($bindName, $value, $bindType); $value = ':' . $bindName; @@ -309,9 +334,19 @@ abstract class Builder } $whereStr = ''; - if (in_array($exp, ['=', '<>', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE'])) { + if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { // 比较运算 及 模糊匹配 $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); + } + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } } elseif ('EXP' == $exp) { // 表达式查询 $whereStr .= '( ' . $key . ' ' . $value . ' )'; @@ -328,8 +363,13 @@ abstract class Builder $bind = []; $array = []; foreach ($value as $k => $v) { - $bind[$bindName . '_in_' . $k] = [$v, $bindType]; - $array[] = ':' . $bindName . '_in_' . $k; + if ($this->query->isBind($bindName . '_in_' . $k)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $k; + } else { + $bindKey = $bindName . '_in_' . $k; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; } $this->query->bind($bind); $zone = implode(',', $array); @@ -342,12 +382,19 @@ abstract class Builder // BETWEEN 查询 $data = is_array($value) ? $value : explode(',', $value); if (array_key_exists($field, $binds)) { + if ($this->query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } $bind = [ - $bindName . '_between_1' => [$data[0], $bindType], - $bindName . '_between_2' => [$data[1], $bindType], + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], ]; $this->query->bind($bind); - $between = ':' . $bindName . '_between_1' . ' AND :' . $bindName . '_between_2'; + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; } else { $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); } @@ -392,12 +439,23 @@ abstract class Builder protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) { // 获取时间字段类型 - $type = $this->query->getFieldsType($options); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + $type = $this->query->getTableInfo($table, 'type'); if (isset($type[$key])) { $info = $type[$key]; } if (isset($info)) { - $value = strtotime($value) ?: $value; + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + if (preg_match('/(datetime|timestamp)/is', $info)) { // 日期及时间戳类型 $value = date('Y-m-d H:i:s', $value); @@ -425,14 +483,29 @@ abstract class Builder /** * join分析 * @access protected - * @param mixed $join + * @param array $join + * @param array $options 查询条件 * @return string */ - protected function parseJoin($join) + protected function parseJoin($join, $options = []) { $joinStr = ''; if (!empty($join)) { - $joinStr = ' ' . implode(' ', $join) . ' '; + foreach ($join as $item) { + list($table, $type, $on) = $item; + $condition = []; + foreach ((array) $on as $val) { + if (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($table, $options); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } } return $joinStr; } @@ -441,22 +514,25 @@ abstract class Builder * order分析 * @access protected * @param mixed $order + * @param array $options 查询条件 * @return string */ - protected function parseOrder($order) + protected function parseOrder($order, $options = []) { if (is_array($order)) { $array = []; foreach ($order as $key => $val) { if (is_numeric($key)) { - if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val); - } elseif ('[rand]' == $val) { + if ('[rand]' == $val) { $array[] = $this->parseRand(); + } elseif (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } else { + $array[] = $val; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($key) . ' ' . $sort; + $array[] = $this->parseKey($key, $options) . ' ' . $sort; } } $order = implode(',', $array); @@ -572,14 +648,14 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], [ - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), $this->parseDistinct($options['distinct']), - $this->parseField($options['field']), - $this->parseJoin($options['join']), + $this->parseField($options['field'], $options), + $this->parseJoin($options['join'], $options), $this->parseWhere($options['where'], $options), $this->parseGroup($options['group']), $this->parseHaving($options['having']), - $this->parseOrder($options['order']), + $this->parseOrder($options['order'], $options), $this->parseLimit($options['limit']), $this->parseUnion($options['union']), $this->parseLock($options['lock']), @@ -611,7 +687,7 @@ abstract class Builder ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), implode(' , ', $fields), implode(' , ', $values), $this->parseComment($options['comment']), @@ -643,8 +719,13 @@ abstract class Builder 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]); @@ -657,7 +738,7 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), implode(' , ', $fields), implode(' UNION ALL ', $values), $this->parseComment($options['comment']), @@ -681,7 +762,7 @@ abstract class Builder } $fields = array_map([$this, 'parseKey'], $fields); - $sql = 'INSERT INTO ' . $this->parseTable($table) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); return $sql; } @@ -694,7 +775,7 @@ abstract class Builder */ public function update($data, $options) { - $table = $this->parseTable($options['table']); + $table = $this->parseTable($options['table'], $options); $data = $this->parseData($data, $options); if (empty($data)) { return ''; @@ -706,11 +787,11 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), implode(',', $set), - $this->parseJoin($options['join']), + $this->parseJoin($options['join'], $options), $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order']), + $this->parseOrder($options['order'], $options), $this->parseLimit($options['limit']), $this->parseLock($options['lock']), $this->parseComment($options['comment']), @@ -730,11 +811,11 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table']), - !empty($options['using']) ? ' USING ' . $this->parseTable($options['using']) . ' ' : '', - $this->parseJoin($options['join']), + $this->parseTable($options['table'], $options), + !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', + $this->parseJoin($options['join'], $options), $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order']), + $this->parseOrder($options['order'], $options), $this->parseLimit($options['limit']), $this->parseLock($options['lock']), $this->parseComment($options['comment']), diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index f405f9b9..f14e010b 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,10 +13,8 @@ namespace think\db; use PDO; use PDOStatement; -use think\Collection; use think\Db; use think\db\exception\BindParamException; -use think\db\Query; use think\Debug; use think\Exception; use think\exception\PDOException; @@ -52,8 +50,6 @@ abstract class Connection protected $linkRead; protected $linkWrite; - // 查询结果类型 - protected $resultSetType = 'array'; // 查询结果类型 protected $fetchType = PDO::FETCH_ASSOC; // 字段属性大小写 @@ -65,47 +61,51 @@ abstract class Connection // 数据库连接参数配置 protected $config = [ // 数据库类型 - 'type' => '', + 'type' => '', // 服务器地址 - 'hostname' => '', + 'hostname' => '', // 数据库名 - 'database' => '', + 'database' => '', // 用户名 - 'username' => '', + 'username' => '', // 密码 - 'password' => '', + 'password' => '', // 端口 - 'hostport' => '', + 'hostport' => '', // 连接dsn - 'dsn' => '', + 'dsn' => '', // 数据库连接参数 - 'params' => [], + 'params' => [], // 数据库编码默认采用utf8 - 'charset' => 'utf8', + 'charset' => 'utf8', // 数据库表前缀 - 'prefix' => '', + 'prefix' => '', // 数据库调试模式 - 'debug' => false, + 'debug' => false, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, + 'deploy' => 0, // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, + 'rw_separate' => false, // 读写分离后 主服务器数量 - 'master_num' => 1, + 'master_num' => 1, // 指定从服务器序号 - 'slave_no' => '', + 'slave_no' => '', // 是否严格检查字段是否存在 - 'fields_strict' => true, + 'fields_strict' => true, + // 数据返回类型 + 'result_type' => PDO::FETCH_ASSOC, // 数据集返回类型 - 'resultset_type' => 'array', + 'resultset_type' => 'array', // 自动写入时间戳字段 - 'auto_timestamp' => false, + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 - 'sql_explain' => false, + 'sql_explain' => false, // Builder类 - 'builder' => '', + 'builder' => '', // Query类 - 'query' => '\\think\\db\\Query', + 'query' => '\\think\\db\\Query', ]; // PDO连接参数 @@ -117,6 +117,9 @@ abstract class Connection PDO::ATTR_EMULATE_PREPARES => false, ]; + // 绑定参数 + protected $bind = []; + /** * 架构函数 读取数据库配置信息 * @access public @@ -136,11 +139,11 @@ abstract class Connection * @param string $queryClass 查询对象类名 * @return Query */ - public function model($model, $queryClass = '') + public function getQuery($model = 'db', $queryClass = '') { if (!isset($this->query[$model])) { $class = $queryClass ?: $this->config['query']; - $this->query[$model] = new $class($this, $model); + $this->query[$model] = new $class($this, 'db' == $model ? '' : $model); } return $this->query[$model]; } @@ -154,11 +157,7 @@ abstract class Connection */ public function __call($method, $args) { - if (!isset($this->query['database'])) { - $class = $this->config['query']; - $this->query['database'] = new $class($this); - } - return call_user_func_array([$this->query['database'], $method], $args); + return call_user_func_array([$this->getQuery(), $method], $args); } /** @@ -268,9 +267,10 @@ abstract class Connection } // 记录当前字段属性大小写设置 $this->attrCase = $params[PDO::ATTR_CASE]; - // 记录数据集返回类型 - if (isset($config['resultset_type'])) { - $this->resultSetType = $config['resultset_type']; + + // 数据返回类型 + if (isset($config['result_type'])) { + $this->fetchType = $config['result_type']; } try { if (empty($config['dsn'])) { @@ -322,25 +322,33 @@ abstract class Connection /** * 执行查询 返回数据集 * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $class 是否返回PDO对象 * @param string $sql sql指令 * @param array $bind 参数绑定 * @param boolean $master 是否在主服务器读操作 - * @param bool|string $class 指定返回的数据集对象 + * @param bool $pdo 是否返回PDO对象 * @return mixed * @throws BindParamException * @throws PDOException */ - public function query($sql, $bind = [], $master = false, $class = false) + public function query($sql, $bind = [], $master = false, $pdo = false) { $this->initConnect($master); if (!$this->linkID) { return false; } - // 根据参数绑定组装最终的SQL语句 - $this->queryStr = $this->getRealSql($sql, $bind); + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } //释放前次的查询结果 - if (!empty($this->PDOStatement)) { + if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { $this->free(); } @@ -349,17 +357,25 @@ abstract class Connection // 调试开始 $this->debug(true); // 预处理 - $this->PDOStatement = $this->linkID->prepare($sql); + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); // 参数绑定 - $this->bindValue($bind); + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } // 执行查询 - $result = $this->PDOStatement->execute(); + $this->PDOStatement->execute(); // 调试结束 $this->debug(false); - $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); - return $this->getResult($class, $procedure); + // 返回结果集 + return $this->getResult($pdo, $procedure); } catch (\PDOException $e) { - throw new PDOException($e, $this->config, $this->queryStr); + throw new PDOException($e, $this->config, $this->getLastsql()); } } @@ -378,11 +394,15 @@ abstract class Connection if (!$this->linkID) { return false; } - // 根据参数绑定组装最终的SQL语句 - $this->queryStr = $this->getRealSql($sql, $bind); + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } //释放前次的查询结果 - if (!empty($this->PDOStatement)) { + if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { $this->free(); } @@ -391,18 +411,26 @@ abstract class Connection // 调试开始 $this->debug(true); // 预处理 - $this->PDOStatement = $this->linkID->prepare($sql); - // 参数绑定操作 - $this->bindValue($bind); + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } // 执行语句 - $result = $this->PDOStatement->execute(); + $this->PDOStatement->execute(); // 调试结束 $this->debug(false); $this->numRows = $this->PDOStatement->rowCount(); return $this->numRows; } catch (\PDOException $e) { - throw new PDOException($e, $this->config, $this->queryStr); + throw new PDOException($e, $this->config, $this->getLastsql()); } } @@ -415,23 +443,23 @@ abstract class Connection */ public function getRealSql($sql, array $bind = []) { - if ($bind) { - foreach ($bind as $key => $val) { - $value = is_array($val) ? $val[0] : $val; - $type = is_array($val) ? $val[1] : PDO::PARAM_STR; - if (PDO::PARAM_STR == $type) { - $value = $this->quote($value); - } - // 判断占位符 - $sql = is_numeric($key) ? - substr_replace($sql, $value, strpos($sql, '?'), 1) : - str_replace( - [':' . $key . ')', ':' . $key . ',', ':' . $key . ' '], - [$value . ')', $value . ',', $value . ' '], - $sql . ' '); + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { + $value = $this->quote($value); + } elseif (PDO::PARAM_INT == $type) { + $value = (float) $value; } + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' '], + [$value . ')', $value . ',', $value . ' '], + $sql . ' '); } - return $sql; + return rtrim($sql); } /** @@ -441,7 +469,7 @@ abstract class Connection * @access public * @param array $bind 要绑定的参数列表 * @return void - * @throws \think\Exception + * @throws BindParamException */ protected function bindValue(array $bind = []) { @@ -449,6 +477,9 @@ abstract class Connection // 占位符 $param = is_numeric($key) ? $key + 1 : ':' . $key; if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); } else { $result = $this->PDOStatement->bindValue($param, $val); @@ -457,7 +488,36 @@ abstract class Connection throw new BindParamException( "Error occurred when binding parameters '{$param}'", $this->config, - $this->queryStr, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam($bind) + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + if (!$result) { + $param = array_shift($val); + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), $bind ); } @@ -465,46 +525,37 @@ abstract class Connection } /** - * 获得数据集 + * 获得数据集数组 * @access protected - * @param bool|string $class true 返回PDOStatement 字符串用于指定返回的类名 - * @param bool $procedure 是否存储过程 - * @return mixed + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return array */ - protected function getResult($class = '', $procedure = false) + protected function getResult($pdo = false, $procedure = false) { - if (true === $class) { + if ($pdo) { // 返回PDOStatement对象处理 return $this->PDOStatement; } if ($procedure) { // 存储过程返回结果 - return $this->procedure($class); + return $this->procedure(); } $result = $this->PDOStatement->fetchAll($this->fetchType); $this->numRows = count($result); - - if (!empty($class)) { - // 返回指定数据集对象类 - $result = new $class($result); - } elseif ('collection' == $this->resultSetType) { - // 返回数据集Collection对象 - $result = new Collection($result); - } return $result; } /** * 获得存储过程数据集 * @access protected - * @param bool|string $class true 返回PDOStatement 字符串用于指定返回的类名 * @return array */ - protected function procedure($class) + protected function procedure() { $item = []; do { - $result = $this->getResult($class); + $result = $this->getResult(); if ($result) { $item[] = $result; } @@ -685,7 +736,10 @@ abstract class Connection */ public function close() { - $this->linkID = null; + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; } /** @@ -695,7 +749,7 @@ abstract class Connection */ public function getLastSql() { - return $this->queryStr; + return $this->getRealSql($this->queryStr, $this->bind); } /** @@ -733,7 +787,7 @@ abstract class Connection $error = ''; } if ('' != $this->queryStr) { - $error .= "\n [ SQL语句 ] : " . $this->queryStr; + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); } return $error; } @@ -768,7 +822,7 @@ abstract class Connection // 记录操作结束时间 Debug::remark('queryEndTime', 'time'); $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); - $sql = $sql ?: $this->queryStr; + $sql = $sql ?: $this->getLastsql(); $log = $sql . ' [ RunTime:' . $runtime . 's ]'; $result = []; // SQL性能分析 diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 2e9308cc..45cdd432 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -16,8 +16,6 @@ use think\Cache; use think\Collection; use think\Config; use think\Db; -use think\db\Builder; -use think\db\Connection; use think\db\exception\BindParamException; use think\db\exception\DataNotFoundException; use think\db\exception\ModelNotFoundException; @@ -27,13 +25,14 @@ use think\exception\PDOException; use think\Loader; use think\Model; use think\model\Relation; +use think\model\relation\OneToOne; use think\Paginator; class Query { // 数据库Connection对象实例 protected $connection; - // 数据库驱动类型 + // 数据库Builder对象实例 protected $builder; // 当前模型类名称 protected $model; @@ -51,6 +50,8 @@ class Query protected $bind = []; // 数据表信息 protected static $info = []; + // 回调事件 + private static $event = []; /** * 架构函数 @@ -61,9 +62,10 @@ class Query public function __construct(Connection $connection = null, $model = '') { $this->connection = $connection ?: Db::connect([], true); - $this->builder = $this->connection->getConfig('builder') ?: $this->connection->getConfig('type'); $this->prefix = $this->connection->getConfig('prefix'); $this->model = $model; + // 设置当前连接的Builder对象 + $this->setBuilder(); } /** @@ -111,9 +113,42 @@ class Query public function connect($config) { $this->connection = Db::connect($config); + $this->setBuilder(); return $this; } + /** + * 设置当前的数据库Builder对象 + * @access protected + * @return void + */ + protected function setBuilder() + { + $builder = $this->connection->getConfig('builder') ?: $this->connection->getConfig('type'); + $class = false !== strpos($builder, '\\') ? $builder : '\\think\\db\\builder\\' . ucfirst($builder); + $this->builder = new $class($this->connection, $this); + } + + /** + * 获取当前的模型对象名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + /** * 指定默认的数据表名(不含前缀) * @access public @@ -194,8 +229,8 @@ class Query /** * 执行语句 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 + * @param string $sql sql指令 + * @param array $bind 参数绑定 * @return int * @throws BindParamException * @throws PDOException @@ -208,7 +243,7 @@ class Query /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -350,35 +385,18 @@ class Query } } - /** - * 获取当前的builder实例对象 - * @access protected - * @return Builder - */ - protected function builder() - { - static $builder = []; - $driver = $this->builder; - if (!isset($builder[$driver])) { - $class = false !== strpos($driver, '\\') ? $driver : '\\think\\db\\builder\\' . ucfirst($driver); - $builder[$driver] = new $class($this->connection); - } - // 设置当前查询对象 - $builder[$driver]->setQuery($this); - return $builder[$driver]; - } - /** * 得到某个字段的值 * @access public * @param string $field 字段名 * @param mixed $default 默认值 + * @param bool $force 强制转为数字类型 * @return mixed */ - public function value($field, $default = null) + public function value($field, $default = null, $force = false) { - $result = null; - if (!empty($this->options['cache'])) { + $result = false; + if (empty($options['fetch_sql']) && !empty($this->options['cache'])) { // 判断查询缓存 $cache = $this->options['cache']; if (empty($this->options['table'])) { @@ -387,16 +405,19 @@ class Query $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options)); $result = Cache::get($key); } - if (!$result) { + if (false === $result) { if (isset($this->options['field'])) { unset($this->options['field']); } - $pdo = $this->field($field)->fetchPdo(true)->find(); + $pdo = $this->field($field)->limit(1)->getPdo(); if (is_string($pdo)) { // 返回SQL语句 return $pdo; } $result = $pdo->fetchColumn(); + if ($force) { + $result = is_numeric($result) ? $result + 0 : $result; + } if (isset($cache)) { // 缓存数据 if (isset($cache['tag'])) { @@ -409,7 +430,7 @@ class Query // 清空查询条件 $this->options = []; } - return !is_null($result) ? $result : $default; + return false !== $result ? $result : $default; } /** @@ -422,7 +443,7 @@ class Query public function column($field, $key = '') { $result = false; - if (!empty($this->options['cache'])) { + if (empty($options['fetch_sql']) && !empty($this->options['cache'])) { // 判断查询缓存 $cache = $this->options['cache']; if (empty($this->options['table'])) { @@ -431,14 +452,14 @@ class Query $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options)); $result = Cache::get($guid); } - if (!$result) { + if (false === $result) { if (isset($this->options['field'])) { unset($this->options['field']); } if ($key && '*' != $field) { $field = $key . ',' . $field; } - $pdo = $this->field($field)->fetchPdo(true)->select(); + $pdo = $this->field($field)->getPdo(); if (is_string($pdo)) { // 返回SQL语句 return $pdo; @@ -453,6 +474,9 @@ class Query $key1 = array_shift($fields); $key2 = $fields ? array_shift($fields) : ''; $key = $key ?: $key1; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } foreach ($resultSet as $val) { if ($count > 2) { $result[$val[$key]] = $val; @@ -485,11 +509,18 @@ class Query * COUNT查询 * @access public * @param string $field 字段名 - * @return integer + * @return integer|string */ public function count($field = '*') { - return (int) $this->value('COUNT(' . $field . ') AS tp_count', 0); + if (isset($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } + + return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); } /** @@ -498,31 +529,31 @@ class Query * @param string $field 字段名 * @return float|int */ - public function sum($field = '*') + public function sum($field) { - return $this->value('SUM(' . $field . ') AS tp_sum', 0) + 0; + return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); } /** * MIN查询 * @access public * @param string $field 字段名 - * @return float|int + * @return mixed */ - public function min($field = '*') + public function min($field) { - return $this->value('MIN(' . $field . ') AS tp_min', 0) + 0; + return $this->value('MIN(' . $field . ') AS tp_min', 0, true); } /** * MAX查询 * @access public * @param string $field 字段名 - * @return float|int + * @return mixed */ - public function max($field = '*') + public function max($field) { - return $this->value('MAX(' . $field . ') AS tp_max', 0) + 0; + return $this->value('MAX(' . $field . ') AS tp_max', 0, true); } /** @@ -531,9 +562,9 @@ class Query * @param string $field 字段名 * @return float|int */ - public function avg($field = '*') + public function avg($field) { - return $this->value('AVG(' . $field . ') AS tp_avg', 0) + 0; + return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); } /** @@ -578,8 +609,6 @@ class Query // 清空查询条件 $this->options = []; return true; - } else { - return $this->setField($field, $step); } } return $this->setField($field, ['exp', $field . '+' . $step]); @@ -609,8 +638,6 @@ class Query // 清空查询条件 $this->options = []; return true; - } else { - return $this->setField($field, $step); } } return $this->setField($field, ['exp', $field . '-' . $step]); @@ -663,39 +690,54 @@ class Query } } } else { - $prefix = $this->prefix; - // 传入的表名为数组 - if (is_array($join)) { - if (0 !== $key = key($join)) { - // 设置了键名则键名为表名,键值作为表的别名 - $table = $key . ' ' . array_shift($join); - } else { - $table = array_shift($join); - } - if (count($join)) { - // 有设置第二个元素则把第二元素作为表前缀 - $table = (string) current($join) . $table; - } elseif (false === strpos($table, '.')) { - // 加上默认的表前缀 - $table = $prefix . $table; - } + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + return $this; + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * @access public + * @param array|string $join + * @return array|string + */ + protected function getJoinTable($join, &$alias = null) + { + // 传入的表名为数组 + if (is_array($join)) { + list($table, $alias) = each($join); + } else { + $join = trim($join); + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; } else { - $join = trim($join); - if (0 === strpos($join, '__')) { - $table = $this->parseSqlTable($join); - } elseif (false === strpos($join, '(') && false === strpos($join, '.') && !empty($prefix) && 0 !== strpos($join, $prefix)) { - // 传入的表名中不带有'('并且不以默认的表前缀开头时加上默认的表前缀 - $table = $prefix . $join; + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); } else { $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); } } - if (is_array($condition)) { - $condition = implode(' AND ', $condition); + } + if (isset($alias)) { + if (isset($this->options['alias'][$table])) { + $table = $table . '@think' . uniqid(); } - $this->options['join'][] = strtoupper($type) . ' JOIN ' . $table . ' ON ' . $condition; + $table = [$table => $alias]; + $this->alias($table); } - return $this; + return $table; } /** @@ -762,6 +804,68 @@ class Query return $this; } + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['exp', $field . '+' . $step]); + } + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['exp', $field . '-' . $step]); + } + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, ['exp', $value]); + return $this; + } + /** * 指定JOIN查询字段 * @access public @@ -780,13 +884,8 @@ class Query } } else { $fields = []; - if (is_array($join)) { - // 支持数据表别名 - list($join, $alias, $table) = array_pad($join, 3, ''); - } else { - $alias = $join; - } - $table = !empty($table) ? $table : $this->getTable($join); + $table = $this->getJoinTable($join, $alias); + if (true === $field) { $fields = $alias . '.*'; } else { @@ -810,9 +909,9 @@ class Query } $this->field($fields); if ($on) { - $this->join($table . ' ' . $alias, $on, $type); + $this->join($table, $on, $type); } else { - $this->table($table . ' ' . $alias); + $this->table($table); } } return $this; @@ -880,6 +979,156 @@ class Query return $this; } + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $condition); + return $this; + } + /** * 分析查询表达式 * @access public @@ -892,6 +1141,7 @@ class Query */ protected function parseWhereExp($logic, $field, $op, $condition, $param = []) { + $logic = strtoupper($logic); if ($field instanceof \Closure) { $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; return; @@ -910,33 +1160,96 @@ class Query if (is_array($field)) { // 数组批量查询 $where = $field; - } elseif ($field) { - // 字符串查询 - if (is_numeric($field)) { - $where[] = ['exp', $field]; - } else { - $where[$field] = ['null', '']; + foreach ($where as $k => $val) { + $this->options['multi'][$logic][$k][] = $val; } + } elseif ($field && is_string($field)) { + // 字符串查询 + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } } elseif (is_array($op)) { $where[$field] = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { // null查询 - $where[$field] = [$op, '']; + $where[$field] = [$op, '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } elseif (is_null($condition)) { // 字段相等查询 $where[$field] = ['eq', $op]; + if ('AND' != $logic) { + $this->options['multi'][$logic][$field][] = $where[$field]; + } } else { - $where[$field] = [$op, $condition]; + $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null]; + if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { + // 参数绑定 + $this->bind($param[2]); + } + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; } if (!empty($where)) { if (!isset($this->options['where'][$logic])) { $this->options['where'][$logic] = []; } + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; + } elseif (is_array($field)) { + foreach ($field as $key => $val) { + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; + } + } + } $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); } } + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) + { + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + } + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + /** * 指定查询数量 * @access public @@ -971,17 +1284,17 @@ class Query /** * 分页查询 - * @param int|null $listRows 每页数量 - * @param int|bool $simple 简洁模式或者总记录数 - * @param array $config 配置参数 - * page:当前页, - * path:url路径, - * query:url额外参数, - * fragment:url锚点, - * var_page:分页变量, - * list_rows:每页数量 - * type:分页类名 - * @return \think\paginator\Collection + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator * @throws DbException */ public function paginate($listRows = null, $simple = false, $config = []) @@ -990,8 +1303,13 @@ class Query $total = $simple; $simple = false; } - $config = array_merge(Config::get('paginate'), $config); - $listRows = $listRows ?: $config['list_rows']; + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } /** @var Paginator $class */ $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); @@ -1006,8 +1324,11 @@ class Query if (!isset($total) && !$simple) { $options = $this->getOptions(); - $total = $this->count(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + $bind = $this->bind; + $total = $this->count(); $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); } elseif ($simple) { $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); @@ -1021,11 +1342,44 @@ class Query /** * 指定当前操作的数据表 * @access public - * @param string $table 表名 + * @param mixed $table 表名 * @return $this */ public function table($table) { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $tables = $table; + $table = []; + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } $this->options['table'] = $table; return $this; } @@ -1067,7 +1421,14 @@ class Query } } } - $this->options['order'] = $field; + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } } return $this; } @@ -1145,12 +1506,27 @@ class Query /** * 指定数据表别名 * @access public - * @param string $alias 数据表别名 + * @param mixed $alias 数据表别名 * @return $this */ public function alias($alias) { - $this->options['alias'] = $alias; + if (is_array($alias)) { + foreach ($alias as $key => $val) { + $this->options['alias'][$key] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } return $this; } @@ -1198,19 +1574,7 @@ class Query */ public function fetchPdo($pdo = true) { - $this->options['fetch_class'] = $pdo; - return $this; - } - - /** - * 指定数据集返回对象 - * @access public - * @param string $class 指定返回的数据集对象类名 - * @return $this - */ - public function fetchClass($class) - { - $this->options['fetch_class'] = $class; + $this->options['fetch_pdo'] = $pdo; return $this; } @@ -1289,7 +1653,7 @@ class Query switch (strtolower($op)) { case 'today': case 'd': - $range = 'today'; + $range = ['today', 'tomorrow']; break; case 'week': case 'w': @@ -1315,6 +1679,8 @@ class Query case 'last year': $range = [mktime(0, 0, 0, 1, 1, $date['year'] - 1), mktime(0, 0, 0, 1, 1, $date['year'])]; break; + default: + $range = $op; } $op = is_array($range) ? 'between' : '>'; } @@ -1325,7 +1691,7 @@ class Query /** * 获取数据表信息 * @access public - * @param string $tableName 数据表名 留空自动获取 + * @param mixed $tableName 数据表名 留空自动获取 * @param string $fetch 获取信息类型 包括 fields type bind pk * @return mixed */ @@ -1345,10 +1711,16 @@ class Query $tableName = $this->parseSqlTable($tableName); } + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + list($guid) = explode(' ', $tableName); - if (!isset(self::$info[$guid])) { + $db = $this->getConfig('database'); + if (!isset(self::$info[$db . '.' . $guid])) { if (!strpos($guid, '.')) { - $schema = $this->getConfig('database') . '.' . $guid; + $schema = $db . '.' . $guid; } else { $schema = $guid; } @@ -1374,9 +1746,9 @@ class Query } else { $pk = null; } - self::$info[$guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; } - return $fetch ? self::$info[$guid][$fetch] : self::$info[$guid]; + return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; } /** @@ -1428,7 +1800,7 @@ class Query */ protected function getFieldBindType($type) { - if (preg_match('/(int|double|float|decimal|real|numeric|serial)/is', $type)) { + if (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { $bind = PDO::PARAM_INT; } elseif (preg_match('/bool/is', $type)) { $bind = PDO::PARAM_BOOL; @@ -1510,13 +1882,14 @@ class Query $with = explode(',', $with); } - $i = 0; + $first = true; $currentModel = $this->model; /** @var Model $class */ $class = new $currentModel; foreach ($with as $key => $relation) { - $closure = false; + $subRelation = ''; + $closure = false; if ($relation instanceof \Closure) { // 支持闭包查询过滤关联条件 $closure = $relation; @@ -1528,53 +1901,51 @@ class Query } /** @var Relation $model */ - $model = $class->$relation(); - $info = $model->getRelationInfo(); - if (in_array($info['type'], [Relation::HAS_ONE, Relation::BELONGS_TO])) { - if (0 == $i) { - $name = Loader::parseName(basename(str_replace('\\', '/', $currentModel))); - $table = $this->getTable(); - $alias = isset($info['alias'][$name]) ? $info['alias'][$name] : $name; - $this->table($table)->alias($alias); - if (isset($this->options['field'])) { - $field = $this->options['field']; - unset($this->options['field']); - } else { - $field = true; - } - $this->field($field, false, $table, $alias); - } - // 预载入封装 - $joinTable = $model->getTable(); - $joinName = Loader::parseName(basename(str_replace('\\', '/', $info['model']))); - $joinAlias = isset($info['alias'][$joinName]) ? $info['alias'][$joinName] : $joinName; - $this->via($joinAlias); - - if (Relation::HAS_ONE == $info['type']) { - $this->join($joinTable . ' ' . $joinAlias, $alias . '.' . $info['localKey'] . '=' . $joinAlias . '.' . $info['foreignKey'], $info['joinType']); - } else { - $this->join($joinTable . ' ' . $joinAlias, $alias . '.' . $info['foreignKey'] . '=' . $joinAlias . '.' . $info['localKey'], $info['joinType']); - } - - if ($closure) { - // 执行闭包查询 - call_user_func_array($closure, [ & $this]); - //指定获取关联的字段 - //需要在 回调中 调方法 withField 方法,如 - // $query->where(['id'=>1])->withField('id,name'); - if (!empty($this->options['with_field'])) { - $field = $this->options['with_field']; - unset($this->options['with_field']); - } - } - $this->field($field, false, $joinTable, $joinAlias, $relation . '__'); - $i++; + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; } elseif ($closure) { $with[$key] = $closure; } } $this->via(); - $this->options['with'] = $with; + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'] = $relation; + } else { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!isset($this->options['field'])) { + $this->field('*'); + } + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = '(' . (new $this->model)->$relation()->getRelationCountQuery($closure) . ')'; + $this->field([$count => Loader::parseName($relation) . '_count']); + } + } return $this; } @@ -1609,12 +1980,22 @@ class Query /** * 设置关联查询 * @access public - * @param string $relation 关联名称 + * @param string|array $relation 关联名称 * @return $this */ public function relation($relation) { - $this->options['relation'] = $relation; + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_mrege($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } return $this; } @@ -1630,8 +2011,9 @@ class Query { $pk = $this->getPk($options); // 获取当前数据表 - if (!empty($options['alias'])) { - $alias = $options['alias']; + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + if (!empty($options['alias'][$table])) { + $alias = $options['alias'][$table]; } if (is_string($pk)) { $key = isset($alias) ? $alias . '.' . $pk : $pk; @@ -1672,12 +2054,13 @@ class Query * @param string $sequence 自增序列名 * @return integer|string */ - public function insert(array $data, $replace = false, $getLastInsID = false, $sequence = null) + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) { // 分析查询表达式 $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); // 生成SQL语句 - $sql = $this->builder()->insert($data, $options, $replace); + $sql = $this->builder->insert($data, $options, $replace); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1687,6 +2070,9 @@ class Query // 执行操作 $result = $this->execute($sql, $bind); + if ($result) { + $this->trigger('after_insert', $options); + } if ($getLastInsID) { $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); return $this->getLastInsID($sequence); @@ -1721,7 +2107,7 @@ class Query return false; } // 生成SQL语句 - $sql = $this->builder()->insertAll($dataSet, $options); + $sql = $this->builder->insertAll($dataSet, $options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1747,7 +2133,7 @@ class Query $options = $this->parseExpress(); // 生成SQL语句 $table = $this->parseSqlTable($table); - $sql = $this->builder()->selectInsert($fields, $table, $options); + $sql = $this->builder->selectInsert($fields, $table, $options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1767,9 +2153,10 @@ class Query * @throws Exception * @throws PDOException */ - public function update(array $data) + public function update(array $data = []) { $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); $pk = $this->getPk($options); if (isset($options['cache']) && is_string($options['cache'])) { $key = $options['cache']; @@ -1805,7 +2192,7 @@ class Query $key = 'think:' . $options['table'] . '|' . $options['where']['AND'][$pk]; } // 生成UPDATE SQL语句 - $sql = $this->builder()->update($data, $options); + $sql = $this->builder->update($data, $options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1818,10 +2205,35 @@ class Query Cache::rm($key); } // 执行操作 - return '' == $sql ? 0 : $this->execute($sql, $bind); + $result = '' == $sql ? 0 : $this->execute($sql, $bind); + if ($result) { + $this->trigger('after_update', $options); + } + return $result; } } + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + /** * 查找记录 * @access public @@ -1860,19 +2272,22 @@ class Query } if (!$resultSet) { // 生成查询SQL - $sql = $this->builder()->select($options); + $sql = $this->builder->select($options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { // 获取实际执行的SQL语句 return $this->connection->getRealSql($sql, $bind); } - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_class']); + if ($resultSet = $this->trigger('before_select', $options)) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } } if (isset($cache)) { @@ -1885,28 +2300,41 @@ class Query } } - // 返回结果处理 - if (count($resultSet) > 0) { - // 数据列表读取后的处理 - if (!empty($this->model)) { - // 生成模型对象 - $model = $this->model; + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $modelName = $this->model; + if (count($resultSet) > 0) { foreach ($resultSet as $key => $result) { /** @var Model $result */ - $result = new $model($result); - $result->isUpdate(true); + $model = new $modelName($result); + $model->isUpdate(true); + // 关联查询 if (!empty($options['relation'])) { - $result->relationQuery($options['relation']); + $model->relationQuery($options['relation']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $model->relationCount($model, $options['with_count']); } - $resultSet[$key] = $result; + $resultSet[$key] = $model; } - if (!empty($options['with']) && $result instanceof Model) { + if (!empty($options['with'])) { // 预载入 - $resultSet = $result->eagerlyResultSet($resultSet, $options['with'], is_object($resultSet) ? get_class($resultSet) : ''); + $model->eagerlyResultSet($resultSet, $options['with']); } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = (new $modelName)->toCollection($resultSet); } - } elseif (!empty($options['fail'])) { + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + // 返回结果处理 + if (!empty($options['fail']) && count($resultSet) == 0) { $this->throwNotFound($options); } return $resultSet; @@ -1943,7 +2371,7 @@ class Query // 判断查询缓存 $cache = $options['cache']; if (true === $cache['key'] && !is_null($data) && !is_array($data)) { - $key = 'think:' . $options['table'] . '|' . $data; + $key = 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; } else { $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options)); } @@ -1951,19 +2379,24 @@ class Query } if (!$result) { // 生成查询SQL - $sql = $this->builder()->select($options); + $sql = $this->builder->select($options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { // 获取实际执行的SQL语句 return $this->connection->getRealSql($sql, $bind); } - // 执行查询 - $result = $this->query($sql, $bind, $options['master'], $options['fetch_class']); - if ($result instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $result; + // 事件回调 + if ($result = $this->trigger('before_find', $options)) { + } else { + // 执行查询 + $result = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($result instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $result; + } } if (isset($cache)) { @@ -1988,9 +2421,13 @@ class Query if (!empty($options['relation'])) { $data->relationQuery($options['relation']); } + // 预载入查询 if (!empty($options['with'])) { - // 预载入 - $data->eagerlyResult($data, $options['with'], is_object($result) ? get_class($result) : ''); + $data->eagerlyResult($data, $options['with']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $data->relationCount($data, $options['with_count']); } } } elseif (!empty($options['fail'])) { @@ -2013,7 +2450,8 @@ class Query if (!empty($this->model)) { throw new ModelNotFoundException('model data Not Found:' . $this->model, $this->model, $options); } else { - throw new DataNotFoundException('table data not Found:' . $options['table'], $options['table'], $options); + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); } } @@ -2055,23 +2493,39 @@ class Query */ public function chunk($count, $callback, $column = null) { - $options = $this->getOptions(); - $column = $column ?: $this->getPk(isset($options['table']) ? $options['table'] : ''); + $options = $this->getOptions(); + if (isset($options['table'])) { + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + } else { + $table = ''; + } + $column = $column ?: $this->getPk($table); $bind = $this->bind; $resultSet = $this->limit($count)->order($column, 'asc')->select(); + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } while (!empty($resultSet)) { if (false === call_user_func($callback, $resultSet)) { return false; } $end = end($resultSet); - $lastId = is_array($end) ? $end[$column] : $end->$column; + $lastId = is_array($end) ? $end[$key] : $end->$key; $resultSet = $this->options($options) ->limit($count) ->bind($bind) ->where($column, '>', $lastId) ->order($column, 'asc') ->select(); + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } } return true; } @@ -2130,7 +2584,7 @@ class Query throw new Exception('delete without condition'); } // 生成删除SQL语句 - $sql = $this->builder()->delete($options); + $sql = $this->builder->delete($options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -2144,7 +2598,11 @@ class Query Cache::rm($key); } // 执行操作 - return $this->execute($sql, $bind); + $result = $this->execute($sql, $bind); + if ($result) { + $this->trigger('after_delete', $options); + } + return $result; } /** @@ -2201,20 +2659,19 @@ class Query } } - // 表别名 - if (!empty($options['alias'])) { - $options['table'] .= ' ' . $options['alias']; - } - if (!isset($options['field'])) { $options['field'] = '*'; } + if (!isset($options['data'])) { + $options['data'] = []; + } + if (!isset($options['strict'])) { $options['strict'] = $this->getConfig('fields_strict'); } - foreach (['master', 'lock', 'fetch_class', 'fetch_sql', 'distinct'] as $name) { + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { if (!isset($options[$name])) { $options[$name] = false; } @@ -2239,4 +2696,32 @@ class Query return $options; } + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $options 当前查询参数 + * @return bool + */ + protected function trigger($event, $options = []) + { + $result = false; + if (isset(self::$event[$event])) { + $callback = self::$event[$event]; + $result = call_user_func_array($callback, [$options, $this]); + } + return $result; + } } diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 04586906..de38fac5 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -24,19 +24,28 @@ class Mysql extends Builder * 字段和表名处理 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { $key = trim($key); if (strpos($key, '$.') && false === strpos($key, '(')) { // JSON字段支持 list($field, $name) = explode('$.', $key); $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } } if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } + if (isset($table)) { + $key = '`' . $table . '`.' . $key; + } return $key; } diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 0a955a5b..8b853a2f 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -43,15 +43,24 @@ class Pgsql extends Builder * 字段和表名处理 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { $key = trim($key); if (strpos($key, '$.') && false === strpos($key, '(')) { // JSON字段支持 list($field, $name) = explode('$.', $key); $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; } return $key; } diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 17e59cb2..02d1bf2e 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -48,4 +48,25 @@ class Sqlite extends Builder return 'RANDOM()'; } + /** + * 字段和表名处理 + * @access protected + * @param string $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = []) + { + $key = trim($key); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } } diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index bf630d90..d2f418f3 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -27,22 +27,23 @@ class Sqlsrv extends Builder * order分析 * @access protected * @param mixed $order + * @param array $options * @return string */ - protected function parseOrder($order) + protected function parseOrder($order, $options = []) { if (is_array($order)) { $array = []; foreach ($order as $key => $val) { if (is_numeric($key)) { if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val); + $array[] = $this->parseKey($val, $options); } elseif ('[rand]' == $val) { $array[] = $this->parseRand(); } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($key) . ' ' . $sort; + $array[] = $this->parseKey($key, $options) . ' ' . $sort; } } $order = implode(',', $array); @@ -61,17 +62,27 @@ class Sqlsrv extends Builder } /** - * 字段名分析 + * 字段和表名处理 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { $key = '[' . $key . ']'; } + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } return $key; } diff --git a/library/think/db/connector/Mysql.php b/library/think/db/connector/Mysql.php index 47b069fb..31435694 100644 --- a/library/think/db/connector/Mysql.php +++ b/library/think/db/connector/Mysql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -51,10 +51,13 @@ class Mysql extends Connection { $this->initConnect(true); list($tableName) = explode(' ', $tableName); - if (strpos($tableName, '.')) { - $tableName = str_replace('.', '`.`', $tableName); + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; } - $sql = 'SHOW COLUMNS FROM `' . $tableName . '`'; + $sql = 'SHOW COLUMNS FROM ' . $tableName; // 调试开始 $this->debug(true); $pdo = $this->linkID->query($sql); diff --git a/library/think/db/connector/Pgsql.php b/library/think/db/connector/Pgsql.php index e9866cf3..4f8756ca 100644 --- a/library/think/db/connector/Pgsql.php +++ b/library/think/db/connector/Pgsql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/db/connector/Sqlite.php b/library/think/db/connector/Sqlite.php index 95b023e5..4a08c740 100644 --- a/library/think/db/connector/Sqlite.php +++ b/library/think/db/connector/Sqlite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/db/exception/BindParamException.php b/library/think/db/exception/BindParamException.php index 585a0d73..d0e2387b 100644 --- a/library/think/db/exception/BindParamException.php +++ b/library/think/db/exception/BindParamException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -16,7 +16,7 @@ use think\exception\DbException; /** * PDO参数绑定异常 */ -class BindParamException extends DbException +class BindParamException extends DbException { /** diff --git a/library/think/db/exception/DataNotFoundException.php b/library/think/db/exception/DataNotFoundException.php index efd66d3b..e399b063 100644 --- a/library/think/db/exception/DataNotFoundException.php +++ b/library/think/db/exception/DataNotFoundException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,7 +13,7 @@ namespace think\db\exception; use think\exception\DbException; -class DataNotFoundException extends DbException +class DataNotFoundException extends DbException { protected $table; @@ -23,10 +23,10 @@ class DataNotFoundException extends DbException * @param string $table * @param array $config */ - public function __construct($message, $table = '', Array $config = []) + public function __construct($message, $table = '', array $config = []) { - $this->message = $message; - $this->table = $table; + $this->message = $message; + $this->table = $table; $this->setData('Database Config', $config); } diff --git a/library/think/db/exception/ModelNotFoundException.php b/library/think/db/exception/ModelNotFoundException.php index 69b70965..2180ab07 100644 --- a/library/think/db/exception/ModelNotFoundException.php +++ b/library/think/db/exception/ModelNotFoundException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,7 +13,7 @@ namespace think\db\exception; use think\exception\DbException; -class ModelNotFoundException extends DbException +class ModelNotFoundException extends DbException { protected $model; @@ -22,10 +22,10 @@ class ModelNotFoundException extends DbException * @param string $message * @param string $model */ - public function __construct($message, $model = '', Array $config = []) + public function __construct($message, $model = '', array $config = []) { - $this->message = $message; - $this->model = $model; + $this->message = $message; + $this->model = $model; $this->setData('Database Config', $config); } diff --git a/library/think/exception/DbException.php b/library/think/exception/DbException.php index bd6fb442..656d6913 100644 --- a/library/think/exception/DbException.php +++ b/library/think/exception/DbException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/exception/ErrorException.php b/library/think/exception/ErrorException.php index 2f1525c0..e3f18375 100644 --- a/library/think/exception/ErrorException.php +++ b/library/think/exception/ErrorException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/exception/PDOException.php b/library/think/exception/PDOException.php index 677092b9..ebd53dff 100644 --- a/library/think/exception/PDOException.php +++ b/library/think/exception/PDOException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,8 +11,6 @@ namespace think\exception; -use think\exception\DbException; - /** * PDO异常处理类 * 重新封装了系统的\PDOException类 diff --git a/library/think/exception/RouteNotFoundException.php b/library/think/exception/RouteNotFoundException.php new file mode 100644 index 00000000..f85a3c4b --- /dev/null +++ b/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404); + } + +} diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index c4e46279..b639fd04 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -11,6 +11,8 @@ namespace think\log\driver; +use think\App; + /** * 本地化调试输出到文件 */ @@ -35,9 +37,10 @@ class File * 日志写入接口 * @access public * @param array $log 日志信息 + * @param bool $depr 是否写入分割线 * @return bool */ - public function save(array $log = []) + public function save(array $log = [], $depr = true) { $now = date($this->config['time_format']); $destination = $this->config['path'] . date('Ym') . DS . date('d') . '.log'; @@ -50,25 +53,29 @@ class File rename($destination, dirname($destination) . DS . $_SERVER['REQUEST_TIME'] . '-' . basename($destination)); } - // 获取基本信息 - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); - } + $depr = $depr ? "---------------------------------------------------------------\r\n" : ''; + $info = ''; + if (App::$debug) { + // 获取基本信息 + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); + } - $runtime = number_format(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + $runtime = round(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - $info = '[ log ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n"; - $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; - $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + $info = '[ log ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n"; + $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; + $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } foreach ($log as $type => $val) { $level = ''; foreach ($val as $msg) { @@ -80,12 +87,15 @@ class File if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 $filename = $path . DS . date('d') . '_' . $type . '.log'; - error_log("[{$now}] {$server} {$remote} {$method} {$uri}\r\n{$level}\r\n---------------------------------------------------------------\r\n", 3, $filename); + error_log("[{$now}] {$level}\r\n{$depr}", 3, $filename); } else { $info .= $level; } } - return error_log("[{$now}] {$server} {$remote} {$method} {$uri}\r\n{$info}\r\n---------------------------------------------------------------\r\n", 3, $destination); + if (App::$debug) { + $info = "{$server} {$remote} {$method} {$uri}\r\n" . $info; + } + return error_log("[{$now}] {$info}\r\n{$depr}", 3, $destination); } } diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index f24b6091..08fd8ace 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -11,6 +11,8 @@ namespace think\log\driver; +use think\App; + /** * github: https://github.com/luofei614/SocketLog * @author luofei614 @@ -63,24 +65,27 @@ class Socket if (!$this->check()) { return false; } - $runtime = number_format(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; } - // 基本信息 - $trace[] = [ - 'type' => 'group', - 'msg' => $current_uri . $time_str . $memory_str . $file_load, - 'css' => $this->css['page'], - ]; foreach ($log as $type => $val) { $trace[] = [ @@ -204,19 +209,19 @@ class Socket } if (!isset($_SERVER[$key])) { - return null; + return; } if (empty($args)) { if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { $args = ['tabid' => null]; - return null; + return; } parse_str($match[1], $args); } if (isset($args[$name])) { return $args[$name]; } - return null; + return; } /** diff --git a/library/think/model/Collection.php b/library/think/model/Collection.php new file mode 100644 index 00000000..08e11ad8 --- /dev/null +++ b/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model->append($append, $override); + }); + return $this; + } + +} diff --git a/library/think/model/Merge.php b/library/think/model/Merge.php index c28d0fd7..47d54923 100644 --- a/library/think/model/Merge.php +++ b/library/think/model/Merge.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,7 @@ namespace think\model; -use think\Db; +use think\db\Query; use think\Model; class Merge extends Model @@ -39,9 +39,9 @@ class Merge extends Model /** * 查找单条记录 * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 * @return \think\Model */ public static function get($data = null, $with = [], $cache = false) @@ -61,14 +61,14 @@ class Merge extends Model { $class = new static(); $master = $class->name; - $fields = self::getModelField($query, $master, '', $class->mapFields); + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); $query->alias($master)->field($fields); foreach ($class->relationModel as $key => $model) { $name = is_int($key) ? $model : $key; $table = is_int($key) ? $query->getTable($name) : $model; $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); - $fields = self::getModelField($query, $name, $table, $class->mapFields); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); $query->field($fields); } return $query; @@ -77,16 +77,17 @@ class Merge extends Model /** * 获取关联模型的字段 并解决混淆 * @access protected - * @param \think\db\Query $query 查询对象 - * @param string $name 模型名称 - * @param string $table 关联表名称 - * @param array $map 字段映射 + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 * @return array */ - protected static function getModelField($query, $name, $table = '', $map = []) + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) { // 获取模型的字段信息 - $fields = $query->getTableInfo($table, 'fields'); + $fields = $fields ?: $query->getTableInfo($table, 'fields'); $array = []; foreach ($fields as $field) { if ($key = array_search($name . '.' . $field, $map)) { @@ -102,8 +103,9 @@ class Merge extends Model /** * 查找所有记录 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache * @return array|false|string */ public static function all($data = null, $with = [], $cache = false) @@ -116,10 +118,10 @@ class Merge extends Model /** * 处理写入的模型数据 * @access public - * @param string $model 模型名称 - * @param array $data 数据 - * @param bool $insert 是否新增 - * @return void + * @param string $model 模型名称 + * @param array $data 数据 + * @param bool $insert 是否新增 + * @return array */ protected function parseData($model, $data, $insert = false) { @@ -142,10 +144,11 @@ class Merge extends Model /** * 保存模型数据 以及关联数据 * @access public - * @param mixed $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return integer|false + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception */ public function save($data = [], $where = [], $sequence = null) { @@ -156,7 +159,7 @@ class Merge extends Model } // 数据对象赋值 foreach ($data as $key => $value) { - $this->setAttr($key, $value); + $this->setAttr($key, $value, $data); } if (!empty($where)) { $this->isUpdate = true; @@ -167,12 +170,13 @@ class Merge extends Model $this->autoCompleteData($this->auto); // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime) { + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { $this->setAttr($this->updateTime, null); } $db = $this->db(); $db->startTrans(); + $pk = $this->getPk(); try { if ($this->isUpdate) { // 自动写入 @@ -186,19 +190,15 @@ class Merge extends Model $where = $this->updateWhere; } - if (!empty($where)) { - $pk = $this->getPk(); - - if (isset($this->mapFields[$pk])) { - $pk = $this->mapFields[$pk]; - } - if (isset($where[$pk])) { - unset($where[$pk]); - } - } - // 处理模型数据 $data = $this->parseData($this->name, $this->data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } // 写入主表数据 $result = $db->strict(false)->where($where)->update($data); @@ -208,7 +208,7 @@ class Merge extends Model $table = is_int($key) ? $db->getTable($model) : $model; // 处理关联模型数据 $data = $this->parseData($name, $this->data); - $query = clone $db; + $query = new Query; if ($query->table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { $result = 1; } @@ -222,7 +222,7 @@ class Merge extends Model $this->autoCompleteData($this->insert); // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime) { + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { $this->setAttr($this->createTime, null); } @@ -237,7 +237,6 @@ class Merge extends Model if ($result) { $insertId = $db->getLastInsID($sequence); // 写入外键数据 - $pk = $this->getPk(); if ($insertId) { if (is_string($pk)) { $this->data[$pk] = $insertId; @@ -258,7 +257,7 @@ class Merge extends Model $table = is_int($key) ? $db->getTable($model) : $model; // 处理关联模型数据 $data = $this->parseData($name, $source, true); - $query = clone $db; + $query = new Query; $query->table($table)->strict(false)->insert($data); } } @@ -280,7 +279,8 @@ class Merge extends Model /** * 删除当前的记录 并删除关联数据 * @access public - * @return integer + * @return int + * @throws \Exception */ public function delete() { @@ -299,7 +299,7 @@ class Merge extends Model // 删除关联数据 foreach ($this->relationModel as $key => $model) { $table = is_int($key) ? $db->getTable($model) : $model; - $query = clone $db; + $query = new Query; $query->table($table)->where($this->fk, $pk)->delete(); } } diff --git a/library/think/model/Pivot.php b/library/think/model/Pivot.php index 24034b07..7823370c 100644 --- a/library/think/model/Pivot.php +++ b/library/think/model/Pivot.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index 22670bdd..3d56091b 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,691 +11,109 @@ namespace think\model; -use think\Db; +use think\db\Query; use think\Exception; -use think\Loader; use think\Model; -use think\model\Pivot; -class Relation +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation { - const HAS_ONE = 1; - const HAS_MANY = 2; - const HAS_MANY_THROUGH = 5; - const BELONGS_TO = 3; - const BELONGS_TO_MANY = 4; - // 父模型对象 protected $parent; /** @var Model 当前关联的模型类 */ protected $model; - // 中间表模型 - protected $middle; - // 当前关联类型 - protected $type; + /** @var Query 关联模型查询对象 */ + protected $query; // 关联表外键 protected $foreignKey; - // 中间关联表外键 - protected $throughKey; // 关联表主键 protected $localKey; - // 数据表别名 - protected $alias; - // 当前关联的JOIN类型 - protected $joinType; - // 关联模型查询对象 - protected $query; - // 关联查询条件 - protected $where; + // 关联查询参数 + protected $option; + // 基础查询 + protected $baseQuery; /** - * 架构函数 + * 获取关联的所属模型 * @access public - * @param Model $model 上级模型对象 + * @return Model */ - public function __construct(Model $model) + public function getParent() { - $this->parent = $model; + return $this->parent; } /** - * 获取当前关联信息 + * 获取当前的关联模型类 * @access public - * @param string $name 关联信息 - * @return array|string|integer + * @return string */ - public function getRelationInfo($name = '') + public function getModel() { - $info = [ - 'type' => $this->type, - 'model' => $this->model, - 'middle' => $this->middle, - 'foreignKey' => $this->foreignKey, - 'localKey' => $this->localKey, - 'alias' => $this->alias, - 'joinType' => $this->joinType, - ]; - return $name ? $info[$name] : $info; - } - - // 获取关联数据 - public function getRelation($name) - { - // 执行关联定义方法 - $relation = $this->parent->$name(); - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - - // 判断关联类型执行查询 - switch ($this->type) { - case self::HAS_ONE: - $result = $relation->where($foreignKey, $this->parent->$localKey)->find(); - break; - case self::BELONGS_TO: - $result = $relation->where($localKey, $this->parent->$foreignKey)->find(); - break; - case self::HAS_MANY: - $result = $relation->select(); - break; - case self::HAS_MANY_THROUGH: - $result = $relation->select(); - break; - case self::BELONGS_TO_MANY: - // 关联查询 - $pk = $this->parent->getPk(); - $condition['pivot.' . $localKey] = $this->parent->$pk; - $result = $this->belongsToManyQuery($relation, $this->middle, $foreignKey, $localKey, $condition)->select(); - foreach ($result as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); - } - break; - default: - // 直接返回 - $result = $relation; - } - return $result; + return $this->model; } /** - * 预载入关联查询 返回数据集 + * 获取关联的查询对象 * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 - * @return array + * @return Query */ - public function eagerlyResultSet($resultSet, $relation, $class = '') + public function getQuery() { - /** @var array $relations */ - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); - } - // 执行关联方法 - $model = $this->parent->$relation(); - // 获取关联信息 - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - foreach ($resultSet as $result) { - // 模型关联组装 - $this->match($this->model, $relation, $result); - } - break; - case self::HAS_MANY: - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$localKey)) { - $range[] = $result->$localKey; - } - } - - if (!empty($range)) { - $this->where[$foreignKey] = ['in', $range]; - $data = $this->eagerlyOneToMany($model, [ - $foreignKey => [ - 'in', - $range, - ], - ], $relation, $subRelation, $closure); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); - } - } - break; - case self::BELONGS_TO_MANY: - $pk = $resultSet[0]->getPk(); - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - // 查询关联数据 - $data = $this->eagerlyManyToMany($model, [ - 'pivot.' . $localKey => [ - 'in', - $range, - ], - ], $relation, $subRelation); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); - } - } - break; - } - } - return $resultSet; + return $this->query; } /** * 封装关联数据集 * @access public - * @param array $resultSet 数据集 - * @param string $class 数据集类名 + * @param array $resultSet 数据集 * @return mixed */ - protected function resultSetBuild($resultSet, $class = '') - { - return $class ? new $class($resultSet) : $resultSet; - } - - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 - * @return Model - */ - public function eagerlyResult($result, $relation, $class = '') - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); - } - // 执行关联方法 - $model = $this->parent->$relation(); - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - // 模型关联组装 - $this->match($this->model, $relation, $result); - break; - case self::HAS_MANY: - if (isset($result->$localKey)) { - $data = $this->eagerlyOneToMany($model, [$foreignKey => $result->$localKey], $relation, $subRelation, $closure); - // 关联数据封装 - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); - } - break; - case self::BELONGS_TO_MANY: - $pk = $result->getPk(); - if (isset($result->$pk)) { - $pk = $result->$pk; - // 查询管理数据 - $data = $this->eagerlyManyToMany($model, ['pivot.' . $localKey => $pk], $relation, $subRelation); - - // 关联数据封装 - if (!isset($data[$pk])) { - $data[$pk] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$pk], $class)); - } - break; - - } - } - return $result; - } - - /** - * 一对一 关联模型预查询拼装 - * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 - * @return void - */ - protected function match($model, $relation, &$result) - { - // 重新组装模型数据 - foreach ($result->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ($name == $relation) { - $list[$name][$attr] = $val; - unset($result->$key); - } - } - } - - $result->setAttr($relation, !isset($list[$relation]) ? null : (new $model($list[$relation]))->isUpdate(true)); - } - - /** - * 一对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure - * @return array - */ - protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) - { - $foreignKey = $this->foreignKey; - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - } - $list = $model->where($where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$foreignKey][] = $set; - } - return $data; - } - - /** - * 多对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @return array - */ - protected function eagerlyManyToMany($model, $where, $relation, $subRelation = '') - { - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($model, $this->middle, $foreignKey, $localKey, $where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); - $data[$pivot[$localKey]][] = $set; - } - return $data; - } - - /** - * 设置当前关联定义的数据表别名 - * @access public - * @param array $alias 别名定义 - * @return $this - */ - public function setAlias($alias) - { - $this->alias = $alias; - return $this; - } - - /** - * HAS ONE 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return $this - */ - public function hasOne($model, $foreignKey, $localKey, $alias = [], $joinType = 'INNER') - { - $this->type = self::HAS_ONE; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * BELONGS TO 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $otherKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return $this - */ - public function belongsTo($model, $foreignKey, $otherKey, $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $this->type = self::BELONGS_TO; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $otherKey; - $this->alias = $alias; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * HAS MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return $this - */ - public function hasMany($model, $foreignKey, $localKey, $alias) - { - // 记录当前关联信息 - $this->type = self::HAS_MANY; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * HAS MANY 远程关联定义 - * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $firstkey 关联外键 - * @param string $secondKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return $this - */ - public function hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias) + protected function resultSetBuild($resultSet) { - // 记录当前关联信息 - $this->type = self::HAS_MANY_THROUGH; - $this->model = $model; - $this->middle = $through; - $this->foreignKey = $foreignKey; - $this->throughKey = $throughKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; + return (new $this->model)->toCollection($resultSet); } /** - * BELONGS TO MANY 关联定义 + * 移除关联查询参数 * @access public - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 * @return $this */ - public function belongsToMany($model, $table, $foreignKey, $localKey, $alias) + public function removeOption() { - // 记录当前关联信息 - $this->type = self::BELONGS_TO_MANY; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->middle = $table; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 + $this->query->removeOption(); return $this; } /** - * BELONGS TO MANY 关联查询 - * @access public - * @param object $model 关联模型对象 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 - * @return \think\db\Query|string - */ - protected function belongsToManyQuery($model, $table, $foreignKey, $localKey, $condition = []) - { - // 关联查询封装 - $tableName = $model->getTable(); - $relationFk = $model->getPk(); - return $model::field($tableName . '.*') - ->field(true, false, $table, 'pivot', 'pivot__') - ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) - ->where($condition); - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function save($data, array $pivot = []) - { - // 判断关联类型 - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - case self::HAS_MANY: - if ($data instanceof Model) { - $data = $data->getData(); - } - // 保存关联表数据 - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $model = new $this->model; - return $model->save($data); - case self::BELONGS_TO_MANY: - // 保存关联表/中间表数据 - return $this->attach($data, $pivot); - } - } - - /** - * 批量保存当前关联数据对象 - * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function saveAll(array $dataSet, array $pivot = []) - { - $result = false; - foreach ($dataSet as $key => $data) { - // 判断关联类型 - switch ($this->type) { - case self::HAS_MANY: - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $result = $this->save($data); - break; - case self::BELONGS_TO_MANY: - // TODO - $result = $this->attach($data, !empty($pivot) ? $pivot[$key] : []); - break; - } - } - return $result; - } - - /** - * 附加关联的一个中间表数据 - * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function attach($data, $pivot = []) - { - if (is_array($data)) { - // 保存关联表数据 - $model = new $this->model; - $model->save($data); - $id = $model->getLastInsID(); - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - - if ($id) { - // 保存中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - $pivot[$this->foreignKey] = $id; - $query = clone $this->parent->db(); - return $query->table($this->middle)->insert($pivot); - } else { - throw new Exception('miss relation data'); - } - } - - /** - * 解除关联的一个中间表数据 - * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 - * @return integer + * 执行基础查询(进执行一次) + * @access protected + * @return void */ - public function detach($data, $relationDel = false) - { - if (is_array($data)) { - $id = $data; - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - // 删除中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - if (isset($id)) { - $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; - } - $query = clone $this->parent->db(); - $query->table($this->middle)->where($pivot)->delete(); - - // 删除关联表数据 - if (isset($id) && $relationDel) { - $model = $this->model; - $model::destroy($id); - } - } + abstract protected function baseQuery(); public function __call($method, $args) { if ($this->query) { - switch ($this->type) { - case self::HAS_MANY: - if (isset($this->where)) { - $this->query->where($this->where); - } elseif (isset($this->parent->{$this->localKey})) { - // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); - } - break; - case self::HAS_MANY_THROUGH: - $through = $this->middle; - $model = $this->model; - $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); - $throughTable = $through::getTable(); - $pk = (new $this->model)->getPk(); - $throughKey = $this->throughKey; - $modelTable = $this->parent->getTable(); - $this->query->field($alias . '.*')->alias($alias) - ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) - ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) - ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); - break; - case self::BELONGS_TO_MANY: - // TODO + // 执行基础查询 + $this->baseQuery(); - } $result = call_user_func_array([$this->query, $method], $args); - if ($result instanceof \think\db\Query) { + if ($result instanceof Query) { + $this->option = $result->getOptions(); return $this; } else { + $this->option = []; + $this->baseQuery = false; return $result; } } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } } - } diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php new file mode 100644 index 00000000..d1c4b9bd --- /dev/null +++ b/library/think/model/relation/BelongsTo.php @@ -0,0 +1,132 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +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类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->query->where($this->localKey, $this->parent->$foreignKey)->relation($subRelation)->find(); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyWhere($this, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + // 设置关联属性 + $result->setAttr($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @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; + $data = $this->eagerlyWhere($this, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + } + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + // 设置关联属性 + $result->setAttr(Loader::parseName($relation), $relationModel); + } + +} diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php new file mode 100644 index 00000000..c85a8c0c --- /dev/null +++ b/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,360 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; + +class BelongsToMany extends Relation +{ + // 中间表模型 + protected $middle; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->middle = $table; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $middle = $this->middle; + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + // 关联查询 + $pk = $this->parent->getPk(); + $condition['pivot.' . $localKey] = $this->parent->$pk; + $result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->relation($subRelation)->select(); + foreach ($result as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->pivot = new Pivot($pivot, $this->middle); + } + return $result; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + 'pivot.' . $localKey => [ + 'in', + $range, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + return $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk() + ] + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->pivot = new Pivot($pivot, $this->middle); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $table 中间表名 + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($table, $foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $relationFk = $this->query->getPk(); + return $this->query->field($tableName . '.*') + ->field(true, false, $table, 'pivot', 'pivot__') + ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return int + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $result = $this->query->table($this->middle)->insert($pivot, true); + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->query->table($this->middle)->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + $pk = $this->parent->getPk(); + $this->query->join($this->middle . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php new file mode 100644 index 00000000..3094bb55 --- /dev/null +++ b/library/think/model/relation/HasMany.php @@ -0,0 +1,272 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + $result->setAttr($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $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(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + + return $this->query->where([ + $this->foreignKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk() + ] + ])->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [& $model]); + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + $table = $this->query->getTable(); + return $this->parent->db()->alias('a') + ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $this->joinType) + ->group('b.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $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]); + } + } + } + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + } + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php new file mode 100644 index 00000000..e1518972 --- /dev/null +++ b/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 架构函数 + * @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->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param string $class 数据集对象名 为空表示数组 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param string $class 数据集对象名 为空表示数组 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + $through = $this->through; + $model = $this->model; + $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); + $throughTable = $through::getTable(); + $pk = (new $this->model)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php new file mode 100644 index 00000000..2c0cf350 --- /dev/null +++ b/library/think/model/relation/HasOne.php @@ -0,0 +1,159 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + // 判断关联类型执行查询 + return $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $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]); + } + } + } + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyWhere($this, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + } + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + // 设置关联属性 + $result->setAttr($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @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; + $data = $this->eagerlyWhere($this, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + $result->setAttr(Loader::parseName($relation), $relationModel); + } + +} diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php new file mode 100644 index 00000000..3fc8179c --- /dev/null +++ b/library/think/model/relation/MorphMany.php @@ -0,0 +1,239 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type + ], $relation, $subRelation, $closure); + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $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; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + if ($closure) { + call_user_func_array($closure, [& $this->query]); + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk() + ], + $this->morphType => $this->type + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [& $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + $model = new $this->model; + return $model->save($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php new file mode 100644 index 00000000..5e1867f2 --- /dev/null +++ b/library/think/model/relation/MorphTo.php @@ -0,0 +1,198 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = []) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + // 主键数据 + $pk = $this->parent->$morphKey; + return (new $model)->relation($subRelation)->find($pk); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $result->setAttr($attr, $data[$result->$morphKey]); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->isUpdate(true); + } + $result->setAttr(Loader::parseName($relation), $data ?: null); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + } +} diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php new file mode 100644 index 00000000..18b92225 --- /dev/null +++ b/library/think/model/relation/OneToOne.php @@ -0,0 +1,316 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', $query->getModel()))); + $alias = $name; + if ($first) { + $table = $query->getTable(); + $query->table([$table => $alias]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $alias); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [& $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } else { + $field = true; + } + $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + $this->removeOption(); + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } + $result->setAttr(Loader::parseName($relation), !isset($relationModel) ? null : $relationModel->isUpdate(true)); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model->$attr); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [& $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + } +} diff --git a/library/think/paginator/driver/Bootstrap.php b/library/think/paginator/driver/Bootstrap.php index 87e1fe48..58fa9436 100644 --- a/library/think/paginator/driver/Bootstrap.php +++ b/library/think/paginator/driver/Bootstrap.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -102,7 +102,6 @@ class Bootstrap extends Paginator return $html; } - /** * 渲染分页html * @return mixed @@ -127,7 +126,6 @@ class Bootstrap extends Paginator } } - /** * 生成一个可点击的按钮 * @@ -204,4 +202,4 @@ class Bootstrap extends Paginator return $this->getAvailablePageWrapper($url, $page); } -} \ No newline at end of file +} diff --git a/library/think/process/Builder.php b/library/think/process/Builder.php index 826e6745..da561639 100644 --- a/library/think/process/Builder.php +++ b/library/think/process/Builder.php @@ -15,10 +15,9 @@ use think\Process; class Builder { - private $arguments; private $cwd; - private $env = null; + private $env = null; private $input; private $timeout = 60; private $options = []; @@ -155,7 +154,7 @@ class Builder return $this; } - $timeout = (float)$timeout; + $timeout = (float) $timeout; if ($timeout < 0) { throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); @@ -231,4 +230,4 @@ class Builder return $process; } -} \ No newline at end of file +} diff --git a/library/think/process/Utils.php b/library/think/process/Utils.php index 3ed136a5..f94c6488 100644 --- a/library/think/process/Utils.php +++ b/library/think/process/Utils.php @@ -60,7 +60,7 @@ class Utils return $input; } if (is_scalar($input)) { - return (string)$input; + return (string) $input; } throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); } @@ -72,4 +72,4 @@ class Utils return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; } -} \ No newline at end of file +} diff --git a/library/think/process/exception/Failed.php b/library/think/process/exception/Failed.php new file mode 100644 index 00000000..52950823 --- /dev/null +++ b/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/library/think/process/exception/Timeout.php b/library/think/process/exception/Timeout.php index c085ee8e..d5f1162f 100644 --- a/library/think/process/exception/Timeout.php +++ b/library/think/process/exception/Timeout.php @@ -58,4 +58,4 @@ class Timeout extends \RuntimeException throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); } } -} \ No newline at end of file +} diff --git a/library/think/process/pipes/Pipes.php b/library/think/process/pipes/Pipes.php index 51da44f3..82396b8f 100644 --- a/library/think/process/pipes/Pipes.php +++ b/library/think/process/pipes/Pipes.php @@ -53,7 +53,6 @@ abstract class Pipes */ abstract public function areOpen(); - /** * {@inheritdoc} */ @@ -91,4 +90,4 @@ abstract class Pipes $this->blocked = false; } -} \ No newline at end of file +} diff --git a/library/think/process/pipes/Unix.php b/library/think/process/pipes/Unix.php index abfe61b2..fd99a5d6 100644 --- a/library/think/process/pipes/Unix.php +++ b/library/think/process/pipes/Unix.php @@ -25,14 +25,14 @@ class Unix extends Pipes public function __construct($ttyMode, $ptyMode, $input, $disableOutput) { - $this->ttyMode = (bool)$ttyMode; - $this->ptyMode = (bool)$ptyMode; - $this->disableOutput = (bool)$disableOutput; + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; if (is_resource($input)) { $this->input = $input; } else { - $this->inputBuffer = (string)$input; + $this->inputBuffer = (string) $input; } } @@ -134,12 +134,12 @@ class Unix extends Pipes $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; $data = ''; - while ('' !== $dataread = (string)fread($pipe, self::CHUNK_SIZE)) { + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { $data .= $dataread; } if ('' !== $data) { - if ($type === 'input') { + if ('input' === $type) { $this->inputBuffer .= $data; } else { $read[$type] = $data; @@ -147,7 +147,7 @@ class Unix extends Pipes } if (false === $data || (true === $close && feof($pipe) && '' === $data)) { - if ($type === 'input') { + if ('input' === $type) { $this->input = null; } else { fclose($this->pipes[$type]); @@ -160,7 +160,7 @@ class Unix extends Pipes while (strlen($this->inputBuffer)) { $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k if ($written > 0) { - $this->inputBuffer = (string)substr($this->inputBuffer, $written); + $this->inputBuffer = (string) substr($this->inputBuffer, $written); } else { break; } @@ -180,7 +180,7 @@ class Unix extends Pipes */ public function areOpen() { - return (bool)$this->pipes; + return (bool) $this->pipes; } /** @@ -193,4 +193,4 @@ class Unix extends Pipes { return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); } -} \ No newline at end of file +} diff --git a/library/think/process/pipes/Windows.php b/library/think/process/pipes/Windows.php index 2cc44ca7..bba7e9b8 100644 --- a/library/think/process/pipes/Windows.php +++ b/library/think/process/pipes/Windows.php @@ -30,7 +30,7 @@ class Windows extends Pipes public function __construct($disableOutput, $input) { - $this->disableOutput = (bool)$disableOutput; + $this->disableOutput = (bool) $disableOutput; if (!$this->disableOutput) { @@ -128,7 +128,7 @@ class Windows extends Pipes */ public function areOpen() { - return (bool)$this->pipes && (bool)$this->fileHandles; + return (bool) $this->pipes && (bool) $this->fileHandles; } /** @@ -213,7 +213,7 @@ class Windows extends Pipes while (strlen($this->inputBuffer)) { $written = fwrite($w[0], $this->inputBuffer, 2 << 18); if ($written > 0) { - $this->inputBuffer = (string)substr($this->inputBuffer, $written); + $this->inputBuffer = (string) substr($this->inputBuffer, $written); } else { break; } @@ -225,4 +225,4 @@ class Windows extends Pipes unset($this->pipes[0]); } } -} \ No newline at end of file +} diff --git a/library/think/response/Json.php b/library/think/response/Json.php index a137f453..538fc5a0 100644 --- a/library/think/response/Json.php +++ b/library/think/response/Json.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -27,17 +27,25 @@ class Json extends Response * @access protected * @param mixed $data 要处理的数据 * @return mixed + * @throws \Exception */ protected function output($data) { - // 返回JSON数据格式到客户端 包含状态信息 - $data = json_encode($data, $this->options['json_encode_param']); - - if ($data === false) { - throw new \InvalidArgumentException(json_last_error_msg()); + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; } - - return $data; } } diff --git a/library/think/response/Jsonp.php b/library/think/response/Jsonp.php index fda1183a..de8fb304 100644 --- a/library/think/response/Jsonp.php +++ b/library/think/response/Jsonp.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -30,21 +30,29 @@ class Jsonp extends Response * @access protected * @param mixed $data 要处理的数据 * @return mixed + * @throws \Exception */ protected function output($data) { - // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); - $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; - - $data = json_encode($data, $this->options['json_encode_param']); - - if ($data === false) { - throw new \InvalidArgumentException(json_last_error_msg()); + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; } - - $data = $handler . '(' . $data . ');'; - return $data; } } diff --git a/library/think/response/Redirect.php b/library/think/response/Redirect.php index 82a5fe32..f2002779 100644 --- a/library/think/response/Redirect.php +++ b/library/think/response/Redirect.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -53,10 +53,10 @@ class Redirect extends Response { if (is_array($name)) { foreach ($name as $key => $val) { - Session::set($key, $val); + Session::flash($key, $val); } } else { - Session::set($name, $value); + Session::flash($name, $value); } return $this; } @@ -78,14 +78,17 @@ class Redirect extends Response /** * 记住当前url后跳转 + * @return $this */ public function remember() { Session::set('redirect_url', Request::instance()->url()); + return $this; } /** * 跳转到上次记住的url + * @return $this */ public function restore() { @@ -93,5 +96,6 @@ class Redirect extends Response $this->data = Session::get('redirect_url'); Session::delete('redirect_url'); } + return $this; } } diff --git a/library/think/response/View.php b/library/think/response/View.php index 3a5f3660..de75515a 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/response/Xml.php b/library/think/response/Xml.php index 3a2c59bd..ca12e295 100644 --- a/library/think/response/Xml.php +++ b/library/think/response/Xml.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/session/driver/Memcache.php b/library/think/session/driver/Memcache.php index 2c040271..0c02e23e 100644 --- a/library/think/session/driver/Memcache.php +++ b/library/think/session/driver/Memcache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -79,7 +79,7 @@ class Memcache extends SessionHandler */ public function read($sessID) { - return $this->handler->get($this->config['session_name'] . $sessID); + return (string) $this->handler->get($this->config['session_name'] . $sessID); } /** @@ -87,6 +87,7 @@ class Memcache extends SessionHandler * @access public * @param string $sessID * @param String $sessData + * @return bool */ public function write($sessID, $sessData) { @@ -97,6 +98,7 @@ class Memcache extends SessionHandler * 删除Session * @access public * @param string $sessID + * @return bool */ public function destroy($sessID) { @@ -107,6 +109,7 @@ class Memcache extends SessionHandler * Session 垃圾回收 * @access public * @param string $sessMaxLifeTime + * @return true */ public function gc($sessMaxLifeTime) { diff --git a/library/think/session/driver/Memcached.php b/library/think/session/driver/Memcached.php index 4187204a..bcaf8a1b 100644 --- a/library/think/session/driver/Memcached.php +++ b/library/think/session/driver/Memcached.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -87,7 +87,7 @@ class Memcached extends SessionHandler */ public function read($sessID) { - return $this->handler->get($this->config['session_name'] . $sessID); + return (string) $this->handler->get($this->config['session_name'] . $sessID); } /** @@ -95,6 +95,7 @@ class Memcached extends SessionHandler * @access public * @param string $sessID * @param String $sessData + * @return bool */ public function write($sessID, $sessData) { @@ -105,6 +106,7 @@ class Memcached extends SessionHandler * 删除Session * @access public * @param string $sessID + * @return bool */ public function destroy($sessID) { @@ -115,6 +117,7 @@ class Memcached extends SessionHandler * Session 垃圾回收 * @access public * @param string $sessMaxLifeTime + * @return true */ public function gc($sessMaxLifeTime) { diff --git a/library/think/session/driver/Redis.php b/library/think/session/driver/Redis.php index e3dc9983..a4c2b54a 100644 --- a/library/think/session/driver/Redis.php +++ b/library/think/session/driver/Redis.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -81,11 +81,11 @@ class Redis extends SessionHandler * 读取Session * @access public * @param string $sessID - * @return bool|string + * @return string */ public function read($sessID) { - return $this->handler->get($this->config['session_name'] . $sessID); + return (string) $this->handler->get($this->config['session_name'] . $sessID); } /** @@ -108,11 +108,11 @@ class Redis extends SessionHandler * 删除Session * @access public * @param string $sessID - * @return bool|void + * @return bool */ public function destroy($sessID) { - $this->handler->delete($this->config['session_name'] . $sessID); + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; } /** diff --git a/library/think/template/TagLib.php b/library/think/template/TagLib.php index 101593bf..b3325191 100644 --- a/library/think/template/TagLib.php +++ b/library/think/template/TagLib.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/template/driver/File.php b/library/think/template/driver/File.php index 1cd041af..b27e7265 100644 --- a/library/think/template/driver/File.php +++ b/library/think/template/driver/File.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/template/taglib/Cx.php b/library/think/template/taglib/Cx.php index b15c7bba..af7a54c8 100644 --- a/library/think/template/taglib/Cx.php +++ b/library/think/template/taglib/Cx.php @@ -98,7 +98,7 @@ class Cx extends Taglib $name = $this->autoBuildVar($name); } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection): $' . $key . ' = 0;'; + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; @@ -158,7 +158,7 @@ class Cx extends Taglib } else { $name = $this->autoBuildVar($name); } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection): '; + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { if (!isset($var)) { @@ -431,7 +431,7 @@ class Cx extends Taglib { $name = $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty())): ?>' . $content . ''; + $parseStr = 'isEmpty())): ?>' . $content . ''; return $parseStr; } @@ -448,7 +448,7 @@ class Cx extends Taglib { $name = $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty()))): ?>' . $content . ''; + $parseStr = 'isEmpty()))): ?>' . $content . ''; return $parseStr; } @@ -620,8 +620,8 @@ class Cx extends Taglib return $parseStr; } - /** - * U函数的tag标签 + /** + * url函数的tag标签 * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} * @access public * @param array $tag 标签属性 diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 31fe7807..468d3611 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -21,6 +21,8 @@ class Php { // 模板引擎参数 protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', // 模板起始路径 'view_path' => '', // 模板文件后缀 @@ -109,25 +111,34 @@ class Php $this->config['view_path'] = App::$modulePath . 'view' . DS; } + $request = Request::instance(); + // 获取视图根目录 if (strpos($template, '@')) { + // 跨模块调用 list($module, $template) = explode('@', $template); - $path = APP_PATH . $module . DS . 'view' . DS; + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); } else { - $path = $this->config['view_path']; + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; } - // 分析模板文件规则 - $request = Request::instance(); - $controller = Loader::parseName($request->controller()); - if ($controller && 0 !== strpos($template, '/')) { - $depr = $this->config['view_depr']; - $template = str_replace(['/', ':'], $depr, $template); - if ('' == $template) { - // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . $request->action(); - } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . $request->action(); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); } return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 33729b44..39a14ca3 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -24,6 +24,8 @@ class Think private $template; // 模板引擎参数 protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', // 模板起始路径 'view_path' => '', // 模板文件后缀 @@ -103,28 +105,35 @@ class Think */ private function parseTemplate($template) { + // 分析模板文件规则 + $request = Request::instance(); // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); - $path = APP_PATH . $module . DS . 'view' . DS; + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); } else { - // 当前视图目录 - $path = $this->config['view_path']; + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; } - // 分析模板文件规则 - $request = Request::instance(); - $controller = Loader::parseName($request->controller()); - if ($controller && 0 !== strpos($template, '/')) { - $depr = $this->config['view_depr']; - $template = str_replace(['/', ':'], $depr, $template); - if ('' == $template) { - // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . $request->action(); - } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . $request->action(); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); } return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } diff --git a/library/traits/controller/Jump.php b/library/traits/controller/Jump.php index 0fad996d..51f4281f 100644 --- a/library/traits/controller/Jump.php +++ b/library/traits/controller/Jump.php @@ -81,7 +81,7 @@ trait Jump $msg = ''; } if (is_null($url)) { - $url = 'javascript:history.back(-1);'; + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; } elseif ('' !== $url) { $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Url::build($url); } diff --git a/library/traits/model/SoftDelete.php b/library/traits/model/SoftDelete.php index e08d96aa..8781544f 100644 --- a/library/traits/model/SoftDelete.php +++ b/library/traits/model/SoftDelete.php @@ -2,6 +2,8 @@ namespace traits\model; +use think\db\Query; + trait SoftDelete { @@ -22,24 +24,25 @@ trait SoftDelete /** * 查询软删除数据 * @access public - * @return \think\db\Query + * @return Query */ public static function withTrashed() { $model = new static(); - return $model->db(); + $field = $model->getDeleteTimeField(true); + return $model->db(false)->removeWhereField($field); } /** * 只查询软删除数据 * @access public - * @return \think\db\Query + * @return Query */ public static function onlyTrashed() { $model = new static(); - $field = $model->getDeleteTimeField(); - return $model->db()->where($field, 'exp', 'is not null'); + $field = $model->getDeleteTimeField(true); + return $model->db(false)->where($field, 'exp', 'is not null'); } /** @@ -60,7 +63,7 @@ trait SoftDelete $this->data[$name] = $this->autoWriteTimestamp($name); $result = $this->isUpdate()->save(); } else { - $result = $this->db()->delete($this->data); + $result = $this->db(false)->delete($this->data); } $this->trigger('after_delete', $this); @@ -76,8 +79,8 @@ trait SoftDelete */ public static function destroy($data, $force = false) { - $model = new static(); - $query = $model->db(); + // 包含软删除数据 + $query = self::withTrashed(); if (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; @@ -108,15 +111,19 @@ trait SoftDelete public function restore($where = []) { $name = $this->getDeleteTimeField(); + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + $where[$name] = ['not null', '']; + } // 恢复删除 - return $this->isUpdate()->save([$name => null], $where); - + return $this->db(false)->removeWhereField($this->getDeleteTimeField(true))->where($where)->update([$name => null]); } /** * 查询默认不包含软删除数据 * @access protected - * @param \think\db\Query $query 查询对象 + * @param Query $query 查询对象 * @return void */ protected function base($query) @@ -133,13 +140,13 @@ trait SoftDelete */ protected function getDeleteTimeField($read = false) { - if (isset($this->deleteTime)) { - $field = $this->deleteTime; - } else { - $field = 'delete_time'; + $field = isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + if (!strpos($field, '.')) { + $field = $this->db(false)->getTable() . '.' . $field; } if (!$read && strpos($field, '.')) { - list($alias, $field) = explode('.', $field); + $array = explode('.', $field); + $field = array_pop($array); } return $field; } diff --git a/library/traits/think/Instance.php b/library/traits/think/Instance.php index 206a9412..ba45ddd8 100644 --- a/library/traits/think/Instance.php +++ b/library/traits/think/Instance.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,7 @@ namespace traits\think; -use \think\Exception; +use think\Exception; trait Instance { diff --git a/start.php b/start.php index 2d517914..8af62ef3 100644 --- a/start.php +++ b/start.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/tests/thinkphp/library/think/appTest.php b/tests/thinkphp/library/think/appTest.php index 366b4f55..016bbf9c 100644 --- a/tests/thinkphp/library/think/appTest.php +++ b/tests/thinkphp/library/think/appTest.php @@ -16,7 +16,6 @@ namespace tests\thinkphp\library\think; -use ReflectionClass; use think\App; use think\Config; use think\Request; diff --git a/tests/thinkphp/library/think/behavior/One.php b/tests/thinkphp/library/think/behavior/One.php index ae8fa49d..1ec6e66e 100644 --- a/tests/thinkphp/library/think/behavior/One.php +++ b/tests/thinkphp/library/think/behavior/One.php @@ -3,12 +3,12 @@ namespace tests\thinkphp\library\think\behavior; class One { - public function run(&$data){ + public function run(&$data) { $data['id'] = 1; return true; } - public function test(&$data){ + public function test(&$data) { $data['name'] = 'test'; return false; } diff --git a/tests/thinkphp/library/think/behavior/Three.php b/tests/thinkphp/library/think/behavior/Three.php index 0f2e0495..c98b7aeb 100644 --- a/tests/thinkphp/library/think/behavior/Three.php +++ b/tests/thinkphp/library/think/behavior/Three.php @@ -3,7 +3,7 @@ namespace tests\thinkphp\library\think\behavior; class Three { - public function run(&$data){ + public function run(&$data) { $data['id'] = 3; } } diff --git a/tests/thinkphp/library/think/behavior/Two.php b/tests/thinkphp/library/think/behavior/Two.php index 3e194bf5..1e52a2b5 100644 --- a/tests/thinkphp/library/think/behavior/Two.php +++ b/tests/thinkphp/library/think/behavior/Two.php @@ -3,7 +3,7 @@ namespace tests\thinkphp\library\think\behavior; class Two { - public function run(&$data){ + public function run(&$data) { $data['id'] = 2; } } diff --git a/tests/thinkphp/library/think/controllerTest.php b/tests/thinkphp/library/think/controllerTest.php index ce5f49a0..fba8d6c5 100644 --- a/tests/thinkphp/library/think/controllerTest.php +++ b/tests/thinkphp/library/think/controllerTest.php @@ -40,13 +40,13 @@ class Foo extends Controller public function fetchTest() { - $template = dirname(__FILE__) . '/display.html'; - return $this->fetch($template, ['name' => 'ThinkPHP']); + $template = dirname(__FILE__) . '/display.html'; + return $this->fetch($template, ['name' => 'ThinkPHP']); } public function displayTest() { - $template = dirname(__FILE__) . '/display.html'; + $template = dirname(__FILE__) . '/display.html'; return $this->display($template, ['name' => 'ThinkPHP']); } public function test() @@ -71,7 +71,7 @@ class Foo extends Controller ['sex', 'in:0,1', '性别只能为为男或女'], ['age', 'between:1,80', '年龄只能在10-80之间'], ]; - return $this->validate($data, $validate); + return $this->validate($data, $validate); } } diff --git a/tests/thinkphp/library/think/dbTest.php b/tests/thinkphp/library/think/dbTest.php index 9019917e..4d3d16d5 100644 --- a/tests/thinkphp/library/think/dbTest.php +++ b/tests/thinkphp/library/think/dbTest.php @@ -11,17 +11,342 @@ /** * Db类测试 + * @author: 刘志淳 */ namespace tests\thinkphp\library\think; -use \think\Db; +use think\Db; class dbTest extends \PHPUnit_Framework_TestCase { + // 获取测试数据库配置 + private function getConfig() + { + return [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'tp_', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 array 数组 collection Collection对象 + 'resultset_type' => 'array', + // 是否自动写入时间戳字段 + 'auto_timestamp' => false, + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ]; + } + + // 获取创建数据库 SQL + private function getCreateTableSql() + { + $sql[] = <<execute('show databases'); + $config = $this->getConfig(); + $result = Db::connect($config)->execute('show databases'); + $this->assertNotEmpty($result); + } + + public function testExecute() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + foreach ($sql as $one) { + Db::connect($config)->execute($one); + } + $tableNum = Db::connect($config)->execute("show tables;"); + $this->assertEquals(4, $tableNum); + } + + public function testQuery() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + Db::connect($config)->batchQuery($sql); + + $tableQueryResult = Db::connect($config)->query("show tables;"); + + $this->assertTrue(is_array($tableQueryResult)); + + $tableNum = count($tableQueryResult); + $this->assertEquals(4, $tableNum); + } + + public function testBatchQuery() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + Db::connect($config)->batchQuery($sql); + + $tableNum = Db::connect($config)->execute("show tables;"); + $this->assertEquals(4, $tableNum); + } + + public function testTable() + { + $config = $this->getConfig(); + $tableName = 'tp_user'; + $result = Db::connect($config)->table($tableName); + $this->assertEquals($tableName, $result->getOptions()['table']); + } + + public function testName() + { + $config = $this->getConfig(); + $tableName = 'user'; + $result = Db::connect($config)->name($tableName); + $this->assertEquals($config['prefix'] . $tableName, $result->getOptions()['table']); + } + + public function testInsert() + { + $config = $this->getConfig(); + $data = [ + 'username' => 'chunice', + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time() + ]; + $result = Db::connect($config)->name('user')->insert($data); + $this->assertEquals(1, $result); + } + + public function testUpdate() + { + $config = $this->getConfig(); + $data = [ + 'username' => 'chunice_update', + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time() + ]; + $result = Db::connect($config)->name('user')->where('username', 'chunice')->update($data); + $this->assertEquals(1, $result); + } + + public function testFind() + { + $config = $this->getConfig(); + $mustFind = Db::connect($config)->name('user')->where('username', 'chunice_update')->find(); + $this->assertNotEmpty($mustFind); + $mustNotFind = Db::connect($config)->name('user')->where('username', 'chunice')->find(); + $this->assertEmpty($mustNotFind); + } + + public function testInsertAll() + { + $config = $this->getConfig(); + + $data = [ + ['username' => 'foo', 'password' => md5('foo'), 'status' => 1, 'create_time' => time()], + ['username' => 'bar', 'password' => md5('bar'), 'status' => 1, 'create_time' => time()] + ]; + + $insertNum = Db::connect($config)->name('user')->insertAll($data); + $this->assertEquals(count($data), $insertNum); + } + + public function testSelect() + { + $config = $this->getConfig(); + $mustFound = Db::connect($config)->name('user')->where('status', 1)->select(); + $this->assertNotEmpty($mustFound); + $mustNotFound = Db::connect($config)->name('user')->where('status', 0)->select(); + $this->assertEmpty($mustNotFound); + } + + public function testValue() + { + $config = $this->getConfig(); + $username = Db::connect($config)->name('user')->where('id', 1)->value('username'); + $this->assertEquals('chunice_update', $username); + $usernameNull = Db::connect($config)->name('user')->where('id', 0)->value('username'); + $this->assertEmpty($usernameNull); + } + + public function testColumn() + { + $config = $this->getConfig(); + $username = Db::connect($config)->name('user')->where('status', 1)->column('username'); + $this->assertNotEmpty($username); + $usernameNull = Db::connect($config)->name('user')->where('status', 0)->column('username'); + $this->assertEmpty($usernameNull); + + } + + public function testInsertGetId() + { + $config = $this->getConfig(); + $id = Db::connect($config)->name('user')->order('id', 'desc')->value('id'); + + $data = [ + 'username' => uniqid(), + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time() + ]; + $lastId = Db::connect($config)->name('user')->insertGetId($data); + $this->assertEquals($id + 1, $lastId); + + } + + public function testGetLastInsId() + { + $config = $this->getConfig(); + $data = [ + 'username' => uniqid(), + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time() + ]; + $lastId = Db::connect($config)->name('user')->insertGetId($data); + + $lastInsId = Db::connect($config)->name('user')->getLastInsID(); + $this->assertEquals($lastId, $lastInsId); + } + + public function testSetField() + { + $config = $this->getConfig(); + + $setFieldNum = Db::connect($config)->name('user')->where('id', 1)->setField('username', 'chunice_setField'); + $this->assertEquals(1, $setFieldNum); + + $setFieldNum = Db::connect($config)->name('user')->where('id', 1)->setField('username', 'chunice_setField'); + $this->assertEquals(0, $setFieldNum); + } + + public function testSetInc() + { + $config = $this->getConfig(); + $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + Db::connect($config)->name('user')->where('id', 1)->setInc('create_time'); + $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + $this->assertEquals($originCreateTime + 1, $newCreateTime); + + } + + public function testSetDec() + { + $config = $this->getConfig(); + $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + Db::connect($config)->name('user')->where('id', 1)->setDec('create_time'); + $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + $this->assertEquals($originCreateTime - 1, $newCreateTime); + } + + public function testDelete() + { + $config = $this->getConfig(); + Db::connect($config)->name('user')->where('id', 1)->delete(); + $result = Db::connect($config)->name('user')->where('id', 1)->find(); + $this->assertEmpty($result); + } + + public function testChunk() + { + // todo 暂未想到测试方法 + } + + public function testCache() + { + $config = $this->getConfig(); + $result = Db::connect($config)->name('user')->where('id', 1)->cache('key', 60)->select(); + $cache = \think\Cache::get('key'); + $this->assertEquals($result, $cache); + + $updateCache = Db::connect($config)->name('user')->cache('key')->find(2); + $this->assertNotEquals($cache, $updateCache); } } diff --git a/tests/thinkphp/library/think/debugTest.php b/tests/thinkphp/library/think/debugTest.php index 12ff815f..683611e9 100644 --- a/tests/thinkphp/library/think/debugTest.php +++ b/tests/thinkphp/library/think/debugTest.php @@ -118,7 +118,7 @@ class debugTest extends \PHPUnit_Framework_TestCase { $useMem = Debug::getUseMem(); - $this->assertLessThan(30, explode(" ", $useMem)[0]); + $this->assertLessThan(35, explode(" ", $useMem)[0]); } /** @@ -161,7 +161,7 @@ class debugTest extends \PHPUnit_Framework_TestCase public function testDump() { if (strstr(PHP_VERSION, 'hhvm')) { - return ; + return; } $var = []; @@ -170,7 +170,7 @@ class debugTest extends \PHPUnit_Framework_TestCase $array = explode("array", json_encode($output)); if (IS_WIN) { $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\r\\n\"", end($array)); - } else if (strstr(PHP_OS, 'Darwin')) { + } elseif (strstr(PHP_OS, 'Darwin')) { $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); } else { $this->assertEquals("(1) {\\n 'key' =>\\n string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); diff --git a/tests/thinkphp/library/think/lang/lang.php b/tests/thinkphp/library/think/lang/lang.php index 044b171b..96880b15 100644 --- a/tests/thinkphp/library/think/lang/lang.php +++ b/tests/thinkphp/library/think/lang/lang.php @@ -1,4 +1,4 @@ '加载', -]; \ No newline at end of file +]; diff --git a/tests/thinkphp/library/think/paginateTest.php b/tests/thinkphp/library/think/paginateTest.php index 5bbcc961..8cd45507 100644 --- a/tests/thinkphp/library/think/paginateTest.php +++ b/tests/thinkphp/library/think/paginateTest.php @@ -11,7 +11,6 @@ namespace tests\thinkphp\library\think; - use think\paginator\driver\Bootstrap; class paginateTest extends \PHPUnit_Framework_TestCase @@ -38,7 +37,4 @@ class paginateTest extends \PHPUnit_Framework_TestCase $this->assertEquals($render, $p->render()); } - - - -} \ No newline at end of file +} diff --git a/tests/thinkphp/library/think/requestTest.php b/tests/thinkphp/library/think/requestTest.php index 964b5c85..7d89fa36 100644 --- a/tests/thinkphp/library/think/requestTest.php +++ b/tests/thinkphp/library/think/requestTest.php @@ -90,12 +90,14 @@ class requestTest extends \PHPUnit_Framework_TestCase public function testType() { - $request = Request::instance(); - $_SERVER['HTTP_ACCEPT'] = 'application/json'; + $request = Request::instance(); + $request->server(['HTTP_ACCEPT' => 'application/json']); + $this->assertEquals('json', $request->type()); $request->mimeType('test', 'application/test'); $request->mimeType(['test' => 'application/test']); - $_SERVER['HTTP_ACCEPT'] = 'application/test'; + $request->server(['HTTP_ACCEPT' => 'application/test']); + $this->assertEquals('test', $request->type()); } @@ -103,13 +105,13 @@ class requestTest extends \PHPUnit_Framework_TestCase { $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'DELETE'; - $request = new Request(); + $request = Request::create('', ''); $this->assertEquals('DELETE', $request->method()); $this->assertEquals('GET', $request->method(true)); Config::set('var_method', '_method'); $_POST['_method'] = 'POST'; - $request = new Request(); + $request = Request::create('', ''); $this->assertEquals('POST', $request->method()); $this->assertEquals('GET', $request->method(true)); $this->assertTrue($request->isPost()); @@ -130,7 +132,7 @@ class requestTest extends \PHPUnit_Framework_TestCase public function testVar() { Config::set('app_multi_module', true); - $request = new Request(); + $request = Request::create(''); $request->route(['name' => 'thinkphp', 'id' => 6]); $request->get(['id' => 10]); $request->post(['id' => 8]); @@ -156,28 +158,28 @@ class requestTest extends \PHPUnit_Framework_TestCase public function testIsAjax() { - $request = new Request(); + $request = Request::create(''); $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest'; $this->assertTrue($request->isAjax()); } public function testIsPjax() { - $request = new Request(); + $request = Request::create(''); $_SERVER['HTTP_X_PJAX'] = true; $this->assertTrue($request->isPjax()); } public function testIsMobile() { - $request = new Request(); + $request = Request::create(''); $_SERVER['HTTP_VIA'] = 'wap'; $this->assertTrue($request->isMobile()); } public function testBind() { - $request = new Request(); + $request = Request::create(''); $request->user = 'User1'; $request->bind(['user' => 'User2']); $this->assertEquals('User2', $request->user); diff --git a/tests/thinkphp/library/think/routeTest.php b/tests/thinkphp/library/think/routeTest.php index d6790347..b0215cee 100644 --- a/tests/thinkphp/library/think/routeTest.php +++ b/tests/thinkphp/library/think/routeTest.php @@ -227,7 +227,7 @@ class routeTest extends \PHPUnit_Framework_TestCase { $request = Request::instance(); Route::get('say/:name', '@index/hello'); - $this->assertEquals(['type' => 'controller', 'controller' => 'index/hello'], Route::check($request, 'say/thinkphp')); + $this->assertEquals(['type' => 'controller', 'controller' => 'index/hello', 'var' => []], Route::check($request, 'say/thinkphp')); } public function testRouteToMethod() @@ -235,8 +235,8 @@ class routeTest extends \PHPUnit_Framework_TestCase $request = Request::instance(); Route::get('user/:name', '\app\index\service\User::get', [], ['name' => '\w+']); Route::get('info/:name', '\app\index\model\Info@getInfo', [], ['name' => '\w+']); - $this->assertEquals(['type' => 'method', 'method' => '\app\index\service\User::get'], Route::check($request, 'user/thinkphp')); - $this->assertEquals(['type' => 'method', 'method' => ['\app\index\model\Info', 'getInfo']], Route::check($request, 'info/thinkphp')); + $this->assertEquals(['type' => 'method', 'method' => '\app\index\service\User::get', 'var' => []], Route::check($request, 'user/thinkphp')); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\model\Info', 'getInfo'], 'var' => []], Route::check($request, 'info/thinkphp')); } public function testRouteToRedirect() @@ -257,10 +257,10 @@ class routeTest extends \PHPUnit_Framework_TestCase $this->assertEquals(['index', 'blog', 'test'], $result['module']); Route::bind('\app\index\controller', 'namespace'); - $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\blog', 'read']], Route::check($request, 'blog/read')); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\Blog', 'read']], Route::check($request, 'blog/read')); - Route::bind('\app\index\controller\blog', 'class'); - $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\blog', 'read']], Route::check($request, 'read')); + Route::bind('\app\index\controller\Blog', 'class'); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\Blog', 'read']], Route::check($request, 'read')); } public function testDomain() diff --git a/tests/thinkphp/library/think/template/taglib/cxTest.php b/tests/thinkphp/library/think/template/taglib/cxTest.php index a77824b6..8aee392f 100644 --- a/tests/thinkphp/library/think/template/taglib/cxTest.php +++ b/tests/thinkphp/library/think/template/taglib/cxTest.php @@ -19,7 +19,7 @@ namespace tests\thinkphp\library\think\tempplate\taglib; use think\Template; use think\template\taglib\Cx; -class templateTest extends \PHPUnit_Framework_TestCase +class cxTest extends \PHPUnit_Framework_TestCase { public function testPhp() { @@ -47,7 +47,7 @@ EOF; {/volist} EOF; $data = <<\$vo): \$mod = (\$key % 2 );++\$key;?> +\$vo): \$mod = (\$key % 2 );++\$key;?> EOF; @@ -88,7 +88,7 @@ EOF; {/foreach} EOF; $data = <<\$val): ?> +\$val): ?> EOF; @@ -389,7 +389,7 @@ default {/empty} EOF; $data = <<isEmpty())): ?> +isEmpty())): ?> default EOF; @@ -402,7 +402,7 @@ default {/notempty} EOF; $data = <<isEmpty()))): ?> +isEmpty()))): ?> default EOF; @@ -538,13 +538,13 @@ EOF; public function testUrl() { $template = new template(); - $content = <<display($content); $this->expectOutputString(\think\Url::build('Index/index')); } - + public function testFunction() { $template = new template(); diff --git a/tests/thinkphp/library/think/templateTest.php b/tests/thinkphp/library/think/templateTest.php index 29100f4d..49c0eb39 100644 --- a/tests/thinkphp/library/think/templateTest.php +++ b/tests/thinkphp/library/think/templateTest.php @@ -291,15 +291,15 @@ EOF; {\$Think.SITE.URL} EOF; $data = <<
-
-
+server('SERVER_NAME'); ?>
+get('action'); ?>
+post('action'); ?>




-
-
+env('OS'); ?>
+request('action'); ?>



diff --git a/tests/thinkphp/library/think/urlTest.php b/tests/thinkphp/library/think/urlTest.php index 94dfc71d..acb43365 100644 --- a/tests/thinkphp/library/think/urlTest.php +++ b/tests/thinkphp/library/think/urlTest.php @@ -25,7 +25,18 @@ class urlTest extends \PHPUnit_Framework_TestCase public function setUp() { - Route::rules([]); + Route::rules(['get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => []]); Route::name([]); } @@ -63,7 +74,7 @@ class urlTest extends \PHPUnit_Framework_TestCase public function testBuildMethod() { Route::get('blog/:id', '\app\index\controller\blog@read'); - $this->assertEquals('/blog/10.html', Url::build('[\app\index\controller\blog@read]', 'id=10', 'html')); + $this->assertEquals('/blog/10.html', Url::build('\app\index\controller\blog@read', 'id=10', 'html')); } public function testBuildRoute() @@ -86,10 +97,10 @@ class urlTest extends \PHPUnit_Framework_TestCase { Route::get('blog/:id', 'index/blog'); Config::set('url_html_suffix', 'shtml'); - $this->assertEquals('/blog/10.shtml#detail', Url::build('/blog/10#detail')); + $this->assertEquals('/blog/10.shtml#detail', Url::build('index/blog#detail', 'id=10')); Config::set('url_common_param', true); - $this->assertEquals('/blog/10.shtml?foo=bar#detail', Url::build('/blog/10#detail', "foo=bar")); + $this->assertEquals('/blog/10.shtml?foo=bar#detail', Url::build('index/blog#detail', "id=10&foo=bar")); } public function testBuildDomain() diff --git a/tests/thinkphp/library/think/validateTest.php b/tests/thinkphp/library/think/validateTest.php index 4d7819b5..081a6f2b 100644 --- a/tests/thinkphp/library/think/validateTest.php +++ b/tests/thinkphp/library/think/validateTest.php @@ -88,7 +88,7 @@ class validateTest extends \PHPUnit_Framework_TestCase 'date' => '16-3-8', 'ok' => 'yes', 'value' => 100, - 'bool' => 'true', + 'bool' => true, 'title' => '流年ThinkPHP', 'city' => '上海', 'nickname' => '流年ThinkPHP_2016', -- Gitee From e2433c9e8226e694014f8865b903fc160cb70ea2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 13 Feb 2017 13:47:27 +0800 Subject: [PATCH 0003/1384] =?UTF-8?q?=E6=A0=B8=E5=BF=83=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 89 +- library/think/Build.php | 2 +- library/think/Cache.php | 43 +- library/think/Collection.php | 8 +- library/think/Config.php | 11 +- library/think/Console.php | 11 +- library/think/Controller.php | 11 +- library/think/Cookie.php | 20 +- library/think/Db.php | 23 +- library/think/Debug.php | 10 +- library/think/Env.php | 2 +- library/think/Error.php | 2 +- library/think/Exception.php | 2 +- library/think/File.php | 4 +- library/think/Hook.php | 222 ++-- library/think/Lang.php | 10 +- library/think/Loader.php | 61 +- library/think/Log.php | 14 +- library/think/Model.php | 937 ++++++++++---- library/think/Paginator.php | 153 ++- library/think/Process.php | 112 +- library/think/Request.php | 193 +-- library/think/Response.php | 39 +- library/think/Route.php | 337 +++-- library/think/Session.php | 76 +- library/think/Template.php | 103 +- library/think/Url.php | 164 ++- library/think/Validate.php | 203 +-- library/think/View.php | 38 +- library/think/cache/Driver.php | 25 +- library/think/cache/driver/File.php | 5 +- library/think/cache/driver/Lite.php | 2 +- library/think/cache/driver/Memcache.php | 3 +- library/think/cache/driver/Memcached.php | 2 +- library/think/cache/driver/Redis.php | 2 +- library/think/cache/driver/Sqlite.php | 3 +- library/think/cache/driver/Wincache.php | 5 +- library/think/cache/driver/Xcache.php | 3 +- library/think/config/driver/Ini.php | 2 +- library/think/config/driver/Json.php | 2 +- library/think/config/driver/Xml.php | 2 +- library/think/console/Input.php | 2 +- library/think/console/command/Help.php | 3 +- library/think/console/command/Lists.php | 2 +- .../console/command/optimize/Autoload.php | 2 +- .../think/console/command/optimize/Schema.php | 2 +- library/think/console/output/Ask.php | 2 +- .../think/console/output/driver/Console.php | 16 +- library/think/controller/Rest.php | 2 +- library/think/db/Builder.php | 222 ++-- library/think/db/Connection.php | 281 +++-- library/think/db/Query.php | 1089 +++++++++++++---- library/think/db/builder/Mysql.php | 15 +- library/think/db/builder/Pgsql.php | 15 +- library/think/db/builder/Sqlite.php | 25 +- library/think/db/builder/Sqlsrv.php | 23 +- library/think/db/connector/Mysql.php | 27 +- library/think/db/connector/Pgsql.php | 3 +- library/think/db/connector/Sqlite.php | 4 +- library/think/db/connector/Sqlsrv.php | 2 +- .../think/db/exception/BindParamException.php | 4 +- .../db/exception/DataNotFoundException.php | 10 +- .../db/exception/ModelNotFoundException.php | 10 +- library/think/exception/DbException.php | 2 +- library/think/exception/ErrorException.php | 2 +- library/think/exception/PDOException.php | 4 +- .../exception/RouteNotFoundException.php | 22 + library/think/log/driver/File.php | 50 +- library/think/log/driver/Socket.php | 45 +- library/think/model/Collection.php | 79 ++ library/think/model/Merge.php | 84 +- library/think/model/Pivot.php | 2 +- library/think/model/Relation.php | 668 +--------- library/think/model/relation/BelongsTo.php | 171 +++ .../think/model/relation/BelongsToMany.php | 390 ++++++ library/think/model/relation/HasMany.php | 273 +++++ .../think/model/relation/HasManyThrough.php | 150 +++ library/think/model/relation/HasOne.php | 175 +++ library/think/model/relation/MorphMany.php | 265 ++++ library/think/model/relation/MorphTo.php | 223 ++++ library/think/model/relation/OneToOne.php | 316 +++++ library/think/paginator/driver/Bootstrap.php | 6 +- library/think/process/Builder.php | 7 +- library/think/process/Utils.php | 4 +- library/think/process/exception/Failed.php | 42 + library/think/process/exception/Timeout.php | 2 +- library/think/process/pipes/Pipes.php | 3 +- library/think/process/pipes/Unix.php | 20 +- library/think/process/pipes/Windows.php | 8 +- library/think/response/Json.php | 24 +- library/think/response/Jsonp.php | 32 +- library/think/response/Redirect.php | 10 +- library/think/response/View.php | 2 +- library/think/response/Xml.php | 2 +- library/think/session/driver/Memcache.php | 7 +- library/think/session/driver/Memcached.php | 7 +- library/think/session/driver/Redis.php | 10 +- library/think/template/TagLib.php | 2 +- library/think/template/driver/File.php | 2 +- library/think/template/taglib/Cx.php | 12 +- library/think/view/driver/Php.php | 39 +- library/think/view/driver/Think.php | 39 +- library/traits/controller/Jump.php | 7 +- library/traits/model/SoftDelete.php | 41 +- library/traits/think/Instance.php | 4 +- 105 files changed, 5675 insertions(+), 2295 deletions(-) create mode 100644 library/think/exception/RouteNotFoundException.php create mode 100644 library/think/model/Collection.php create mode 100644 library/think/model/relation/BelongsTo.php create mode 100644 library/think/model/relation/BelongsToMany.php create mode 100644 library/think/model/relation/HasMany.php create mode 100644 library/think/model/relation/HasManyThrough.php create mode 100644 library/think/model/relation/HasOne.php create mode 100644 library/think/model/relation/MorphMany.php create mode 100644 library/think/model/relation/MorphTo.php create mode 100644 library/think/model/relation/OneToOne.php create mode 100644 library/think/process/exception/Failed.php diff --git a/library/think/App.php b/library/think/App.php index 7e140ca9..963d8b35 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,18 +11,9 @@ namespace think; -use think\Config; -use think\Env; -use think\Exception; use think\exception\HttpException; use think\exception\HttpResponseException; -use think\Hook; -use think\Lang; -use think\Loader; -use think\Log; -use think\Request; -use think\Response; -use think\Route; +use think\exception\RouteNotFoundException; /** * App 应用管理 @@ -126,6 +117,8 @@ class App // 监听app_begin Hook::listen('app_begin', $dispatch); + // 请求缓存检查 + $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); switch ($dispatch['type']) { case 'redirect': @@ -138,11 +131,13 @@ class App break; case 'controller': // 执行控制器操作 - $data = Loader::action($dispatch['controller']); + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']); break; case 'method': // 执行回调方法 - $data = self::invokeMethod($dispatch['method']); + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); break; case 'function': // 执行闭包 @@ -217,7 +212,7 @@ class App public static function invokeMethod($method, $vars = []) { if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : new $method[0](Request::instance()); + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); $reflect = new \ReflectionMethod($class, $method[1]); } else { // 静态方法 @@ -225,7 +220,7 @@ class App } $args = self::bindParams($reflect, $vars); - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); return $reflect->invokeArgs(isset($class) ? $class : null, $args); } @@ -245,8 +240,6 @@ class App } else { $args = []; } - - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); return $reflect->newInstanceArgs($args); } @@ -254,7 +247,7 @@ class App * 绑定参数 * @access public * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 + * @param array $vars 变量 * @return array */ private static function bindParams($reflect, $vars = []) @@ -282,7 +275,14 @@ class App if ($bind instanceof $className) { $args[] = $bind; } else { - $args[] = method_exists($className, 'instance') ? $className::instance() : new $className(); + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + if ($method->isPublic() && $method->isStatic()) { + $args[] = $className::invoke(Request::instance()); + continue; + } + } + $args[] = method_exists($className, 'instance') ? $className::instance() : new $className; } } elseif (1 == $type && !empty($vars)) { $args[] = array_shift($vars); @@ -294,8 +294,6 @@ class App throw new \InvalidArgumentException('method param miss:' . $name); } } - // 全局过滤 - array_walk_recursive($args, [Request::instance(), 'filterExp']); } return $args; } @@ -337,6 +335,8 @@ class App // 初始化模块 $request->module($module); $config = self::init($module); + // 模块请求缓存检查 + $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); } else { throw new HttpException(404, 'module not exists:' . $module); } @@ -364,34 +364,29 @@ class App // 监听module_init Hook::listen('module_init', $request); - try { - $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']); - if (is_null($instance)) { - throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); - } - // 获取当前操作名 - $action = $actionName . $config['action_suffix']; - if (!preg_match('/^[A-Za-z](\w)*$/', $action)) { - // 非法操作 - throw new \ReflectionException('illegal action name:' . $actionName); - } + $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']); + if (is_null($instance)) { + throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); + } + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + $vars = []; + if (is_callable([$instance, $action])) { // 执行操作方法 $call = [$instance, $action]; - Hook::listen('action_begin', $call); - - $data = self::invokeMethod($call); - } catch (\ReflectionException $e) { + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { // 操作不存在 - if (method_exists($instance, '_empty')) { - $reflect = new \ReflectionMethod($instance, '_empty'); - $data = $reflect->invokeArgs($instance, [$action]); - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); - } else { - throw new HttpException(404, 'method not exists:' . (new \ReflectionClass($instance))->getName() . '->' . $action); - } + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); } - return $data; + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); } /** @@ -443,9 +438,9 @@ class App // 监听app_init Hook::listen('app_init'); - self::$init = $config; + self::$init = true; } - return self::$init; + return Config::get(); } /** @@ -547,7 +542,7 @@ class App $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; if ($must && false === $result) { // 路由无效 - throw new HttpException(404, 'Route Not Found'); + throw new RouteNotFoundException(); } } if (false === $result) { diff --git a/library/think/Build.php b/library/think/Build.php index dd2bd550..13b7bfdc 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/Cache.php b/library/think/Cache.php index a5ac0b32..9e4b30ef 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,7 @@ namespace think; -use think\App; +use think\cache\Driver; class Cache { @@ -31,7 +31,7 @@ class Cache * @access public * @param array $options 配置数组 * @param bool|string $name 缓存连接标识 true 强制重新连接 - * @return \think\cache\Driver + * @return Driver */ public static function connect(array $options = [], $name = false) { @@ -79,11 +79,11 @@ class Cache * 切换缓存类型 需要配置 cache.type 为 complex * @access public * @param string $name 缓存标识 - * @return \think\cache\Driver + * @return Driver */ - public static function store($name) + public static function store($name = '') { - if ('complex' == Config::get('cache.type')) { + if ('' !== $name && 'complex' == Config::get('cache.type')) { self::connect(Config::get('cache.' . $name), strtolower($name)); } return self::$handler; @@ -185,13 +185,42 @@ class Cache return self::$handler->clear($tag); } + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public static function pull($name) + { + self::init(); + self::$readTimes++; + self::$writeTimes++; + return self::$handler->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::init(); + self::$readTimes++; + return self::$handler->remember($name, $value, $expire); + } + /** * 缓存标签 * @access public * @param string $name 标签名 * @param string|array $keys 缓存标识 * @param bool $overlay 是否覆盖 - * @return \think\cache\Driver + * @return Driver */ public static function tag($name, $keys = null, $overlay = false) { diff --git a/library/think/Collection.php b/library/think/Collection.php index 8315268a..41b42759 100644 --- a/library/think/Collection.php +++ b/library/think/Collection.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -225,11 +225,11 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria $result = []; foreach ($this->items as $row) { - $key = $value = null; + $key = $value = null; $keySet = $valueSet = false; if (null !== $index_key && array_key_exists($index_key, $row)) { $keySet = true; - $key = (string)$row[$index_key]; + $key = (string) $row[$index_key]; } if (null === $column_key) { $valueSet = true; @@ -368,6 +368,6 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria if ($items instanceof self) { return $items->all(); } - return (array)$items; + return (array) $items; } } diff --git a/library/think/Config.php b/library/think/Config.php index 5ab032fd..fc6c50ac 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -59,11 +59,14 @@ class Config self::$config[$range] = []; } if (is_file($file)) { + $name = strtolower($name); $type = pathinfo($file, PATHINFO_EXTENSION); - if ('php' != $type) { - return self::parse($file, $type, $name, $range); - } else { + if ('php' == $type) { return self::set(include $file, $name, $range); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return self::set(yaml_parse_file($file), $name, $range); + } else { + return self::parse($file, $type, $name, $range); } } else { return self::$config[$range]; diff --git a/library/think/Console.php b/library/think/Console.php index ab8e0211..1d97ab2b 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -88,18 +88,19 @@ class Console } /** - * @param $command - * @param array $parameters + * @param $command + * @param array $parameters + * @param string $driver * @return Output|Buffer */ - public static function call($command, array $parameters = []) + public static function call($command, array $parameters = [], $driver = 'buffer') { $console = self::init(false); array_unshift($parameters, $command); $input = new Input($parameters); - $output = new Output('buffer'); + $output = new Output($driver); $console->setCatchExceptions(false); $console->find($command)->run($input, $output); @@ -317,7 +318,7 @@ class Console if (!$command->isEnabled()) { $command->setConsole(null); - return null; + return; } if (null === $command->getDefinition()) { diff --git a/library/think/Controller.php b/library/think/Controller.php index a889e4d9..69db0a1d 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,16 +13,19 @@ namespace think; \think\Loader::import('controller/Jump', TRAIT_PATH, EXT); -use think\Exception; use think\exception\ValidateException; class Controller { use \traits\controller\Jump; - // 视图类实例 + /** + * @var \think\View 视图类实例 + */ protected $view; - // Request实例 + /** + * @var \think\Request Request实例 + */ protected $request; // 验证失败是否抛出异常 protected $failException = false; diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 648b2c60..93edb8d7 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -99,6 +99,22 @@ class Cookie $_COOKIE[$name] = $value; } + /** + * 永久保存Cookie数据 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + $option['expire'] = 315360000; + self::set($name, $value, $option); + } + /** * 判断Cookie数据 * @param string $name cookie名称 @@ -133,7 +149,7 @@ class Cookie } return $value; } else { - return null; + return; } } diff --git a/library/think/Db.php b/library/think/Db.php index b00aee61..00f719e0 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,10 +11,8 @@ namespace think; -use think\App; -use think\Collection; +use think\db\Connection; use think\db\Query; -use think\paginator\Collection as PaginatorCollection; /** * Class Db @@ -26,22 +24,25 @@ use think\paginator\Collection as PaginatorCollection; * @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 = true , integer $expire = null) static 设置查询缓存 + * @method Query cache(mixed $key = null , integer $expire = 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 = []) static 查询单个记录 - * @method mixed select(mixed $data = []) static 查询多个记录 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID * @method integer insertAll(array $dataSet) static 插入多条记录 * @method integer update(array $data) static 更新记录 - * @method integer delete(mixed $data = []) static 删除记录 + * @method integer delete(mixed $data = null) static 删除记录 * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 - * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = false) static SQL查询 + * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = null) static SQL查询 * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 - * @method PaginatorCollection paginate(integer $listRows = 15, mixed $simple = false, array $config = []) static 分页查询 + * @method 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语句 */ class Db @@ -59,7 +60,7 @@ class Db * @access public * @param mixed $config 连接配置 * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Connection + * @return Connection * @throws Exception */ public static function connect($config = [], $name = false) diff --git a/library/think/Debug.php b/library/think/Debug.php index e36a764e..844dfb89 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,11 +11,7 @@ namespace think; -use think\Config; use think\exception\ClassNotFoundException; -use think\Log; -use think\Request; -use think\Response; use think\response\Redirect; class Debug @@ -178,8 +174,8 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); - return null; + echo($output); + return; } else { return $output; } diff --git a/library/think/Env.php b/library/think/Env.php index a23d9925..6f31841d 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/Error.php b/library/think/Error.php index 28f75882..c17292bf 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/Exception.php b/library/think/Exception.php index ac648764..034c85b6 100644 --- a/library/think/Exception.php +++ b/library/think/Exception.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/File.php b/library/think/File.php index 43f38575..ba59273f 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -39,7 +39,7 @@ class File extends SplFileObject public function __construct($filename, $mode = 'r') { parent::__construct($filename, $mode); - $this->filename = $this->getRealPath(); + $this->filename = $this->getRealPath() ?: $this->getPathname(); } /** diff --git a/library/think/Hook.php b/library/think/Hook.php index 062ac962..f06196e4 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -1,9 +1,8 @@ $behavior) { + self::add($tag, $behavior); + } + } else { + self::$tags = $tags + self::$tags; + } + } - /** - * 批量导入插件 - * @param array $tags 插件信息 - * @param boolean $recursive 是否递归合并 - */ - public static function import(array $tags, $recursive = true) - { - if ($recursive) { - foreach ($tags as $tag => $behavior) { - self::add($tag, $behavior); - } - } else { - self::$tags = $tags + self::$tags; - } - } + /** + * 获取插件信息 + * @param string $tag 插件位置 留空获取全部 + * @return array + */ + public static function get($tag = '') + { + if (empty($tag)) { + //获取全部的插件信息 + return self::$tags; + } else { + return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + } + } - /** - * 获取插件信息 - * @param string $tag 插件位置 留空获取全部 - * @return array - */ - public static function get($tag = '') - { - if (empty($tag)) {//获取全部的插件信息 - return self::$tags; - } else { - return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; - } - } + /** + * 监听标签的行为 + * @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) + { + $results = []; + $tags = static::get($tag); + foreach ($tags as $key => $name) { + $results[$key] = self::exec($name, $tag, $params, $extra); + if (false === $results[$key]) { + // 如果返回false 则中断行为执行 + break; + } elseif (!is_null($results[$key]) && $once) { + break; + } + } + return $once ? end($results) : $results; + } - /** - * 监听标签的行为 - * @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) - { - $results = []; - $tags = static::get($tag); - foreach ($tags as $key => $name) { - $results[$key] = self::exec($name, $tag, $params, $extra); - if (false === $results[$key]) {// 如果返回false 则中断行为执行 - break; - } elseif (!is_null($results[$key]) && $once) { - break; - } - } - return $once ? end($results) : $results; - } + /** + * 执行某个行为 + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param Mixed $params 传人的参数 + * @param mixed $extra 额外参数 + * @return mixed + */ + public static function exec($class, $tag = '', &$params = null, $extra = null) + { + App::$debug && Debug::remark('behavior_start', 'time'); + $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; - /** - * 执行某个行为 - * @param mixed $class 要执行的行为 - * @param string $tag 方法名(标签名) - * @param Mixed $params 传人的参数 - * @param mixed $extra 额外参数 - * @return mixed - */ - public static function exec($class, $tag = '', &$params = null, $extra = null) - { - App::$debug && Debug::remark('behavior_start', 'time'); - if (is_callable($class)) { - $result = call_user_func_array($class, [ & $params, $extra]); - $class = 'Closure'; - } elseif (is_object($class)) { - $result = call_user_func_array([$class, $tag], [ & $params, $extra]); - $class = get_class($class); - } else { - $obj = new $class(); - $result = ($tag && is_callable([$obj, $tag])) ? $obj->$tag($params, $extra) : $obj->run($params, $extra); - } - if (App::$debug) { - Debug::remark('behavior_end', 'time'); - Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); - } - return $result; - } + $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]); + } else { + $obj = new $class(); + $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; + $result = $obj->$method($params, $extra); + } + if (App::$debug) { + Debug::remark('behavior_end', 'time'); + Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + } + return $result; + } } diff --git a/library/think/Lang.php b/library/think/Lang.php index 16e7f545..d52f1947 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,10 +11,6 @@ namespace think; -use think\App; -use think\Cookie; -use think\Log; - class Lang { // 语言数据 @@ -83,7 +79,9 @@ class Lang // 记录加载信息 App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); $_lang = include $_file; - $lang = array_change_key_case($_lang) + $lang; + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } } } if (!empty($lang)) { diff --git a/library/think/Loader.php b/library/think/Loader.php index cfa0a217..70de3175 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -365,15 +365,20 @@ class Loader */ public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') { - if (isset(self::$instance[$name . $layer])) { - return self::$instance[$name . $layer]; + $guid = $name . $layer; + if (isset(self::$instance[$guid])) { + return self::$instance[$guid]; } - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); + if (strpos($name, '\\')) { + $class = $name; } else { - $module = Request::instance()->module(); + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = Request::instance()->module(); + } + $class = self::parseClass($module, $layer, $name, $appendSuffix); } - $class = self::parseClass($module, $layer, $name, $appendSuffix); if (class_exists($class)) { $model = new $class(); } else { @@ -384,7 +389,7 @@ class Loader throw new ClassNotFoundException('class not exists:' . $class, $class); } } - self::$instance[$name . $layer] = $model; + self::$instance[$guid] = $model; return $model; } @@ -399,12 +404,16 @@ class Loader */ public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name); + if (strpos($name, '\\')) { + $class = $name; } else { - $module = Request::instance()->module(); + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name); + } else { + $module = Request::instance()->module(); + } + $class = self::parseClass($module, $layer, $name, $appendSuffix); } - $class = self::parseClass($module, $layer, $name, $appendSuffix); if (class_exists($class)) { return App::invokeClass($class); } elseif ($empty && class_exists($emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix))) { @@ -427,16 +436,20 @@ class Loader if (empty($name)) { return new Validate; } - - if (isset(self::$instance[$name . $layer])) { - return self::$instance[$name . $layer]; + $guid = $name . $layer; + if (isset(self::$instance[$guid])) { + return self::$instance[$guid]; } - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name); + if (strpos($name, '\\')) { + $class = $name; } else { - $module = Request::instance()->module(); + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name); + } else { + $module = Request::instance()->module(); + } + $class = self::parseClass($module, $layer, $name, $appendSuffix); } - $class = self::parseClass($module, $layer, $name, $appendSuffix); if (class_exists($class)) { $validate = new $class; } else { @@ -447,7 +460,7 @@ class Loader throw new ClassNotFoundException('class not exists:' . $class, $class); } } - self::$instance[$name . $layer] = $validate; + self::$instance[$guid] = $validate; return $validate; } @@ -493,14 +506,16 @@ class Loader * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 * @param string $name 字符串 * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) * @return string */ - public static function parseName($name, $type = 0) + public static function parseName($name, $type = 0, $ucfirst = true) { if ($type) { - return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { return strtoupper($match[1]); - }, $name)); + }, $name); + return $ucfirst ? ucfirst($name) : lcfirst($name); } else { return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); } diff --git a/library/think/Log.php b/library/think/Log.php index 5fd1631a..4a8b0b40 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -32,13 +32,14 @@ class Log const SQL = 'sql'; const NOTICE = 'notice'; const ALERT = 'alert'; + const DEBUG = 'debug'; // 日志信息 protected static $log = []; // 配置参数 protected static $config = []; // 日志类型 - protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert']; + protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; // 日志写入驱动 protected static $driver; @@ -83,6 +84,10 @@ class Log public static function record($msg, $type = 'log') { self::$log[$type][] = $msg; + if (IS_CLI && count(self::$log[$type]) > 100) { + // 命令行下面日志写入改进 + self::save(); + } } /** @@ -136,6 +141,9 @@ class Log if (empty(self::$config['level'])) { // 获取全部日志 $log = self::$log; + if (!App::$debug && isset($log['debug'])) { + unset($log['debug']); + } } else { // 记录允许级别 $log = []; @@ -180,7 +188,7 @@ class Log self::init(Config::get('log')); } // 写入日志 - return self::$driver->save($log); + return self::$driver->save($log, false); } /** diff --git a/library/think/Model.php b/library/think/Model.php index f062e3c5..1222b548 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,31 +12,22 @@ namespace think; use InvalidArgumentException; -use think\Cache; -use think\Config; -use think\Db; use think\db\Query; -use think\Exception; use think\Exception\ValidateException; -use think\Loader; +use think\model\Collection as ModelCollection; use think\model\Relation; -use think\paginator\Collection as PaginatorCollection; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphTo; /** * Class Model * @package think - * @method static PaginatorCollection paginate(integer $listRows = 15, boolean $simple = false, array $config = []) 分页查询 - * @method static mixed value($field, $default = null) 得到某个字段的值 - * @method static array column($field, $key = '') 得到某个列的数组 - * @method static integer count($field = '*') COUNT查询 - * @method static integer sum($field = '*') SUM查询 - * @method static integer min($field = '*') MIN查询 - * @method static integer max($field = '*') MAX查询 - * @method static integer avg($field = '*') AVG查询 - * @method static setField($field, $value = '') - * @method static Query where($field, $op = null, $condition = null) 指定AND查询条件 - * @method static static findOrFail($data = null) 查找单条记录 如果不存在则抛出异常 - * + * @mixin Query */ abstract class Model implements \JsonSerializable, \ArrayAccess { @@ -88,7 +79,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 更新时间字段 protected $updateTime = 'update_time'; // 时间字段取出后的默认时间格式 - protected $dateFormat = 'Y-m-d H:i:s'; + protected $dateFormat; // 字段类型或者格式转换 protected $type = []; // 是否为更新数据 @@ -101,6 +92,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $failException = false; // 全局查询范围 protected $useGlobalScope = true; + // 是否采用批量验证 + protected $batchValidate = false; + // 查询数据集对象 + protected $resultSetType; + // 关联自动写入 + protected $relationWrite; + // + protected static $db; /** * 初始化过的模型. @@ -137,9 +136,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = $this->db()->getConfig('auto_timestamp'); + $this->autoWriteTimestamp = $this->db(false)->getConfig('auto_timestamp'); } + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $this->db(false)->getConfig('datetime_format'); + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->db(false)->getConfig('resultset_type'); + } // 执行初始化操作 $this->initialize(); } @@ -154,8 +161,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { $model = $this->class; if (!isset(self::$links[$model])) { + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); + } else { + $connection = $this->connection; + } + } else { + $connection = []; + } // 设置当前模型 确保查询返回模型对象 - $query = Db::connect($this->connection)->model($model, $this->query); + $query = Db::connect($connection)->getQuery($model, $this->query); // 设置当前数据表和模型名 if (!empty($this->table)) { @@ -178,26 +195,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return self::$links[$model]; } - /** - * 获取关联模型实例 - * @access protected - * @param string|array $relation 关联查询 - * @return Relation|Query - */ - protected function relation($relation = null) - { - if (!is_null($relation)) { - // 执行关联查询 - return $this->db()->relation($relation); - } - - // 获取关联对象实例 - if (is_null($this->relation)) { - $this->relation = new Relation($this); - } - return $this->relation; - } - /** * 初始化模型 * @access protected @@ -218,12 +215,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @return void */ protected static function init() - {} + { + } /** * 设置数据对象值 * @access public - * @param mixed $data 数据或者属性名 + * @param mixed $data 数据或者属性名 * @param mixed $value 值 * @return $this */ @@ -270,9 +268,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 修改器 设置数据对象值 * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @param array $data 数据 + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 * @return $this */ public function setAttr($name, $value, $data = []) @@ -292,7 +290,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 标记字段更改 - if (!isset($this->data[$name]) || ($this->data[$name] != $value && !in_array($name, $this->change))) { + if (isset($this->data[$name]) && is_scalar($this->data[$name]) && is_scalar($value) && 0 !== strcmp($this->data[$name], $value)) { + $this->change[] = $name; + } elseif (!isset($this->data[$name]) || $value != $this->data[$name]) { $this->change[] = $name; } // 设置数据对象属性 @@ -303,7 +303,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 自动写入时间戳 * @access public - * @param string $name 时间戳字段 + * @param string $name 时间戳字段 * @return mixed */ protected function autoWriteTimestamp($name) @@ -317,26 +317,50 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'datetime': case 'date': $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, $_SERVER['REQUEST_TIME']); + $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $format); break; case 'timestamp': - case 'int': + case 'integer': + default: $value = $_SERVER['REQUEST_TIME']; break; } - } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), ['datetime', 'date', 'timestamp'])) { - $value = date($this->dateFormat, $_SERVER['REQUEST_TIME']); + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat); } else { - $value = $_SERVER['REQUEST_TIME']; + $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat, true); } return $value; } + /** + * 时间日期字段格式化处理 + * @access public + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp && false !== $format) { + $time = date($format, $time); + } + return $time; + } + /** * 数据写入 类型转换 * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 + * @param mixed $value 值 + * @param string|array $type 要转换的类型 * @return mixed */ protected function writeTransform($value, $type) @@ -367,7 +391,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess break; case 'datetime': $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, is_numeric($value) ? $value : strtotime($value)); + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); break; case 'object': if (is_object($value)) { @@ -383,6 +408,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'serialize': $value = serialize($value); break; + } return $value; } @@ -411,11 +437,24 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } elseif (isset($this->type[$name])) { // 类型转换 $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } } elseif ($notFound) { - $method = Loader::parseName($name, 1); - if (method_exists($this, $method) && !method_exists('\think\Model', $method)) { + $method = Loader::parseName($name, 1, false); + if (method_exists($this, $method) && $this->$method() instanceof Relation) { + // 清空之前的查询参数 + $this->$method()->removeOption(); // 不存在该字段 获取关联数据 - $value = $this->relation()->getRelation($method); + $value = $this->$method()->getRelation(); // 保存关联对象值 $this->data[$name] = $value; } else { @@ -428,8 +467,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 数据读取 类型转换 * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 + * @param mixed $value 值 + * @param string|array $type 要转换的类型 * @return mixed */ protected function readTransform($value, $type) @@ -456,13 +495,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'timestamp': if (!is_null($value)) { $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, $value); + $value = $this->formatDateTime($value, $format); } break; case 'datetime': if (!is_null($value)) { $format = !empty($param) ? $param : $this->dateFormat; - $value = date($format, strtotime($value)); + $value = $this->formatDateTime(strtotime($value), $format); } break; case 'json': @@ -477,6 +516,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'serialize': $value = unserialize($value); break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } } return $value; } @@ -484,38 +528,119 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置需要追加的输出属性 * @access public - * @param array $append 属性列表 + * @param array $append 属性列表 + * @param bool $override 是否覆盖 * @return $this */ - public function append($append = []) + public function append($append = [], $override = false) { - $this->append = $append; + $this->append = $override ? $append : array_merge($this->append, $append); + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($relation, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + $model = $this->getAttr($relation); + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if ($this->__isset($key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->setAttr($key, $model->$attr); + } + } + } return $this; } /** * 设置需要隐藏的输出属性 * @access public - * @param array $hidden 属性列表 + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 * @return $this */ - public function hidden($hidden = []) + public function hidden($hidden = [], $override = false) { - $this->hidden = $hidden; + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); return $this; } /** * 设置需要输出的属性 + * @access public * @param array $visible + * @param bool $override 是否覆盖 * @return $this */ - public function visible($visible = []) + public function visible($visible = [], $override = false) { - $this->visible = $visible; + $this->visible = $override ? $visible : array_merge($this->visible, $visible); return $this; } + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|ModelCollection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + /** * 转换当前模型对象为数组 * @access public @@ -523,26 +648,29 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function toArray() { - $item = []; - - //过滤属性 + $item = []; + $visible = []; + $hidden = []; + // 过滤属性 if (!empty($this->visible)) { - $data = array_intersect_key($this->data, array_flip($this->visible)); + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($this->data, array_flip($array)); } elseif (!empty($this->hidden)) { - $data = array_diff_key($this->data, array_flip($this->hidden)); + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($this->data, array_flip($array)); } else { $data = $this->data; } foreach ($data as $key => $val) { - if ($val instanceof Model || $val instanceof Collection) { + if ($val instanceof Model || $val instanceof ModelCollection) { // 关联模型对象 - $item[$key] = $val->toArray(); + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); } elseif (is_array($val) && reset($val) instanceof Model) { // 关联模型数据集 $arr = []; foreach ($val as $k => $value) { - $arr[$k] = $value->toArray(); + $arr[$k] = $this->subToArray($value, $visible, $hidden, $k); } $item[$key] = $arr; } else { @@ -552,8 +680,19 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 追加属性(必须定义获取器) if (!empty($this->append)) { - foreach ($this->append as $name) { - $item[$name] = $this->getAttr($name); + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $item[$name] = $this->getAttr($name); + } } } return !empty($item) ? $item : []; @@ -562,7 +701,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 转换当前模型对象为JSON字符串 * @access public - * @param integer $options json参数 + * @param integer $options json参数 * @return string */ public function toJson($options = JSON_UNESCAPED_UNICODE) @@ -570,6 +709,40 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return json_encode($this->toArray(), $options); } + /** + * 转换当前模型数据集为数据集对象 + * @access public + * @param array|\think\Collection $collection 数据集 + * @return \think\Collection + */ + public function toCollection($collection) + { + if ($this->resultSetType) { + if ('collection' == $this->resultSetType) { + $collection = new ModelCollection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { + $class = $this->resultSetType; + $collection = new $class($collection); + } + } + return $collection; + } + + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + /** * 获取模型对象的主键 * @access public @@ -579,10 +752,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public function getPk($name = '') { if (!empty($name)) { - $table = $this->db()->getTable($name); - return $this->db()->getPk($table); + $table = $this->db(false)->getTable($name); + return $this->db(false)->getPk($table); } elseif (empty($this->pk)) { - $this->pk = $this->db()->getPk(); + $this->pk = $this->db(false)->getPk(); } return $this->pk; } @@ -607,9 +780,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 保存当前数据对象 * @access public - * @param array $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 * @return integer|false */ public function save($data = [], $where = [], $sequence = null) @@ -628,8 +801,32 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } + // 自动关联写入 + if (!empty($this->relationWrite)) { + $relation = []; + foreach ($this->relationWrite as $key => $name) { + if (!is_numeric($key)) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } + } + } elseif (isset($this->data[$name])) { + $relation[$name] = $this->data[$name]; + if (!$this->isUpdate) { + unset($this->data[$name]); + } + } + } + } + // 检测字段 if (!empty($this->field)) { + if (true === $this->field) { + $this->field = $this->db(false)->getTableInfo('', 'fields'); + } foreach ($this->data as $key => $val) { if (!in_array($key, $this->field)) { unset($this->data[$key]); @@ -641,7 +838,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->autoCompleteData($this->auto); // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime) { + if ($this->autoWriteTimestamp && $this->updateTime && (empty($this->change) || !in_array($this->updateTime, $this->change))) { $this->setAttr($this->updateTime, null); } @@ -649,7 +846,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (false === $this->trigger('before_write', $this)) { return false; } - + $pk = $this->getPk(); if ($this->isUpdate) { // 自动更新 $this->autoCompleteData($this->update); @@ -680,18 +877,41 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $where = $this->updateWhere; } - if (!empty($where)) { - $pk = $this->getPk(); - if (is_string($pk) && isset($data[$pk])) { - if (!isset($where[$pk])) { - unset($where); - $where[$pk] = $data[$pk]; + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + + // 关联更新 + if (isset($relation)) { + foreach ($relation as $name => $val) { + if (isset($data[$name])) { + unset($data[$name]); } - unset($data[$pk]); } } + // 模型更新 $result = $this->db()->where($where)->update($data); + + // 关联更新 + if (isset($relation)) { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + // 清空change $this->change = []; // 更新回调 @@ -701,7 +921,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->autoCompleteData($this->insert); // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime) { + if ($this->autoWriteTimestamp && $this->createTime && (empty($this->change) || !in_array($this->createTime, $this->change))) { $this->setAttr($this->createTime, null); } @@ -712,13 +932,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $result = $this->db()->insert($this->data); // 获取自动增长主键 - if ($result) { + if ($result && is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { $insertId = $this->db()->getLastInsID($sequence); - $pk = $this->getPk(); - if (is_string($pk) && $insertId) { + if ($insertId) { $this->data[$pk] = $insertId; } } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + // 标记为更新 $this->isUpdate = true; // 清空change @@ -735,9 +963,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 保存多个数据到当前数据对象 * @access public - * @param array $dataSet 数据 - * @param boolean $replace 是否自动识别更新和写入 + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 * @return array|false + * @throws \Exception */ public function saveAll($dataSet, $replace = true) { @@ -745,7 +974,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 数据批量验证 $validate = $this->validate; foreach ($dataSet as $data) { - if (!$this->validate($validate)->validateData($data)) { + if (!$this->validateData($data, $validate)) { return false; } } @@ -761,9 +990,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } foreach ($dataSet as $key => $data) { if (!empty($auto) && isset($data[$pk])) { - $result[$key] = self::update($data); + $result[$key] = self::update($data, [], $this->field); } else { - $result[$key] = self::create($data); + $result[$key] = self::create($data, $this->field); } } $db->commit(); @@ -777,18 +1006,33 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置允许写入的字段 * @access public - * @param bool|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @param mixed $field 允许写入的字段 如果为true只允许写入数据表字段 * @return $this */ public function allowField($field) { - if (true === $field) { - $field = $this->db()->getTableInfo('', 'fields'); + if (is_string($field)) { + $field = explode(',', $field); } $this->field = $field; return $this; } + /** + * 设置只读字段 + * @access public + * @param mixed $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->readonly = $field; + return $this; + } + /** * 是否为更新数据 * @access public @@ -835,7 +1079,29 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->db()->delete($this->data); + // 删除条件 + $pk = $this->getPk(); + if (isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + + // 删除当前模型数据 + $result = $this->db()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } $this->trigger('after_delete', $this); return $result; @@ -856,11 +1122,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置字段验证 * @access public - * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 - * @param array $msg 提示信息 + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 + * @param bool $batch 批量验证 * @return $this */ - public function validate($rule = true, $msg = []) + public function validate($rule = true, $msg = [], $batch = false) { if (is_array($rule)) { $this->validate = [ @@ -870,6 +1137,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } else { $this->validate = true === $rule ? $this->name : $rule; } + $this->batchValidate = $batch; return $this; } @@ -888,13 +1156,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 自动验证数据 * @access protected - * @param array $data 验证数据 + * @param array $data 验证数据 + * @param mixed $rule 验证规则 + * @param bool $batch 批量验证 * @return bool */ - protected function validateData($data) + protected function validateData($data, $rule = null, $batch = null) { - if (!empty($this->validate)) { - $info = $this->validate; + $info = is_null($rule) ? $this->validate : $rule; + + if (!empty($info)) { if (is_array($info)) { $validate = Loader::validate(); $validate->rule($info['rule']); @@ -909,7 +1180,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $validate->scene($scene); } } - if (!$validate->check($data)) { + $batch = is_null($batch) ? $this->batchValidate : $batch; + + if (!$validate->batch($batch)->check($data)) { $this->error = $validate->getError(); if ($this->failException) { throw new ValidateException($this->error); @@ -935,9 +1208,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 注册回调方法 * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @param bool $override 是否覆盖 + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 * @return void */ public static function event($event, $callback, $override = false) @@ -952,8 +1225,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 触发事件 * @access protected - * @param string $event 事件名 - * @param mixed $params 传入参数(引用) + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) * @return bool */ protected function trigger($event, &$params) @@ -974,12 +1247,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 写入数据 * @access public - * @param array $data 数据数组 + * @param array $data 数据数组 + * @param array|true $field 允许字段 * @return $this */ - public static function create($data = []) + public static function create($data = [], $field = null) { $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } $model->isUpdate(false)->save($data, []); return $model; } @@ -987,13 +1264,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 更新数据 * @access public - * @param array $data 数据数组 - * @param array $where 更新条件 + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 * @return $this */ - public static function update($data = [], $where = []) + public static function update($data = [], $where = [], $field = null) { - $model = new static(); + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } $result = $model->isUpdate(true)->save($data, $where); return $model; } @@ -1009,6 +1290,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public static function get($data = null, $with = [], $cache = false) { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } $query = static::parseQuery($data, $with, $cache); return $query->find($data); } @@ -1024,6 +1309,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public static function all($data = null, $with = [], $cache = false) { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } $query = static::parseQuery($data, $with, $cache); return $query->select($data); } @@ -1031,9 +1320,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 分析查询表达式 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 * @return Query */ protected static function parseQuery(&$data, $with, $cache) @@ -1085,9 +1374,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 命名范围 * @access public - * @param string|array|Closure $name 命名范围名称 逗号分隔 - * @param mixed ...$params 参数调用 - * @return Model + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Model|Query */ public static function scope($name) { @@ -1115,72 +1404,45 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置是否使用全局查询范围 - * @param bool $use 是否启用全局查询范围 + * @param bool $use 是否启用全局查询范围 * @access public * @return Model */ public static function useGlobalScope($use) { - $model = new static(); - $model->useGlobalScope = $use; + $model = new static(); + static::$db = $model->db($use); return $model; } /** * 根据关联条件查询当前模型 * @access public - * @param string $relation 关联方法名 - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Model + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Relation|Query */ public static function has($relation, $operator = '>=', $count = 1, $id = '*') { - $model = new static(); - $info = $model->$relation()->getRelationInfo(); - $table = $info['model']::getTable(); - switch ($info['type']) { - case Relation::HAS_MANY: - return $model->db()->alias('a') - ->join($table . ' b', 'a.' . $info['localKey'] . '=b.' . $info['foreignKey'], $info['joinType']) - ->group('b.' . $info['foreignKey']) - ->having('count(' . $id . ')' . $operator . $count); - case Relation::HAS_MANY_THROUGH: - // TODO + $relation = (new static())->$relation(); + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); } + return $relation->has($operator, $count, $id); } /** * 根据关联条件查询当前模型 * @access public - * @param string $relation 关联方法名 - * @param mixed $where 查询条件(数组或者闭包) - * @return Model + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @return Relation|Query */ public static function hasWhere($relation, $where = []) { - $model = new static(); - $info = $model->$relation()->getRelationInfo(); - switch ($info['type']) { - case Relation::HAS_ONE: - case Relation::HAS_MANY: - $table = $info['model']::getTable(); - if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where['b.' . $key] = $val; - unset($where[$key]); - } - } - } - return $model->db()->alias('a') - ->field('a.*') - ->join($table . ' b', 'a.' . $info['localKey'] . '=b.' . $info['foreignKey'], $info['joinType']) - ->where($where); - case Relation::HAS_MANY_THROUGH: - // TODO - } + return (new static())->$relation()->hasWhere($where); } /** @@ -1211,9 +1473,23 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_string($relations)) { $relations = explode(',', $relations); } - $this->relation(); - foreach ($relations as $relation) { - $this->data[$relation] = $this->relation->getRelation($relation); + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); } return $this; } @@ -1221,130 +1497,257 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 预载入关联查询 返回数据集 * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 + * @param array $resultSet 数据集 + * @param string $relation 关联名 * @return array */ - public function eagerlyResultSet($resultSet, $relation) - { - return $this->relation()->eagerlyResultSet($resultSet, $relation); + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } } /** * 预载入关联查询 返回模型对象 * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 + * @param Model $result 数据对象 + * @param string $relation 关联名 * @return Model */ - public function eagerlyResult($result, $relation) + public function eagerlyResult(&$result, $relation) { - return $this->relation()->eagerlyResult($result, $relation); + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param string|array $relation 关联名 + * @return void + */ + public function relationCount(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure); + if (!isset($name)) { + $name = Loader::parseName($relation) . '_count'; + } + $result->setAttr($name, $count); + } + } + + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; } /** * HAS ONE 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return Relation + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return HasOne */ public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') { // 记录当前关联信息 $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->hasOne($model, $foreignKey, $localKey, $alias, $joinType); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); } /** * BELONGS TO 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $otherKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return Relation + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return BelongsTo */ - public function belongsTo($model, $foreignKey = '', $otherKey = '', $alias = [], $joinType = 'INNER') + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') { // 记录当前关联信息 $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: Loader::parseName(basename(str_replace('\\', '/', $model))) . '_id'; - $otherKey = $otherKey ?: (new $model)->getPk(); - return $this->relation()->belongsTo($model, $foreignKey, $otherKey, $alias, $joinType); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType); } /** * HAS MANY 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return Relation + * @param string $localKey 关联主键 + * @return HasMany */ - public function hasMany($model, $foreignKey = '', $localKey = '', $alias = []) + public function hasMany($model, $foreignKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->hasMany($model, $foreignKey, $localKey, $alias); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasMany($this, $model, $foreignKey, $localKey); } /** * HAS MANY 远程关联定义 * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 + * @param string $model 模型名 + * @param string $through 中间模型名 * @param string $foreignKey 关联外键 * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return Relation + * @param string $localKey 关联主键 + * @return HasManyThrough */ - public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '', $alias = []) + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $through = $this->parseModel($through); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - $name = Loader::parseName(basename(str_replace('\\', '/', $through))); - $throughKey = $throughKey ?: $name . '_id'; - return $this->relation()->hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); } /** * BELONGS TO MANY 关联定义 * @access public - * @param string $model 模型名 - * @param string $table 中间表名 + * @param string $model 模型名 + * @param string $table 中间表名 * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 - * @return Relation + * @param string $localKey 当前模型关联键 + * @return BelongsToMany */ - public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '', $alias = []) + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $name = Loader::parseName(basename(str_replace('\\', '/', $model))); - $table = $table ?: $this->db()->getTable(Loader::parseName($this->name) . '_' . $name); + $table = $table ?: $this->db(false)->getTable(Loader::parseName($this->name) . '_' . $name); $foreignKey = $foreignKey ?: $name . '_id'; - $localKey = $localKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->belongsToMany($model, $table, $foreignKey, $localKey, $alias); + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: Loader::parseName($this->name); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphTo($this, $morphType, $foreignKey, $alias); } public function __call($method, $args) { - $query = $this->db(); + if (isset(static::$db)) { + $query = static::$db; + static::$db = null; + } else { + $query = $this->db(); + } + if (method_exists($this, 'scope' . $method)) { // 动态调用命名范围 $method = 'scope' . $method; @@ -1358,24 +1761,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public static function __callStatic($method, $params) { - $query = self::getDb(); - return call_user_func_array([$query, $method], $params); - } - - protected static function getDb() - { - $model = get_called_class(); - if (!isset(self::$links[$model])) { - self::$links[$model] = (new static())->db(); + if (isset(static::$db)) { + $query = static::$db; + static::$db = null; + } else { + $query = (new static())->db(); } - return self::$links[$model]; + + return call_user_func_array([$query, $method], $params); } /** * 修改器 设置数据对象的值 * @access public - * @param string $name 名称 - * @param mixed $value 值 + * @param string $name 名称 + * @param mixed $value 值 * @return void */ public function __set($name, $value) @@ -1466,4 +1866,49 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->initialize(); } + /** + * 模型事件快捷方法 + * @param $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + } diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 7385ebb0..0c1bea8a 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,15 +11,19 @@ namespace think; -use think\paginator\Collection as PaginatorCollection; -use think\Request; +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; -abstract class Paginator +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { /** @var bool 是否为简洁模式 */ protected $simple = false; - /** @var PaginatorCollection 数据集 */ + /** @var Collection 数据集 */ protected $items; /** @var integer 当前页 */ @@ -42,33 +46,33 @@ abstract class Paginator 'var_page' => 'page', 'path' => '/', 'query' => [], - 'fragment' => '' + 'fragment' => '', ]; - protected function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { $this->options = array_merge($this->options, $options); - $this->options['path'] = $this->options['path'] != '/' ? rtrim($this->options['path'], '/') : $this->options['path']; + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; $this->simple = $simple; $this->listRows = $listRows; + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + if ($simple) { - if (!$items instanceof Collection) { - $items = Collection::make($items); - } $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = count($items) > ($this->listRows); $items = $items->slice(0, $this->listRows); } else { $this->total = $total; - $this->lastPage = (int)ceil($total / $listRows); + $this->lastPage = (int) ceil($total / $listRows); $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = $this->currentPage < $this->lastPage; } - - $this->items = PaginatorCollection::make($items, $this); + $this->items = $items; } /** @@ -78,12 +82,11 @@ abstract class Paginator * @param bool $simple * @param null $total * @param array $options - * @return PaginatorCollection + * @return Paginator */ public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { - $paginator = new static($items, $listRows, $currentPage, $total, $simple, $options); - return $paginator->items; + return new static($items, $listRows, $currentPage, $total, $simple, $options); } protected function setCurrentPage($currentPage) @@ -134,7 +137,7 @@ abstract class Paginator { $page = Request::instance()->request($varPage); - if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) { + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { return $page; } @@ -182,7 +185,7 @@ abstract class Paginator */ public function hasPages() { - return !($this->currentPage == 1 && !$this->hasMore); + return !(1 == $this->currentPage && !$this->hasMore); } /** @@ -239,7 +242,6 @@ abstract class Paginator return $this; } - /** * 构造锚点字符串 * @@ -255,4 +257,113 @@ abstract class Paginator * @return mixed */ abstract public function render(); -} \ No newline at end of file + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Retrieve an external iterator + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + try { + $total = $this->total(); + } catch (Exception $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray() + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + return call_user_func_array([$this->getCollection(), $name], $arguments); + } + +} diff --git a/library/think/Process.php b/library/think/Process.php index 1982de24..6f3faa31 100644 --- a/library/think/Process.php +++ b/library/think/Process.php @@ -14,9 +14,9 @@ namespace think; use think\process\exception\Failed as ProcessFailedException; use think\process\exception\Timeout as ProcessTimeoutException; use think\process\pipes\Pipes; -use think\process\Utils; use think\process\pipes\Unix as UnixPipes; use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; class Process { @@ -47,10 +47,10 @@ class Process private $exitcode; private $fallbackExitcode; private $processInformation; - private $outputDisabled = false; + private $outputDisabled = false; private $stdout; private $stderr; - private $enhanceWindowsCompatibility = true; + private $enhanceWindowsCompatibility = true; private $enhanceSigchildCompatibility; private $process; private $status = self::STATUS_READY; @@ -147,7 +147,7 @@ class Process $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); $this->options = array_replace([ 'suppress_errors' => true, - 'binary_pipes' => true + 'binary_pipes' => true, ], $options); } @@ -490,7 +490,7 @@ class Process public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { - return null; + return; } return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; @@ -586,7 +586,7 @@ class Process */ public function isStarted() { - return $this->status != self::STATUS_READY; + return self::STATUS_READY != $this->status; } /** @@ -597,7 +597,7 @@ class Process { $this->updateStatus(false); - return $this->status == self::STATUS_TERMINATED; + return self::STATUS_TERMINATED == $this->status; } /** @@ -645,7 +645,7 @@ class Process * @param string $line */ public function addOutput($line) - { +{ $this->lastOutputTime = microtime(true); $this->stdout .= $line; } @@ -655,7 +655,7 @@ class Process * @param string $line */ public function addErrorOutput($line) - { +{ $this->lastOutputTime = microtime(true); $this->stderr .= $line; } @@ -665,7 +665,7 @@ class Process * @return string */ public function getCommandLine() - { +{ return $this->commandline; } @@ -675,7 +675,7 @@ class Process * @return self */ public function setCommandLine($commandline) - { +{ $this->commandline = $commandline; return $this; @@ -686,7 +686,7 @@ class Process * @return float|null */ public function getTimeout() - { +{ return $this->timeout; } @@ -695,7 +695,7 @@ class Process * @return float|null */ public function getIdleTimeout() - { +{ return $this->idleTimeout; } @@ -705,7 +705,7 @@ class Process * @return self */ public function setTimeout($timeout) - { +{ $this->timeout = $this->validateTimeout($timeout); return $this; @@ -717,7 +717,7 @@ class Process * @return self */ public function setIdleTimeout($timeout) - { +{ if (null !== $timeout && $this->outputDisabled) { throw new \LogicException('Idle timeout can not be set while the output is disabled.'); } @@ -733,7 +733,7 @@ class Process * @return self */ public function setTty($tty) - { +{ if ('\\' === DS && $tty) { throw new \RuntimeException('TTY mode is not supported on Windows platform.'); } @@ -741,7 +741,7 @@ class Process throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); } - $this->tty = (bool)$tty; + $this->tty = (bool) $tty; return $this; } @@ -751,7 +751,7 @@ class Process * @return bool */ public function isTty() - { +{ return $this->tty; } @@ -761,8 +761,8 @@ class Process * @return self */ public function setPty($bool) - { - $this->pty = (bool)$bool; +{ + $this->pty = (bool) $bool; return $this; } @@ -772,7 +772,7 @@ class Process * @return bool */ public function isPty() - { +{ return $this->pty; } @@ -781,7 +781,7 @@ class Process * @return string|null */ public function getWorkingDirectory() - { +{ if (null === $this->cwd) { return getcwd() ?: null; } @@ -795,7 +795,7 @@ class Process * @return self */ public function setWorkingDirectory($cwd) - { +{ $this->cwd = $cwd; return $this; @@ -806,7 +806,7 @@ class Process * @return array */ public function getEnv() - { +{ return $this->env; } @@ -816,14 +816,14 @@ class Process * @return self */ public function setEnv(array $env) - { +{ $env = array_filter($env, function ($value) { return !is_array($value); }); $this->env = []; foreach ($env as $key => $value) { - $this->env[(binary)$key] = (binary)$value; + $this->env[(binary) $key] = (binary) $value; } return $this; @@ -834,7 +834,7 @@ class Process * @return null|string */ public function getInput() - { +{ return $this->input; } @@ -844,7 +844,7 @@ class Process * @return self */ public function setInput($input) - { +{ if ($this->isRunning()) { throw new \LogicException('Input can not be set while the process is running.'); } @@ -859,7 +859,7 @@ class Process * @return array */ public function getOptions() - { +{ return $this->options; } @@ -869,7 +869,7 @@ class Process * @return self */ public function setOptions(array $options) - { +{ $this->options = $options; return $this; @@ -880,7 +880,7 @@ class Process * @return bool */ public function getEnhanceWindowsCompatibility() - { +{ return $this->enhanceWindowsCompatibility; } @@ -890,8 +890,8 @@ class Process * @return self */ public function setEnhanceWindowsCompatibility($enhance) - { - $this->enhanceWindowsCompatibility = (bool)$enhance; +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; return $this; } @@ -901,7 +901,7 @@ class Process * @return bool */ public function getEnhanceSigchildCompatibility() - { +{ return $this->enhanceSigchildCompatibility; } @@ -911,8 +911,8 @@ class Process * @return self */ public function setEnhanceSigchildCompatibility($enhance) - { - $this->enhanceSigchildCompatibility = (bool)$enhance; +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; return $this; } @@ -921,8 +921,8 @@ class Process * 是否超时 */ public function checkTimeout() - { - if ($this->status !== self::STATUS_STARTED) { +{ + if (self::STATUS_STARTED !== $this->status) { return; } @@ -944,7 +944,7 @@ class Process * @return bool */ public static function isPtySupported() - { +{ static $result; if (null !== $result) { @@ -970,7 +970,7 @@ class Process * @return array */ private function getDescriptors() - { +{ if ('\\' === DS) { $this->processPipes = WindowsPipes::create($this, $this->input); } else { @@ -994,7 +994,7 @@ class Process * @return callable */ protected function buildCallback($callback) - { +{ $out = self::OUT; $callback = function ($type, $data) use ($callback, $out) { if ($out == $type) { @@ -1016,7 +1016,7 @@ class Process * @param bool $blocking */ protected function updateStatus($blocking) - { +{ if (self::STATUS_STARTED !== $this->status) { return; } @@ -1036,7 +1036,7 @@ class Process * @return bool */ protected function isSigchildEnabled() - { +{ if (null !== self::$sigchild) { return self::$sigchild; } @@ -1057,8 +1057,8 @@ class Process * @return float|null */ private function validateTimeout($timeout) - { - $timeout = (float)$timeout; +{ + $timeout = (float) $timeout; if (0.0 === $timeout) { $timeout = null; @@ -1075,15 +1075,15 @@ class Process * @param bool $close */ private function readPipes($blocking, $close) - { +{ $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 == $type) { - $this->fallbackExitcode = (int)$data; + $this->fallbackExitcode = (int) $data; } else { - $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); } } } @@ -1092,7 +1092,7 @@ class Process * 捕获退出码 */ private function captureExitCode() - { +{ if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { $this->exitcode = $this->processInformation['exitcode']; } @@ -1103,7 +1103,7 @@ class Process * @return int 退出码 */ private function close() - { +{ $this->processPipes->close(); if (is_resource($this->process)) { $exitcode = proc_close($this->process); @@ -1117,7 +1117,7 @@ class Process if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { $this->exitcode = $this->fallbackExitcode; } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] - && 0 < $this->processInformation['termsig'] + && 0 < $this->processInformation['termsig'] ) { $this->exitcode = 128 + $this->processInformation['termsig']; } @@ -1129,7 +1129,7 @@ class Process * 重置数据 */ private function resetProcessData() - { +{ $this->starttime = null; $this->callback = null; $this->exitcode = null; @@ -1151,7 +1151,7 @@ class Process * @return bool */ private function doSignal($signal, $throwException) - { +{ if (!$this->isRunning()) { if ($throwException) { throw new \LogicException('Can not send signal on a non running process.'); @@ -1186,7 +1186,7 @@ class Process * @param string $functionName */ private function requireProcessIsStarted($functionName) - { +{ if (!$this->isStarted()) { throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); } @@ -1197,9 +1197,9 @@ class Process * @param string $functionName */ private function requireProcessIsTerminated($functionName) - { +{ if (!$this->isTerminated()) { throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); } } -} \ No newline at end of file +} diff --git a/library/think/Request.php b/library/think/Request.php index 077e190d..9c8ae276 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,11 +11,6 @@ namespace think; -use think\Config; -use think\Exception; -use think\File; -use think\Session; - class Request { /** @@ -25,7 +20,7 @@ class Request protected $method; /** - * @var string 域名 + * @var string 域名(含协议和端口) */ protected $domain; @@ -121,13 +116,15 @@ class Request protected $input; // 请求缓存 protected $cache; + // 缓存是否检查 + protected $isCheckCache; /** * 架构函数 - * @access public + * @access protected * @param array $options 参数 */ - public function __construct($options = []) + protected function __construct($options = []) { foreach ($options as $name => $item) { if (property_exists($this, $name)) { @@ -224,8 +221,9 @@ class Request if (!isset($info['path'])) { $info['path'] = '/'; } - $options = []; - $queryString = ''; + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; if (isset($info['query'])) { parse_str(html_entity_decode($info['query']), $query); if (!empty($params)) { @@ -238,6 +236,11 @@ class Request } elseif (!empty($params)) { $queryString = http_build_query($params, '', '&'); } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); $server['QUERY_STRING'] = $queryString; $options['cookie'] = $cookie; @@ -248,14 +251,14 @@ class Request $options['baseUrl'] = $info['path']; $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); $options['method'] = $server['REQUEST_METHOD']; - $options['domain'] = $info['scheme'] . '://' . $server['HTTP_HOST']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; $options['content'] = $content; self::$instance = new self($options); return self::$instance; } /** - * 获取当前包含协议的域名 + * 设置或获取当前包含协议的域名 * @access public * @param string $domain 域名 * @return string @@ -272,7 +275,7 @@ class Request } /** - * 获取当前完整URL 包括QUERY_STRING + * 设置或获取当前完整URL 包括QUERY_STRING * @access public * @param string|true $url URL地址 true 带域名获取 * @return string @@ -299,7 +302,7 @@ class Request } /** - * 获取当前URL 不含QUERY_STRING + * 设置或获取当前URL 不含QUERY_STRING * @access public * @param string $url URL地址 * @return string @@ -317,7 +320,7 @@ class Request } /** - * 获取当前执行的文件 SCRIPT_NAME + * 设置或获取当前执行的文件 SCRIPT_NAME * @access public * @param string $file 当前执行的文件 * @return string @@ -349,7 +352,7 @@ class Request } /** - * 获取URL访问根地址 + * 设置或获取URL访问根地址 * @access public * @param string $url URL地址 * @return string @@ -453,7 +456,7 @@ class Request */ public function type() { - $accept = isset($this->server['HTTP_ACCEPT']) ? $this->server['HTTP_ACCEPT'] : $_SERVER['HTTP_ACCEPT']; + $accept = $this->server('HTTP_ACCEPT'); if (empty($accept)) { return false; } @@ -600,14 +603,14 @@ class Request } /** - * 设置获取获取当前请求的参数 + * 获取获取当前请求的参数 * @access public * @param string|array $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed */ - public function param($name = '', $default = null, $filter = null) + public function param($name = '', $default = null, $filter = '') { if (empty($this->param)) { $method = $this->method(true); @@ -644,7 +647,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function route($name = '', $default = null, $filter = null) + public function route($name = '', $default = null, $filter = '') { if (is_array($name)) { $this->param = []; @@ -661,7 +664,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function get($name = '', $default = null, $filter = null) + public function get($name = '', $default = null, $filter = '') { if (empty($this->get)) { $this->get = $_GET; @@ -681,10 +684,15 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function post($name = '', $default = null, $filter = null) + public function post($name = '', $default = null, $filter = '') { if (empty($this->post)) { - $this->post = $_POST; + $content = $this->input; + if (empty($_POST) && 'application/json' == $this->contentType()) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } } if (is_array($name)) { $this->param = []; @@ -701,12 +709,12 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function put($name = '', $default = null, $filter = null) + public function put($name = '', $default = null, $filter = '') { if (is_null($this->put)) { $content = $this->input; - if (strpos($content, '":')) { - $this->put = json_decode($content, true); + if ('application/json' == $this->contentType()) { + $this->put = (array) json_decode($content, true); } else { parse_str($content, $this->put); } @@ -727,7 +735,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function delete($name = '', $default = null, $filter = null) + public function delete($name = '', $default = null, $filter = '') { return $this->put($name, $default, $filter); } @@ -740,7 +748,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function patch($name = '', $default = null, $filter = null) + public function patch($name = '', $default = null, $filter = '') { return $this->put($name, $default, $filter); } @@ -752,7 +760,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function request($name = '', $default = null, $filter = null) + public function request($name = '', $default = null, $filter = '') { if (empty($this->request)) { $this->request = $_REQUEST; @@ -772,7 +780,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function session($name = '', $default = null, $filter = null) + public function session($name = '', $default = null, $filter = '') { if (empty($this->session)) { $this->session = Session::get(); @@ -791,7 +799,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function cookie($name = '', $default = null, $filter = null) + public function cookie($name = '', $default = null, $filter = '') { if (empty($this->cookie)) { $this->cookie = $_COOKIE; @@ -810,7 +818,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function server($name = '', $default = null, $filter = null) + public function server($name = '', $default = null, $filter = '') { if (empty($this->server)) { $this->server = $_SERVER; @@ -845,7 +853,7 @@ class Request $keys = array_keys($file); $count = count($file['name']); for ($i = 0; $i < $count; $i++) { - if (empty($file['tmp_name'][$i])) { + if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { continue; } $temp['key'] = $key; @@ -859,7 +867,7 @@ class Request if ($file instanceof File) { $array[$key] = $file; } else { - if (empty($file['tmp_name'])) { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { continue; } $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); @@ -878,7 +886,7 @@ class Request return $array[$name]; } } - return null; + return; } /** @@ -888,7 +896,7 @@ class Request * @param string|array $filter 过滤方法 * @return mixed */ - public function env($name = '', $default = null, $filter = null) + public function env($name = '', $default = null, $filter = '') { if (empty($this->env)) { $this->env = $_ENV; @@ -910,18 +918,22 @@ class Request { if (empty($this->header)) { $header = []; - $server = $this->server ?: $_SERVER; - foreach ($server as $key => $val) { - if (0 === strpos($key, 'HTTP_')) { - $key = str_replace('_', '-', strtolower(substr($key, 5))); - $header[$key] = $val; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server ?: $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; } - } - if (isset($server['CONTENT_TYPE'])) { - $header['content-type'] = $server['CONTENT_TYPE']; - } - if (isset($server['CONTENT_LENGTH'])) { - $header['content-length'] = $server['CONTENT_LENGTH']; } $this->header = array_change_key_case($header); } @@ -943,7 +955,7 @@ class Request * @param string|array $filter 过滤函数 * @return mixed */ - public function input($data = [], $name = '', $default = null, $filter = null) + public function input($data = [], $name = '', $default = null, $filter = '') { if (false === $name) { // 获取原始数据 @@ -972,13 +984,17 @@ class Request } // 解析过滤器 - $filter = $filter ?: $this->filter; - - if (is_string($filter)) { - $filter = explode(',', $filter); + if (is_null($filter)) { + $filter = []; } else { - $filter = (array) $filter; + $filter = $filter ?: $this->filter; + if (is_string($filter)) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } } + $filter[] = $default; if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); @@ -1237,8 +1253,7 @@ class Request if (false !== $pos) { unset($arr[$pos]); } - - $ip = trim($arr[0]); + $ip = trim(current($arr)); } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif (isset($_SERVER['REMOTE_ADDR'])) { @@ -1249,7 +1264,7 @@ class Request } // IP地址合法验证 $long = sprintf("%u", ip2long($ip)); - $ip = $long ? array($ip, $long) : array('0.0.0.0', 0); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; return $ip[$type]; } @@ -1333,6 +1348,21 @@ class Request return $this->server('REMOTE_PORT'); } + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + list($type) = explode(';', $contentType); + return trim($type); + } + return ''; + } + /** * 获取当前请求的路由信息 * @access public @@ -1366,7 +1396,7 @@ class Request * 设置或者获取当前的模块名 * @access public * @param string $module 模块名 - * @return string|$this + * @return string|Request */ public function module($module = null) { @@ -1382,7 +1412,7 @@ class Request * 设置或者获取当前的控制器名 * @access public * @param string $controller 控制器名 - * @return string|$this + * @return string|Request */ public function controller($controller = null) { @@ -1398,7 +1428,7 @@ class Request * 设置或者获取当前的操作名 * @access public * @param string $action 操作名 - * @return string + * @return string|Request */ public function action($action = null) { @@ -1414,7 +1444,7 @@ class Request * 设置或者获取当前的语言 * @access public * @param string $lang 语言名 - * @return string + * @return string|Request */ public function langset($lang = null) { @@ -1468,15 +1498,40 @@ class Request } /** - * 读取或者设置缓存 + * 设置当前地址的请求缓存 * @access public * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id * @param mixed $expire 缓存有效期 - * @return mixed + * @param array $except 缓存排除 + * @return void */ - public function cache($key, $expire = null) + public function cache($key, $expire = null, $except = []) { - if ($this->isGet()) { + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === strpos($this->url(), $rule)) { + return; + } + } + // 自动缓存功能 + $key = md5($this->host()) . '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url())], $key); + } + if (false !== strpos($key, ':')) { $param = $this->param(); foreach ($param as $item => $val) { @@ -1484,9 +1539,6 @@ class Request $key = str_replace(':' . $item, $val, $key); } } - } elseif ('__URL__' == $key) { - // 当前URL地址作为缓存标识 - $key = md5($this->url()); } elseif (strpos($key, ']')) { if ('[' . $this->ext() . ']' == $key) { // 缓存某个后缀的请求 @@ -1495,6 +1547,9 @@ class Request return; } } + if (isset($fun)) { + $key = $fun($key); + } if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { // 读取缓存 @@ -1511,7 +1566,7 @@ class Request } /** - * 读取缓存设置 + * 读取请求缓存设置 * @access public * @return array */ diff --git a/library/think/Response.php b/library/think/Response.php index 8b62e419..656198f0 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,11 +11,6 @@ namespace think; -use think\Cache; -use think\Config; -use think\Debug; -use think\Env; -use think\Request; use think\response\Json as JsonResponse; use think\response\Jsonp as JsonpResponse; use think\response\Redirect as RedirectResponse; @@ -103,6 +98,16 @@ class Response Debug::inject($this, $data); } + if (200 == $this->code) { + $cache = Request::instance()->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::set($cache[0], [$data, $this->header], $cache[1]); + } + } + if (!headers_sent() && !empty($this->header)) { // 发送状态码 http_response_code($this->code); @@ -111,16 +116,7 @@ class Response header($name . ':' . $val); } } - if (200 == $this->code) { - $cache = Request::instance()->getCache(); - if ($cache) { - header('Cache-Control: max-age=' . $cache[1] . ',must-revalidate'); - header('Last-Modified:' . gmdate('D, d M Y H:i:s') . ' GMT'); - header('Expires:' . gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'); - $header['Content-Type'] = $this->header['Content-Type']; - Cache::set($cache[0], [$data, $header], $cache[1]); - } - } + echo $data; if (function_exists('fastcgi_finish_request')) { @@ -130,6 +126,11 @@ class Response // 监听response_end Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } } /** @@ -278,7 +279,11 @@ class Response */ public function getHeader($name = '') { - return !empty($name) ? $this->header[$name] : $this->header; + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } } /** diff --git a/library/think/Route.php b/library/think/Route.php index c18a2d67..679a1372 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,26 +11,19 @@ namespace think; -use think\App; -use think\Config; use think\exception\HttpException; -use think\Hook; -use think\Loader; -use think\Log; -use think\Request; -use think\Response; class Route { // 路由规则 private static $rules = [ - 'GET' => [], - 'POST' => [], - 'PUT' => [], - 'DELETE' => [], - 'PATCH' => [], - 'HEAD' => [], - 'OPTIONS' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], '*' => [], 'alias' => [], 'domain' => [], @@ -40,21 +33,22 @@ class Route // REST路由操作方法定义 private static $rest = [ - 'index' => ['GET', '', 'index'], - 'create' => ['GET', '/create', 'create'], - 'edit' => ['GET', '/:id/edit', 'edit'], - 'read' => ['GET', '/:id', 'read'], - 'save' => ['POST', '', 'save'], - 'update' => ['PUT', '/:id', 'update'], - 'delete' => ['DELETE', '/:id', 'delete'], + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '/:id/edit', 'edit'], + 'read' => ['get', '/:id', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/:id', 'update'], + 'delete' => ['delete', '/:id', 'delete'], ]; // 不同请求类型的方法前缀 private static $methodPrefix = [ - 'GET' => 'get', - 'POST' => 'post', - 'PUT' => 'put', - 'DELETE' => 'delete', + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', ]; // 子域名 @@ -68,6 +62,8 @@ class Route private static $domainRule; // 当前域名 private static $domain; + // 当前路由执行过程中的参数 + private static $option = []; /** * 注册变量规则 @@ -126,7 +122,7 @@ class Route * 设置路由绑定 * @access public * @param mixed $bind 绑定信息 - * @param string $type 绑定类型 默认为module 支持 namespace class + * @param string $type 绑定类型 默认为module 支持 namespace class controller * @return mixed */ public static function bind($bind, $type = 'module') @@ -148,8 +144,9 @@ class Route } elseif ('' === $name) { return self::$rules['name']; } elseif (!is_null($value)) { - self::$rules['name'][$name][] = $value; + self::$rules['name'][strtolower($name)][] = $value; } else { + $name = strtolower($name); return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; } } @@ -198,7 +195,7 @@ class Route unset($rule['__rest__']); } - self::registerRules($rule, strtoupper($type)); + self::registerRules($rule, strtolower($type)); } // 批量注册路由 @@ -241,7 +238,7 @@ class Route $pattern = array_merge(self::getGroup('pattern'), $pattern); } - $type = strtoupper($type); + $type = strtolower($type); if (strpos($type, '|')) { $option['method'] = $type; @@ -292,20 +289,24 @@ class Route } elseif ('$' == substr($rule, -1, 1)) { // 是否完整匹配 $option['complete_match'] = true; - $rule = substr($rule, 0, -1); } } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { // 是否完整匹配 $option['complete_match'] = true; - $rule = substr($rule, 0, -1); } - if ('/' != $rule) { + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { $rule = trim($rule, '/'); } $vars = self::parseVar($rule); if (isset($name)) { - self::name($name, [$rule, $vars, self::$domain]); + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + self::name($name, [$key, $vars, self::$domain, $suffix]); } if ($group) { if ('*' != $type) { @@ -327,7 +328,7 @@ class Route } if ('*' == $type) { // 注册路由快捷方式 - foreach (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as $method) { + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { if (self::$domain) { self::$rules['domain'][self::$domain][$method][$rule] = true; } else { @@ -338,6 +339,27 @@ class Route } } + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected static function setOption($options = []) + { + self::$option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public static function getOption() + { + return self::$option; + } + /** * 获取当前的分组信息 * @access public @@ -422,15 +444,17 @@ class Route $options['complete_match'] = true; $key = substr($key, 0, -1); } + $key = trim($key, '/'); $vars = self::parseVar($key); $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; // 设置路由标识 - self::name($route, [$key, $vars, self::$domain]); + $suffix = isset($options['ext']) ? $options['ext'] : null; + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); } self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; } - foreach (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as $method) { + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { if (!isset(self::$rules[$method][$name])) { self::$rules[$method][$name] = true; } elseif (is_array(self::$rules[$method][$name])) { @@ -575,7 +599,8 @@ class Route } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); } - $item = ltrim($rule . $val[1], '/'); + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); } } @@ -624,23 +649,23 @@ class Route public static function setMethodPrefix($method, $prefix = '') { if (is_array($method)) { - self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method, CASE_UPPER)); + self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); } else { - self::$methodPrefix[strtoupper($method)] = $prefix; + self::$methodPrefix[strtolower($method)] = $prefix; } } /** * rest方法定义和修改 * @access public - * @param string $name 方法名称 - * @param array $resourece 资源 + * @param string $name 方法名称 + * @param array|bool $resource 资源 * @return void */ public static function rest($name, $resource = []) { if (is_array($name)) { - self::$rest = array_merge(self::$rest, $name); + self::$rest = $resource ? $name : array_merge(self::$rest, $name); } else { self::$rest[$name] = $resource; } @@ -681,7 +706,7 @@ class Route if (is_array($rules)) { self::$rules = $rules; } elseif ($rules) { - return true === $rules ? self::$rules : self::$rules[$rules]; + return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; } else { $rules = self::$rules; unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); @@ -697,7 +722,7 @@ class Route * @param string $method 请求类型 * @return void */ - public static function checkDomain($request, &$currentRules, $method = 'GET') + public static function checkDomain($request, &$currentRules, $method = 'get') { // 域名规则 $rules = self::$rules['domain']; @@ -742,6 +767,10 @@ class Route } } if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } if (isset($item['[bind]'])) { // 解析子域名部署规则 list($rule, $option, $pattern) = $item['[bind]']; @@ -798,18 +827,16 @@ class Route public static function check($request, $url, $depr = '/', $checkDomain = false) { // 分隔符替换 确保路由定义使用统一的分隔符 - if ('/' != $depr) { - $url = str_replace($depr, '/', $url); - } + $url = str_replace($depr, '|', $url); - if (strpos($url, '/') && isset(self::$rules['alias'][strstr($url, '/', true)])) { + if (strpos($url, '|') && isset(self::$rules['alias'][strstr($url, '|', true)])) { // 检测路由别名 $result = self::checkRouteAlias($request, $url, $depr); if (false !== $result) { return $result; } } - $method = $request->method(); + $method = strtolower($request->method()); // 获取当前请求类型的路由规则 $rules = self::$rules[$method]; // 检测域名部署 @@ -821,17 +848,19 @@ class Route if (false !== $return) { return $return; } - if ('/' != $url) { - $url = rtrim($url, '/'); + if ('|' != $url) { + $url = rtrim($url, '|'); } - if (isset($rules[$url])) { + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { // 静态路由规则检测 - $rule = $rules[$url]; + $rule = $rules[$item]; if (true === $rule) { - $rule = self::getRouteExpress($url); + $rule = self::getRouteExpress($item); } - if (!empty($rule['route']) && self::checkOption($rule['option'], $url, $request)) { - return self::parseRule($url, $rule['route'], $url, $rule['option']); + if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { + self::setOption($rule['option']); + return self::parseRule($item, $rule['route'], $url, $rule['option']); } } @@ -874,7 +903,7 @@ class Route $pattern = $item['pattern']; // 检查参数有效性 - if (!self::checkOption($option, $url, $request)) { + if (!self::checkOption($option, $request)) { continue; } @@ -891,10 +920,10 @@ class Route } else { $str = $key; } - if (is_string($str) && $str && 0 !== strpos($url, $str)) { + if (is_string($str) && $str && 0 !== strpos(str_replace('|', '/', $url), $str)) { continue; } - + self::setOption($option); $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); if (false !== $result) { return $result; @@ -909,10 +938,12 @@ class Route if ($group) { $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); } + + self::setOption($option); if (isset($options['bind_model']) && isset($option['bind_model'])) { $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); } - $result = self::checkRule($rule, $route, $url, $pattern, $option); + $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); if (false !== $result) { return $result; } @@ -938,27 +969,40 @@ class Route */ private static function checkRouteAlias($request, $url, $depr) { - $array = explode('/', $url, 2); - $item = self::$rules['alias'][$array[0]]; + $array = explode('|', $url); + $alias = array_shift($array); + $item = self::$rules['alias'][$alias]; if (is_array($item)) { list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } } else { $rule = $item; } + $bind = implode('|', $array); // 参数有效性检查 - if (isset($option) && !self::checkOption($option, $url, $request)) { + if (isset($option) && !self::checkOption($option, $request)) { // 路由不匹配 return false; } elseif (0 === strpos($rule, '\\')) { // 路由到类 - return self::bindToClass($array[1], substr($rule, 1), $depr); - } elseif (0 === strpos($url, '@')) { + return self::bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { // 路由到控制器类 - return self::bindToController($array[1], substr($rule, 1), $depr); + return self::bindToController($bind, substr($rule, 1), $depr); } else { // 路由到模块/控制器 - return self::bindToModule($array[1], $rule, $depr); + return self::bindToModule($bind, $rule, $depr); } } @@ -982,6 +1026,9 @@ class Route case 'class': // 绑定到类 return self::bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return self::bindToController($url, $bind, $depr); case 'namespace': // 绑定到命名空间 return self::bindToNamespace($url, $bind, $depr); @@ -1000,7 +1047,8 @@ class Route */ public static function bindToClass($url, $class, $depr = '/') { - $array = explode($depr, $url, 2); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); @@ -1018,13 +1066,14 @@ class Route */ public static function bindToNamespace($url, $namespace, $depr = '/') { - $array = explode($depr, $url, 3); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); if (!empty($array[2])) { self::parseUrlParams($array[2]); } - return ['type' => 'method', 'method' => [$namespace . '\\' . $class, $method]]; + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method]]; } /** @@ -1037,7 +1086,8 @@ class Route */ public static function bindToController($url, $controller, $depr = '/') { - $array = explode($depr, $url, 2); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); @@ -1049,13 +1099,14 @@ class Route * 绑定到模块/控制器 * @access public * @param string $url URL地址 - * @param string $class 控制器类名(带命名空间) + * @param string $controller 控制器类名(带命名空间) * @param string $depr URL分隔符 * @return array */ public static function bindToModule($url, $controller, $depr = '/') { - $array = explode($depr, $url, 2); + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); @@ -1067,19 +1118,22 @@ class Route * 路由参数有效性检查 * @access private * @param array $option 路由参数 - * @param string $url URL地址 * @param Request $request Request对象 * @return bool */ - private static function checkOption($option, $url, $request) + private static function checkOption($option, $request) { - // 请求类型检测 - if ((isset($option['method']) && false === stripos($option['method'], $request->method())) - || (isset($option['ext']) && false === stripos($option['ext'], $request->ext())) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos($option['deny_ext'], $request->ext())) + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 - || (!empty($option['https']) && !$request->isSsl()) // https检测 - || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'], '', $url)) // 行为检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 ) { return false; @@ -1095,24 +1149,28 @@ class Route * @param string $url URL地址 * @param array $pattern 变量规则 * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) * @return array|false */ - private static function checkRule($rule, $route, $url, $pattern, $option) + private static function checkRule($rule, $route, $url, $pattern, $option, $depr) { // 检查完整规则定义 - if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', $url)) { + if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { return false; } - // 检测是否设置了参数分隔符 - if ($depr = Config::get('url_params_depr')) { - $url = str_replace($depr, '/', $url); - $rule = str_replace($depr, '/', $rule); + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); } - $len1 = substr_count($url, '/'); + $len1 = substr_count($url, '|'); $len2 = substr_count($rule, '/'); // 多余参数是否合并 $merge = !empty($option['merge_extra_vars']) ? true : false; + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } if ($len1 >= $len2 || strpos($rule, '[')) { if (!empty($option['complete_match'])) { @@ -1140,12 +1198,14 @@ class Route */ public static function parseUrl($url, $depr = '/', $autoSearch = false) { + if (isset(self::$bind['module'])) { + $bind = str_replace('/', $depr, self::$bind['module']); // 如果有模块/控制器绑定 - $url = self::$bind['module'] . '/' . ltrim($url, '/'); + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); } - - list($path, $var) = self::parseUrlPath($url, $depr); + $url = str_replace($depr, '|', $url); + list($path, $var) = self::parseUrlPath($url); $route = [null, null, null]; if (isset($path)) { // 解析模块 @@ -1155,15 +1215,24 @@ class Route $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; $item = []; + $find = false; foreach ($path as $val) { - $item[] = array_shift($path); - if (is_file($dir . DS . $val . $suffix . EXT)) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; break; } else { - $dir .= DS . $val; + $dir .= DS . Loader::parseName($val); } } - $controller = implode('.', $item); + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } } else { // 解析控制器 $controller = !empty($path) ? array_shift($path) : null; @@ -1171,11 +1240,18 @@ class Route // 解析操作 $action = !empty($path) ? array_shift($path) : null; // 解析额外参数 - self::parseUrlParams(empty($path) ? '' : implode('/', $path)); + self::parseUrlParams(empty($path) ? '' : implode('|', $path)); // 封装路由 $route = [$module, $controller, $action]; - if (isset(self::$rules['name'][implode($depr, $route)])) { - throw new HttpException(404, 'invalid request:' . $url); + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); } } return ['type' => 'module', 'module' => $route]; @@ -1185,15 +1261,12 @@ class Route * 解析URL的pathinfo参数和变量 * @access private * @param string $url URL地址 - * @param string $depr URL分隔符 * @return array */ - private static function parseUrlPath($url, $depr = '/') + private static function parseUrlPath($url) { // 分隔符替换 确保路由定义使用统一的分隔符 - if ('/' != $depr) { - $url = str_replace($depr, '/', $url); - } + $url = str_replace('|', '/', $url); $url = trim($url, '/'); $var = []; if (false !== strpos($url, '?')) { @@ -1219,13 +1292,12 @@ class Route * @param string $url URL地址 * @param string $rule 路由规则 * @param array $pattern 变量规则 - * @param bool $merge 合并额外变量 * @return array|false */ - private static function match($url, $rule, $pattern, $merge) + private static function match($url, $rule, $pattern) { $m2 = explode('/', $rule); - $m1 = $merge ? explode('/', $url, count($m2)) : explode('/', $url); + $m1 = explode('|', $url); $var = []; foreach ($m2 as $key => $val) { @@ -1269,9 +1341,16 @@ class Route if (!$optional && !isset($m1[$key])) { return false; } - if (isset($m1[$key]) && isset($pattern[$name]) && !preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { + if (isset($m1[$key]) && isset($pattern[$name])) { // 检查变量规则 - return false; + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } } $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { @@ -1290,17 +1369,16 @@ class Route * @param string $pathinfo URL地址 * @param array $option 路由参数 * @param array $matches 匹配的变量 - * @param bool $merge 合并额外变量 * @return array */ - private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $merge = false) + private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = []) { $request = Request::instance(); // 解析路由规则 if ($rule) { $rule = explode('/', $rule); // 获取URL地址中的参数 - $paths = $merge ? explode('/', $pathinfo, count($rule)) : explode('/', $pathinfo); + $paths = explode('|', $pathinfo); foreach ($rule as $item) { $fun = ''; if (0 === strpos($item, '[:')) { @@ -1315,7 +1393,7 @@ class Route } } } else { - $paths = explode('/', $pathinfo); + $paths = explode('|', $pathinfo); } // 获取路由地址规则 @@ -1328,7 +1406,6 @@ class Route foreach ($matches as $key => $val) { if (false !== strpos($route, ':' . $key)) { $route = str_replace(':' . $key, $val, $route); - unset($matches[$key]); } } } @@ -1372,7 +1449,7 @@ class Route } // 解析额外参数 - self::parseUrlParams(empty($paths) ? '' : implode('/', $paths), $matches); + self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); // 记录匹配的路由信息 $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); @@ -1399,28 +1476,35 @@ class Route if ($route instanceof \Closure) { // 执行闭包 $result = ['type' => 'function', 'function' => $route]; - } elseif (0 === strpos($route, '/') || 0 === strpos($route, 'http')) { + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; } elseif (false !== strpos($route, '\\')) { // 路由到方法 - $route = str_replace('/', '@', $route); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = ['type' => 'method', 'method' => $method]; + list($path, $var) = self::parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; } elseif (0 === strpos($route, '@')) { // 路由到控制器 - $result = ['type' => 'controller', 'controller' => substr($route, 1)]; + $route = substr($route, 1); + list($route, $var) = self::parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); } else { // 路由到模块/控制器/操作 $result = self::parseModule($route); } // 开启请求缓存 - if ($request->isGet() && !empty($option['cache'])) { + if ($request->isGet() && isset($option['cache'])) { $cache = $option['cache']; if (is_array($cache)) { list($key, $expire) = $cache; } else { - $key = $pathinfo; + $key = str_replace('|', '/', $pathinfo); $expire = $cache; } $request->cache($key, $expire); @@ -1432,12 +1516,11 @@ class Route * 解析URL地址为 模块/控制器/操作 * @access private * @param string $url URL地址 - * @param string $depr URL分隔符 * @return array */ - private static function parseModule($url, $depr = '/') + private static function parseModule($url) { - list($path, $var) = self::parseUrlPath($url, $depr); + list($path, $var) = self::parseUrlPath($url); $action = array_pop($path); $controller = !empty($path) ? array_pop($path) : null; $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; @@ -1463,9 +1546,9 @@ class Route { if ($url) { if (Config::get('url_param_type')) { - $var += explode('/', $url); + $var += explode('|', $url); } else { - preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$var) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { $var[$match[1]] = strip_tags($match[2]); }, $url); } diff --git a/library/think/Session.php b/library/think/Session.php index e8f56fd9..4b66284c 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,6 @@ namespace think; -use think\App; use think\exception\ClassNotFoundException; class Session @@ -78,7 +77,12 @@ class Session 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); } @@ -114,7 +118,9 @@ class Session if (is_null(self::$init)) { self::init(); } elseif (false === self::$init) { - session_start(); + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } self::$init = true; } } @@ -191,13 +197,49 @@ class Session self::delete($name, $prefix); return $result; } else { - return null; + return; } } /** - * 删除session数据 + * session设置 下一次请求有效 * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function flash($name, $value) + { + self::set($name, $value); + if (!self::has('__flash__.__time__')) { + self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + self::push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public static function flush() + { + if (self::$init) { + $item = self::get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + self::delete($item); + self::set('__flash__', []); + } + } + } + } + + /** + * 删除session数据 + * @param string|array $name session名称 * @param string|null $prefix 作用域(前缀) * @return void */ @@ -205,7 +247,11 @@ class Session { empty(self::$init) && self::boot(); $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (strpos($name, '.')) { + if (is_array($name)) { + foreach ($name as $key) { + self::delete($key, $prefix); + } + } elseif (strpos($name, '.')) { list($name1, $name2) = explode('.', $name); if ($prefix) { unset($_SESSION[$prefix][$name1][$name2]); @@ -256,6 +302,22 @@ class Session } } + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public static function push($key, $value) + { + $array = self::get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + self::set($key, $array); + } + /** * 启动session * @return void diff --git a/library/think/Template.php b/library/think/Template.php index 036b367e..66ba9e18 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -25,6 +25,7 @@ class Template // 引擎配置 protected $config = [ 'view_path' => '', // 模板路径 + 'view_base' => '', 'view_suffix' => 'html', // 默认模板文件后缀 'view_depr' => DS, 'cache_suffix' => 'php', // 默认模板缓存后缀 @@ -128,7 +129,7 @@ class Template } elseif (isset($this->config[$config])) { return $this->config[$config]; } else { - return null; + return; } } @@ -667,7 +668,7 @@ class Template $content = str_replace($matches[0], '', $content); return explode(',', $matches['name']); } - return null; + return; } /** @@ -959,78 +960,66 @@ class Template * @param array $vars 变量数组 * @return string */ - public function parseThinkVar(&$vars) + public function parseThinkVar($vars) { - $vars[0] = strtoupper(trim($vars[0])); - $parseStr = ''; - if (count($vars) >= 2) { - $vars[1] = trim($vars[1]); - switch ($vars[0]) { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + if ($vars) { + switch ($type) { case 'SERVER': - $parseStr = '$_SERVER[\'' . strtoupper($vars[1]) . '\']'; + $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; break; case 'GET': - $parseStr = '$_GET[\'' . $vars[1] . '\']'; + $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; break; case 'POST': - $parseStr = '$_POST[\'' . $vars[1] . '\']'; + $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; break; case 'COOKIE': - if (isset($vars[2])) { - $parseStr = '\\think\\Cookie::get(\'' . $vars[1] . '.' . $vars[2] . '\')'; - } else { - $parseStr = '\\think\\Cookie::get(\'' . $vars[1] . '\')'; - } + $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; break; case 'SESSION': - if (isset($vars[2])) { - $parseStr = '\\think\\Session::get(\'' . $vars[1] . '.' . $vars[2] . '\')'; - } else { - $parseStr = '\\think\\Session::get(\'' . $vars[1] . '\')'; - } + $parseStr = '\\think\\Session::get(\'' . $param . '\')'; break; case 'ENV': - $parseStr = '$_ENV[\'' . strtoupper($vars[1]) . '\']'; + $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; break; case 'REQUEST': - $parseStr = '$_REQUEST[\'' . $vars[1] . '\']'; + $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; break; case 'CONST': - $parseStr = strtoupper($vars[1]); + $parseStr = strtoupper($param); break; case 'LANG': - $parseStr = '\\think\\Lang::get(\'' . $vars[1] . '\')'; + $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; break; case 'CONFIG': - if (isset($vars[2])) { - $vars[1] .= '.' . $vars[2]; - } - $parseStr = '\\think\\Config::get(\'' . $vars[1] . '\')'; + $parseStr = '\\think\\Config::get(\'' . $param . '\')'; break; default: $parseStr = '\'\''; break; } } else { - if (count($vars) == 1) { - switch ($vars[0]) { - case 'NOW': - $parseStr = "date('Y-m-d g:i a',time())"; - break; - case 'VERSION': - $parseStr = 'THINK_VERSION'; - break; - case 'LDELIM': - $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; - break; - case 'RDELIM': - $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; - break; - default: - if (defined($vars[0])) { - $parseStr = $vars[0]; - } - } + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } } } return $parseStr; @@ -1073,14 +1062,20 @@ class Template { if ('' == pathinfo($template, PATHINFO_EXTENSION)) { if (strpos($template, '@')) { - // 跨模块调用模板 + list($module, $template) = explode('@', $template); + } + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $this->config['view_depr'], $template); - $template = APP_PATH . str_replace('@', '/' . basename($this->config['view_path']) . '/', $template); } else { - $template = str_replace(['/', ':'], $this->config['view_depr'], $template); - $template = $this->config['view_path'] . $template; + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + if ($this->config['view_base']) { + $module = isset($module) ? $module : Request::instance()->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; } - $template .= '.' . ltrim($this->config['view_suffix'], '.'); + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); } if (is_file($template)) { diff --git a/library/think/Url.php b/library/think/Url.php index 4e145702..de3cd912 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,15 +11,11 @@ namespace think; -use think\Config; -use think\Loader; -use think\Request; -use think\Route; - class Url { // 生成URL地址的root protected static $root; + protected static $bindCheck; /** * URL生成 支持路由反射 @@ -31,7 +27,7 @@ class Url */ public static function build($url = '', $vars = '', $suffix = true, $domain = false) { - if (false === $domain && Config::get('url_domain_deploy')) { + if (false === $domain && Route::rules('domain')) { $domain = true; } // 解析URL @@ -40,22 +36,24 @@ class Url $name = substr($url, 1, $pos - 1); $url = 'name' . substr($url, $pos + 1); } - $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, '@')) { + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { // 解析域名 - list($anchor, $domain) = explode('@', $anchor, 2); + list($url, $domain) = explode('@', $url, 2); } - } elseif (strpos($url, '@')) { - // 解析域名 - list($url, $domain) = explode('@', $url, 2); } // 解析参数 @@ -77,27 +75,52 @@ class Url if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { // 匹配路由命名标识 $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); if (!empty($match[1])) { $domain = $match[1]; } + if (!is_null($match[2])) { + $suffix = $match[2]; + } } elseif (!empty($rule) && isset($name)) { throw new \InvalidArgumentException('route name not exists:' . $name); } else { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = self::parseUrl($url, $domain); + } if (isset($info['query'])) { // 解析地址里面参数 合并到vars parse_str($info['query'], $params); $vars = array_merge($params, $vars); } - // 路由标识不存在 直接解析 - $url = self::parseUrl($url, $domain); } // 检测URL绑定 - $type = Route::getBind('type'); - if ($type) { - $bind = Route::getBind($type); - if (0 === strpos($url, $bind)) { - $url = substr($url, strlen($bind) + 1); + if (!self::$bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if (0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } } } // 还原URL分隔符 @@ -115,9 +138,14 @@ class Url $vars = urldecode(http_build_query($vars)); $url .= $suffix . '?' . $vars . $anchor; } else { + $paramType = Config::get('url_param_type'); foreach ($vars as $var => $val) { if ('' !== trim($val)) { - $url .= $depr . $var . $depr . urlencode($val); + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } } } $url .= $suffix . $anchor; @@ -128,12 +156,14 @@ class Url // 检测域名 $domain = self::parseDomain($url, $domain); // URL组装 - $url = $domain . (self::$root ?: Request::instance()->root()) . '/' . ltrim($url, '/'); + $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + + self::$bindCheck = false; return $url; } // 直接解析URL地址 - protected static function parseUrl($url, $domain) + protected static function parseUrl($url, &$domain) { $request = Request::instance(); if (0 === strpos($url, '/')) { @@ -149,14 +179,36 @@ class Url // 解析到 模块/控制器/操作 $module = $request->module(); $domains = Route::rules('domain'); - if (isset($domains[$domain]['[bind]'][0])) { - $bindModule = $domains[$domain]['[bind]'][0]; - if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { - $module = ''; + 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 = ''; + } } - } else { - $module = $module ? $module . '/' : ''; } + $module = $module ? $module . '/' : ''; $controller = Loader::parseName($request->controller()); if ('' == $url) { @@ -179,15 +231,15 @@ class Url if (!$domain) { return ''; } - $request = Request::instance(); + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); if (true === $domain) { // 自动判断域名 $domain = $request->host(); - if (Config::get('url_domain_deploy')) { - // 根域名 - $urlDomainRoot = Config::get('url_domain_root'); - $domains = Route::rules('domain'); - $route_domain = array_keys($domains); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); foreach ($route_domain as $domain_prefix) { if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { foreach ($domains as $key => $rule) { @@ -196,13 +248,13 @@ class Url $url = ltrim($url, $rule); $domain = $key; // 生成对应子域名 - if (!empty($urlDomainRoot)) { - $domain .= $urlDomainRoot; + if (!empty($rootDomain)) { + $domain .= $rootDomain; } break; - } else if (false !== strpos($key, '*')) { - if (!empty($urlDomainRoot)) { - $domain .= $urlDomainRoot; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; } break; } @@ -210,13 +262,15 @@ class Url } } } - } elseif (!strpos($domain, '.')) { - $rootDomain = Config::get('url_domain_root'); + + } else { if (empty($rootDomain)) { $host = $request->host(); $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; } - $domain .= '.' . $rootDomain; + if (!strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } } return ($request->isSsl() ? 'https://' : 'http://') . $domain; } @@ -237,18 +291,18 @@ class Url public static function getRuleUrl($rule, &$vars = []) { foreach ($rule as $item) { - list($url, $pattern, $domain) = $item; + list($url, $pattern, $domain, $suffix) = $item; if (empty($pattern)) { - return [$url, $domain]; + return [$url, $domain, $suffix]; } foreach ($pattern as $key => $val) { if (isset($vars[$key])) { $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $vars[$key], $url); unset($vars[$key]); - $result = [$url, $domain]; + $result = [$url, $domain, $suffix]; } elseif (2 == $val) { $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); - $result = [$url, $domain]; + $result = [$url, $domain, $suffix]; } else { break; } diff --git a/library/think/Validate.php b/library/think/Validate.php index 5fa15424..2f728a60 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,9 +11,7 @@ namespace think; -use think\File; -use think\Request; -use think\Session; +use think\exception\ClassNotFoundException; class Validate { @@ -33,6 +31,8 @@ class Validate // 验证提示信息 protected $message = []; + // 验证字段描述 + protected $field = []; // 验证规则默认提示信息 protected static $typeMsg = [ @@ -69,8 +69,8 @@ class Validate 'expire' => '不在有效期内 :rule', 'allowIp' => '不允许的IP访问', 'denyIp' => '禁止的IP访问', - 'confirm' => ':attribute和字段 :rule 不一致', - 'different' => ':attribute和字段 :rule 不能相同', + 'confirm' => ':attribute和确认字段:2不一致', + 'different' => ':attribute和比较字段:2不能相同', 'egt' => ':attribute必须大于等于 :rule', 'gt' => ':attribute必须大于 :rule', 'elt' => ':attribute必须小于等于 :rule', @@ -106,11 +106,13 @@ class Validate * @access public * @param array $rules 验证规则 * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 */ - public function __construct(array $rules = [], $message = []) + public function __construct(array $rules = [], $message = [], $field = []) { $this->rule = array_merge($this->rule, $rules); $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); } /** @@ -118,12 +120,13 @@ class Validate * @access public * @param array $rules 验证规则 * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 * @return Validate */ - public static function make($rules = [], $message = []) + public static function make($rules = [], $message = [], $field = []) { if (is_null(self::$instance)) { - self::$instance = new self($rules, $message); + self::$instance = new self($rules, $message, $field); } return self::$instance; } @@ -215,6 +218,17 @@ class Validate return $this; } + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]); + } + /** * 设置批量验证 * @access public @@ -279,7 +293,7 @@ class Validate // 字段|描述 用于指定属性名称 list($key, $title) = explode('|', $key); } else { - $title = $key; + $title = isset($this->field[$key]) ? $this->field[$key] : $key; } // 场景检测 @@ -300,7 +314,12 @@ class Validate $value = $this->getDataValue($data, $key); // 字段验证 - $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + if ($rule instanceof \Closure) { + // 匿名函数验证 支持传入当前字段和所有字段两个数据 + $result = call_user_func_array($rule, [$value, $data]); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + } if (true !== $result) { // 没有返回true 则表示验证失败 @@ -333,74 +352,90 @@ class Validate */ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) { - if ($rules instanceof \Closure) { - // 匿名函数验证 支持传入当前字段和所有字段两个数据 - $result = call_user_func_array($rules, [$value, $data]); - } else { - // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] - if (is_string($rules)) { - $rules = explode('|', $rules); - } - $i = 0; - foreach ($rules as $key => $rule) { - if ($rule instanceof \Closure) { - $result = call_user_func_array($rule, [$value, $data]); - } else { - // 判断验证类型 - if (is_numeric($key) && strpos($rule, ':')) { + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + $i = 0; + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + if (is_numeric($key)) { + if (strpos($rule, ':')) { list($type, $rule) = explode(':', $rule, 2); if (isset($this->alias[$type])) { // 判断别名 $type = $this->alias[$type]; } $info = $type; - } elseif (is_numeric($key)) { - $type = 'is'; + } elseif (method_exists($this, $rule)) { + $type = $rule; $info = $rule; + $rule = ''; } else { - $info = $type = $key; + $type = 'is'; + $info = $rule; } + } else { + $info = $type = $key; + } - // 如果不是require 有数据才会行验证 - if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { - // 验证类型 - $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; - // 验证数据 - $result = call_user_func_array($callback, [$value, $rule, $data, $field]); - } else { - $result = true; - } + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证类型 + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + } else { + $result = true; } + } - if (false === $result) { - // 验证失败 返回错误信息 - if (isset($msg[$i])) { - $message = $msg[$i]; - } else { - $message = $this->getRuleMsg($field, $title, $info, $rule); + if (false === $result) { + // 验证失败 返回错误信息 + if (isset($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = Lang::get(substr($message, 2, -1)); } - return $message; - } elseif (true !== $result) { - // 返回自定义错误信息 - return $result; + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); } - $i++; + return $result; } + $i++; } - return true !== $result ? $result : true; + return $result; } /** * 验证是否和某个字段的值一致 * @access protected - * @param mixed $value 字段值 + * @param mixed $value 字段值 * @param mixed $rule 验证规则 * @param array $data 数据 + * @param string $field 字段名 * @return bool */ - protected function confirm($value, $rule, $data) + protected function confirm($value, $rule, $data, $field = '') { - return $this->getDataValue($data, $rule) == $value; + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + return $this->getDataValue($data, $rule) === $value; } /** @@ -533,7 +568,7 @@ class Validate break; case 'ip': // 是否为IP地址 - $result = $this->filter($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6); + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); break; case 'url': // 是否为一个URL地址 @@ -556,7 +591,7 @@ class Validate break; case 'boolean': // 是否为布尔值 - $result = in_array($value, [0, 1, true, false]); + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; case 'array': // 是否为数组 @@ -603,6 +638,9 @@ class Validate */ protected function activeUrl($value, $rule) { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } return checkdnsrr($value, $rule); } @@ -618,7 +656,7 @@ class Validate if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } - return $this->filter($value, FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4); + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); } /** @@ -711,19 +749,24 @@ class Validate if (!($file instanceof File)) { return false; } - $rule = explode(',', $rule); - list($width, $height, $type) = getimagesize($file->getRealPath()); - if (isset($rule[2])) { - $imageType = strtolower($rule[2]); - if ('jpeg' == $imageType) { - $imageType = 'jpg'; - } - if (image_type_to_extension($type, false) != $imageType) { - return false; + if ($rule) { + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + if (image_type_to_extension($type, false) != $imageType) { + return false; + } } + + list($w, $h) = $rule; + return $w == $width && $h == $height; + } else { + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); } - list($w, $h) = $rule; - return $w == $width && $h == $height; } /** @@ -766,7 +809,16 @@ class Validate if (is_string($rule)) { $rule = explode(',', $rule); } - $db = Db::name($rule[0]); + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Loader::model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } $key = isset($rule[1]) ? $rule[1] : $field; if (strpos($key, '^')) { @@ -820,6 +872,7 @@ class Validate list($rule, $param) = explode(',', $rule); } elseif (is_array($rule)) { $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; } else { $param = null; } @@ -1156,6 +1209,8 @@ class Validate { if (isset($this->message[$attribute . '.' . $type])) { $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; } elseif (isset($this->message[$attribute])) { $msg = $this->message[$attribute]; } elseif (isset(self::$typeMsg[$type])) { @@ -1163,10 +1218,14 @@ class Validate } else { $msg = $title . '规则错误'; } - // TODO 多语言支持 - if (is_string($msg) && false !== strpos($msg, ':')) { + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = Lang::get(substr($msg, 2, -1)); + } + + if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { // 变量替换 - if (strpos($rule, ',')) { + if (is_string($rule) && strpos($rule, ',')) { $array = array_pad(explode(',', $rule), 3, ''); } else { $array = array_pad([], 3, ''); @@ -1206,7 +1265,7 @@ class Validate public static function __callStatic($method, $params) { - $class = new static; + $class = self::make(); if (method_exists($class, $method)) { return call_user_func_array([$class, $method], $params); } else { diff --git a/library/think/View.php b/library/think/View.php index da087036..020e5789 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -19,6 +19,8 @@ class View public $engine; // 模板变量 protected $data = []; + // 用于静态赋值的模板变量 + protected static $var = []; // 视图输出替换 protected $replace = []; @@ -32,7 +34,21 @@ class View { // 初始化模板引擎 $this->engine((array) $engine); - $this->replace = $replace; + // 基础替换字符串 + $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); } /** @@ -50,6 +66,22 @@ class View return self::$instance; } + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return void + */ + public static function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + } + /** * 模板变量赋值 * @access public @@ -116,7 +148,7 @@ class View public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) { // 模板变量 - $vars = array_merge($this->data, $vars); + $vars = array_merge(self::$var, $this->data, $vars); // 页面缓存 ob_start(); diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index e0aeb7bc..688507a8 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -105,10 +105,31 @@ abstract class Driver $this->rm($name); return $result; } else { - return null; + return; } } + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + if ($value instanceof \Closure) { + $value = call_user_func($value); + } + $this->set($name, $value, $expire); + } else { + $value = $this->get($name); + } + return $value; + } + /** * 缓存标签 * @access public diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 5c98d799..838025e0 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -21,7 +21,7 @@ class File extends Driver { protected $options = [ 'expire' => 0, - 'cache_subdir' => false, + 'cache_subdir' => true, 'prefix' => '', 'path' => CACHE_PATH, 'data_compress' => false, @@ -225,6 +225,7 @@ class File extends Driver foreach ($files as $path) { if (is_dir($path)) { array_map('unlink', glob($path . '/*.php')); + rmdir($path); } else { unlink($path); } diff --git a/library/think/cache/driver/Lite.php b/library/think/cache/driver/Lite.php index b9d10097..57727651 100644 --- a/library/think/cache/driver/Lite.php +++ b/library/think/cache/driver/Lite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/cache/driver/Memcache.php b/library/think/cache/driver/Memcache.php index bddf7157..d41939d8 100644 --- a/library/think/cache/driver/Memcache.php +++ b/library/think/cache/driver/Memcache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; class Memcache extends Driver { diff --git a/library/think/cache/driver/Memcached.php b/library/think/cache/driver/Memcached.php index 4c4505b0..35fafd07 100644 --- a/library/think/cache/driver/Memcached.php +++ b/library/think/cache/driver/Memcached.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index e7f78762..360f515a 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/cache/driver/Sqlite.php b/library/think/cache/driver/Sqlite.php index 6dbd41fe..76c592d8 100644 --- a/library/think/cache/driver/Sqlite.php +++ b/library/think/cache/driver/Sqlite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; /** * Sqlite缓存驱动 diff --git a/library/think/cache/driver/Wincache.php b/library/think/cache/driver/Wincache.php index a9cc7d22..5be8d0df 100644 --- a/library/think/cache/driver/Wincache.php +++ b/library/think/cache/driver/Wincache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; /** * Wincache缓存驱动 @@ -28,7 +27,7 @@ class Wincache extends Driver /** * 架构函数 * @param array $options 缓存参数 - * @throws Exception + * @throws \BadFunctionCallException * @access public */ public function __construct($options = []) diff --git a/library/think/cache/driver/Xcache.php b/library/think/cache/driver/Xcache.php index 6b18b3fc..317a4ee3 100644 --- a/library/think/cache/driver/Xcache.php +++ b/library/think/cache/driver/Xcache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Exception; /** * Xcache缓存驱动 diff --git a/library/think/config/driver/Ini.php b/library/think/config/driver/Ini.php index d8dc558d..a223a578 100644 --- a/library/think/config/driver/Ini.php +++ b/library/think/config/driver/Ini.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/config/driver/Json.php b/library/think/config/driver/Json.php index ec2419f9..557f75fe 100644 --- a/library/think/config/driver/Json.php +++ b/library/think/config/driver/Json.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/config/driver/Xml.php b/library/think/config/driver/Xml.php index 5bc93015..b573a562 100644 --- a/library/think/config/driver/Xml.php +++ b/library/think/config/driver/Xml.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/console/Input.php b/library/think/console/Input.php index 269196eb..2482dfdc 100644 --- a/library/think/console/Input.php +++ b/library/think/console/Input.php @@ -252,7 +252,7 @@ class Input return $token; } - return null; + return; } /** diff --git a/library/think/console/command/Help.php b/library/think/console/command/Help.php index eb0858a3..bae2c653 100644 --- a/library/think/console/command/Help.php +++ b/library/think/console/command/Help.php @@ -16,7 +16,6 @@ use think\console\Input; use think\console\input\Argument as InputArgument; use think\console\input\Option as InputOption; use think\console\Output; -use think\console\helper\Descriptor as DescriptorHelper; class Help extends Command { @@ -67,4 +66,4 @@ EOF $this->command = null; } -} \ No newline at end of file +} diff --git a/library/think/console/command/Lists.php b/library/think/console/command/Lists.php index ffbee07c..084ddaa2 100644 --- a/library/think/console/command/Lists.php +++ b/library/think/console/command/Lists.php @@ -71,4 +71,4 @@ EOF new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list') ]); } -} \ No newline at end of file +} diff --git a/library/think/console/command/optimize/Autoload.php b/library/think/console/command/optimize/Autoload.php index 0481e179..7311aafb 100644 --- a/library/think/console/command/optimize/Autoload.php +++ b/library/think/console/command/optimize/Autoload.php @@ -278,4 +278,4 @@ EOF; return $classes; } -} \ No newline at end of file +} diff --git a/library/think/console/command/optimize/Schema.php b/library/think/console/command/optimize/Schema.php index a1f40cd9..6ac38a36 100644 --- a/library/think/console/command/optimize/Schema.php +++ b/library/think/console/command/optimize/Schema.php @@ -50,7 +50,7 @@ class Schema extends Command } $output->writeln('Succeed!'); return; - } else if ($input->hasOption('table')) { + } elseif ($input->hasOption('table')) { $table = $input->getOption('table'); if (!strpos($table, '.')) { $dbName = Db::getConfig('database'); diff --git a/library/think/console/output/Ask.php b/library/think/console/output/Ask.php index 4d360bce..3933eb29 100644 --- a/library/think/console/output/Ask.php +++ b/library/think/console/output/Ask.php @@ -337,4 +337,4 @@ class Ask return self::$stty = $exitcode === 0; } -} \ No newline at end of file +} diff --git a/library/think/console/output/driver/Console.php b/library/think/console/output/driver/Console.php index af4341b7..3fbe224f 100644 --- a/library/think/console/output/driver/Console.php +++ b/library/think/console/output/driver/Console.php @@ -196,7 +196,7 @@ class Console private function getSttyColumns() { if (!function_exists('proc_open')) { - return null; + return; } $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; @@ -209,7 +209,7 @@ class Console return $info; } - return null; + return; } /** @@ -219,7 +219,7 @@ class Console private function getMode() { if (!function_exists('proc_open')) { - return null; + return; } $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; @@ -234,7 +234,7 @@ class Console return $matches[2] . 'x' . $matches[1]; } } - return null; + return; } private function stringWidth($string) @@ -356,10 +356,10 @@ class Console { if (DIRECTORY_SEPARATOR === '\\') { return - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); } return function_exists('posix_isatty') && @posix_isatty($stream); diff --git a/library/think/controller/Rest.php b/library/think/controller/Rest.php index a75cfd37..c297f4eb 100644 --- a/library/think/controller/Rest.php +++ b/library/think/controller/Rest.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 15a240ad..9034076e 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -12,9 +12,6 @@ namespace think\db; use PDO; -use think\Db; -use think\db\Connection; -use think\db\Query; use think\Exception; abstract class Builder @@ -37,22 +34,33 @@ abstract class Builder /** * 架构函数 * @access public - * @param Connection $connection 数据库连接对象实例 + * @param Connection $connection 数据库连接对象实例 + * @param Query $query 数据库查询对象实例 */ - public function __construct(Connection $connection) + public function __construct(Connection $connection, Query $query) { $this->connection = $connection; + $this->query = $query; } /** - * 设置当前的Query对象实例 - * @access protected - * @param Query $query 当前查询对象实例 + * 获取当前的连接对象实例 + * @access public + * @return void + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 获取当前的Query对象实例 + * @access public * @return void */ - public function setQuery(Query $query) + public function getQuery() { - $this->query = $query; + return $this->query; } /** @@ -89,8 +97,8 @@ abstract class Builder $result = []; foreach ($data as $key => $val) { - $item = $this->parseKey($key); - if (!in_array($key, $fields, true)) { + $item = $this->parseKey($key, $options); + if (false === strpos($key, '.') && !in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } @@ -100,12 +108,16 @@ abstract class Builder $result[$item] = 'NULL'; } elseif (is_scalar($val)) { // 过滤非标量数据 - if ($this->query->isBind(substr($val, 1))) { + if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { $result[$item] = $val; } else { - $this->query->bind($key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':' . $key; + $key = str_replace('.', '_', $key); + $this->query->bind('__data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':__data__' . $key; } + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $result[$item] = $val->__toString(); } } return $result; @@ -115,9 +127,10 @@ abstract class Builder * 字段名分析 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { return $key; } @@ -146,10 +159,11 @@ abstract class Builder /** * field分析 * @access protected - * @param mixed $fields + * @param mixed $fields + * @param array $options * @return string */ - protected function parseField($fields) + protected function parseField($fields, $options = []) { if ('*' == $fields || empty($fields)) { $fieldsStr = '*'; @@ -158,9 +172,9 @@ abstract class Builder $array = []; foreach ($fields as $key => $field) { if (!is_numeric($key)) { - $array[] = $this->parseKey($key) . ' AS ' . $this->parseKey($field); + $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options); } else { - $array[] = $this->parseKey($field); + $array[] = $this->parseKey($field, $options); } } $fieldsStr = implode(',', $array); @@ -171,24 +185,30 @@ abstract class Builder /** * table分析 * @access protected - * @param mixed $table + * @param mixed $tables + * @param array $options * @return string */ - protected function parseTable($tables) + protected function parseTable($tables, $options = []) { - if (is_array($tables)) { - // 支持别名定义 - foreach ($tables as $table => $alias) { - $array[] = !is_numeric($table) ? - $this->parseKey($table) . ' ' . $this->parseKey($alias) : - $this->parseKey($alias); + $item = []; + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + if (strpos($key, '@think')) { + $key = strstr($key, '@think', true); + } + $key = $this->parseSqlTable($key); + $item[] = $this->parseKey($key) . ' ' . $this->parseKey($table); + } else { + $table = $this->parseSqlTable($table); + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + } else { + $item[] = $this->parseKey($table); + } } - $tables = $array; - } elseif (is_string($tables)) { - $tables = $this->parseSqlTable($tables); - $tables = array_map([$this, 'parseKey'], explode(',', $tables)); } - return implode(',', $tables); + return implode(',', $item); } /** @@ -230,7 +250,10 @@ abstract class Builder // 使用闭包查询 $query = new Query($this->connection); call_user_func_array($value, [ & $query]); - $str[] = ' ' . $key . ' ( ' . $this->buildWhere($query->getOptions('where'), $options) . ' )'; + $whereClause = $this->buildWhere($query->getOptions('where'), $options); + if (!empty($whereClause)) { + $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + } } elseif (strpos($field, '|')) { // 不同字段使用相同查询条件(OR) $array = explode('|', $field); @@ -263,7 +286,7 @@ abstract class Builder protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) { // 字段分析 - $key = $field ? $this->parseKey($field) : ''; + $key = $field ? $this->parseKey($field, $options) : ''; // 查询规则和条件 if (!is_array($val)) { @@ -296,12 +319,17 @@ abstract class Builder throw new Exception('where express error:' . $exp); } } - $bindName = $bindName ?: 'where_' . str_replace('.', '_', $field); + $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { + // 处理带非单词字符的字段名 + $bindName = md5($bindName); + } + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { if ($this->query->isBind($bindName)) { - $bindName .= '_' . uniqid(); + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); } $this->query->bind($bindName, $value, $bindType); $value = ':' . $bindName; @@ -309,9 +337,19 @@ abstract class Builder } $whereStr = ''; - if (in_array($exp, ['=', '<>', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE'])) { + if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { // 比较运算 及 模糊匹配 $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); + } + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } } elseif ('EXP' == $exp) { // 表达式查询 $whereStr .= '( ' . $key . ' ' . $value . ' )'; @@ -328,8 +366,13 @@ abstract class Builder $bind = []; $array = []; foreach ($value as $k => $v) { - $bind[$bindName . '_in_' . $k] = [$v, $bindType]; - $array[] = ':' . $bindName . '_in_' . $k; + if ($this->query->isBind($bindName . '_in_' . $k)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $k; + } else { + $bindKey = $bindName . '_in_' . $k; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; } $this->query->bind($bind); $zone = implode(',', $array); @@ -342,12 +385,19 @@ abstract class Builder // BETWEEN 查询 $data = is_array($value) ? $value : explode(',', $value); if (array_key_exists($field, $binds)) { + if ($this->query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } $bind = [ - $bindName . '_between_1' => [$data[0], $bindType], - $bindName . '_between_2' => [$data[1], $bindType], + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], ]; $this->query->bind($bind); - $between = ':' . $bindName . '_between_1' . ' AND :' . $bindName . '_between_2'; + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; } else { $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); } @@ -392,12 +442,23 @@ abstract class Builder protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) { // 获取时间字段类型 - $type = $this->query->getFieldsType($options); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + $type = $this->query->getTableInfo($table, 'type'); if (isset($type[$key])) { $info = $type[$key]; } if (isset($info)) { - $value = strtotime($value) ?: $value; + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + if (preg_match('/(datetime|timestamp)/is', $info)) { // 日期及时间戳类型 $value = date('Y-m-d H:i:s', $value); @@ -425,14 +486,29 @@ abstract class Builder /** * join分析 * @access protected - * @param mixed $join + * @param array $join + * @param array $options 查询条件 * @return string */ - protected function parseJoin($join) + protected function parseJoin($join, $options = []) { $joinStr = ''; if (!empty($join)) { - $joinStr = ' ' . implode(' ', $join) . ' '; + foreach ($join as $item) { + list($table, $type, $on) = $item; + $condition = []; + foreach ((array) $on as $val) { + if (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($table, $options); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } } return $joinStr; } @@ -441,22 +517,25 @@ abstract class Builder * order分析 * @access protected * @param mixed $order + * @param array $options 查询条件 * @return string */ - protected function parseOrder($order) + protected function parseOrder($order, $options = []) { if (is_array($order)) { $array = []; foreach ($order as $key => $val) { if (is_numeric($key)) { - if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val); - } elseif ('[rand]' == $val) { + if ('[rand]' == $val) { $array[] = $this->parseRand(); + } elseif (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } else { + $array[] = $val; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($key) . ' ' . $sort; + $array[] = $this->parseKey($key, $options) . ' ' . $sort; } } $order = implode(',', $array); @@ -572,14 +651,14 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], [ - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), $this->parseDistinct($options['distinct']), - $this->parseField($options['field']), - $this->parseJoin($options['join']), + $this->parseField($options['field'], $options), + $this->parseJoin($options['join'], $options), $this->parseWhere($options['where'], $options), $this->parseGroup($options['group']), $this->parseHaving($options['having']), - $this->parseOrder($options['order']), + $this->parseOrder($options['order'], $options), $this->parseLimit($options['limit']), $this->parseUnion($options['union']), $this->parseLock($options['lock']), @@ -611,7 +690,7 @@ abstract class Builder ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), implode(' , ', $fields), implode(' , ', $values), $this->parseComment($options['comment']), @@ -643,8 +722,13 @@ abstract class Builder 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]); @@ -657,7 +741,7 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), implode(' , ', $fields), implode(' UNION ALL ', $values), $this->parseComment($options['comment']), @@ -681,7 +765,7 @@ abstract class Builder } $fields = array_map([$this, 'parseKey'], $fields); - $sql = 'INSERT INTO ' . $this->parseTable($table) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); return $sql; } @@ -694,7 +778,7 @@ abstract class Builder */ public function update($data, $options) { - $table = $this->parseTable($options['table']); + $table = $this->parseTable($options['table'], $options); $data = $this->parseData($data, $options); if (empty($data)) { return ''; @@ -706,11 +790,11 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table']), + $this->parseTable($options['table'], $options), implode(',', $set), - $this->parseJoin($options['join']), + $this->parseJoin($options['join'], $options), $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order']), + $this->parseOrder($options['order'], $options), $this->parseLimit($options['limit']), $this->parseLock($options['lock']), $this->parseComment($options['comment']), @@ -730,11 +814,11 @@ abstract class Builder $sql = str_replace( ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ - $this->parseTable($options['table']), - !empty($options['using']) ? ' USING ' . $this->parseTable($options['using']) . ' ' : '', - $this->parseJoin($options['join']), + $this->parseTable($options['table'], $options), + !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', + $this->parseJoin($options['join'], $options), $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order']), + $this->parseOrder($options['order'], $options), $this->parseLimit($options['limit']), $this->parseLock($options['lock']), $this->parseComment($options['comment']), diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index f405f9b9..f166e916 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,10 +13,8 @@ namespace think\db; use PDO; use PDOStatement; -use think\Collection; use think\Db; use think\db\exception\BindParamException; -use think\db\Query; use think\Debug; use think\Exception; use think\exception\PDOException; @@ -52,8 +50,6 @@ abstract class Connection protected $linkRead; protected $linkWrite; - // 查询结果类型 - protected $resultSetType = 'array'; // 查询结果类型 protected $fetchType = PDO::FETCH_ASSOC; // 字段属性大小写 @@ -62,50 +58,58 @@ abstract class Connection protected static $event = []; // 查询对象 protected $query = []; + // 使用Builder类 + protected $builder; // 数据库连接参数配置 protected $config = [ // 数据库类型 - 'type' => '', + 'type' => '', // 服务器地址 - 'hostname' => '', + 'hostname' => '', // 数据库名 - 'database' => '', + 'database' => '', // 用户名 - 'username' => '', + 'username' => '', // 密码 - 'password' => '', + 'password' => '', // 端口 - 'hostport' => '', + 'hostport' => '', // 连接dsn - 'dsn' => '', + 'dsn' => '', // 数据库连接参数 - 'params' => [], + 'params' => [], // 数据库编码默认采用utf8 - 'charset' => 'utf8', + 'charset' => 'utf8', // 数据库表前缀 - 'prefix' => '', + 'prefix' => '', // 数据库调试模式 - 'debug' => false, + 'debug' => false, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, + 'deploy' => 0, // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, + 'rw_separate' => false, // 读写分离后 主服务器数量 - 'master_num' => 1, + 'master_num' => 1, // 指定从服务器序号 - 'slave_no' => '', + 'slave_no' => '', // 是否严格检查字段是否存在 - 'fields_strict' => true, + 'fields_strict' => true, + // 数据返回类型 + 'result_type' => PDO::FETCH_ASSOC, // 数据集返回类型 - 'resultset_type' => 'array', + 'resultset_type' => 'array', // 自动写入时间戳字段 - 'auto_timestamp' => false, + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 - 'sql_explain' => false, + 'sql_explain' => false, // Builder类 - 'builder' => '', + 'builder' => '', // Query类 - 'query' => '\\think\\db\\Query', + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, ]; // PDO连接参数 @@ -117,6 +121,9 @@ abstract class Connection PDO::ATTR_EMULATE_PREPARES => false, ]; + // 绑定参数 + protected $bind = []; + /** * 架构函数 读取数据库配置信息 * @access public @@ -136,15 +143,29 @@ abstract class Connection * @param string $queryClass 查询对象类名 * @return Query */ - public function model($model, $queryClass = '') + public function getQuery($model = 'db', $queryClass = '') { if (!isset($this->query[$model])) { $class = $queryClass ?: $this->config['query']; - $this->query[$model] = new $class($this, $model); + $this->query[$model] = new $class($this, 'db' == $model ? '' : $model); } return $this->query[$model]; } + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilder() + { + if (!empty($this->builder)) { + return $this->builder; + } else { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + } + /** * 调用Query类的查询方法 * @access public @@ -154,11 +175,7 @@ abstract class Connection */ public function __call($method, $args) { - if (!isset($this->query['database'])) { - $class = $this->config['query']; - $this->query['database'] = new $class($this); - } - return call_user_func_array([$this->query['database'], $method], $args); + return call_user_func_array([$this->getQuery(), $method], $args); } /** @@ -268,9 +285,10 @@ abstract class Connection } // 记录当前字段属性大小写设置 $this->attrCase = $params[PDO::ATTR_CASE]; - // 记录数据集返回类型 - if (isset($config['resultset_type'])) { - $this->resultSetType = $config['resultset_type']; + + // 数据返回类型 + if (isset($config['result_type'])) { + $this->fetchType = $config['result_type']; } try { if (empty($config['dsn'])) { @@ -322,24 +340,32 @@ abstract class Connection /** * 执行查询 返回数据集 * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $class 是否返回PDO对象 * @param string $sql sql指令 * @param array $bind 参数绑定 * @param boolean $master 是否在主服务器读操作 - * @param bool|string $class 指定返回的数据集对象 + * @param bool $pdo 是否返回PDO对象 * @return mixed * @throws BindParamException * @throws PDOException */ - public function query($sql, $bind = [], $master = false, $class = false) + public function query($sql, $bind = [], $master = false, $pdo = false) { $this->initConnect($master); if (!$this->linkID) { return false; } - // 根据参数绑定组装最终的SQL语句 - $this->queryStr = $this->getRealSql($sql, $bind); - //释放前次的查询结果 + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + // 释放前次的查询结果 if (!empty($this->PDOStatement)) { $this->free(); } @@ -349,17 +375,28 @@ abstract class Connection // 调试开始 $this->debug(true); // 预处理 - $this->PDOStatement = $this->linkID->prepare($sql); + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); // 参数绑定 - $this->bindValue($bind); + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } // 执行查询 - $result = $this->PDOStatement->execute(); + $this->PDOStatement->execute(); // 调试结束 $this->debug(false); - $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); - return $this->getResult($class, $procedure); + // 返回结果集 + return $this->getResult($pdo, $procedure); } catch (\PDOException $e) { - throw new PDOException($e, $this->config, $this->queryStr); + if ($this->config['break_reconnect'] && $this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw new PDOException($e, $this->config, $this->getLastsql()); } } @@ -378,11 +415,15 @@ abstract class Connection if (!$this->linkID) { return false; } - // 根据参数绑定组装最终的SQL语句 - $this->queryStr = $this->getRealSql($sql, $bind); + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } //释放前次的查询结果 - if (!empty($this->PDOStatement)) { + if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { $this->free(); } @@ -391,18 +432,29 @@ abstract class Connection // 调试开始 $this->debug(true); // 预处理 - $this->PDOStatement = $this->linkID->prepare($sql); - // 参数绑定操作 - $this->bindValue($bind); + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } // 执行语句 - $result = $this->PDOStatement->execute(); + $this->PDOStatement->execute(); // 调试结束 $this->debug(false); $this->numRows = $this->PDOStatement->rowCount(); return $this->numRows; } catch (\PDOException $e) { - throw new PDOException($e, $this->config, $this->queryStr); + if ($this->config['break_reconnect'] && $this->isBreak($e)) { + return $this->close()->execute($sql, $bind); + } + throw new PDOException($e, $this->config, $this->getLastsql()); } } @@ -415,23 +467,23 @@ abstract class Connection */ public function getRealSql($sql, array $bind = []) { - if ($bind) { - foreach ($bind as $key => $val) { - $value = is_array($val) ? $val[0] : $val; - $type = is_array($val) ? $val[1] : PDO::PARAM_STR; - if (PDO::PARAM_STR == $type) { - $value = $this->quote($value); - } - // 判断占位符 - $sql = is_numeric($key) ? - substr_replace($sql, $value, strpos($sql, '?'), 1) : - str_replace( - [':' . $key . ')', ':' . $key . ',', ':' . $key . ' '], - [$value . ')', $value . ',', $value . ' '], - $sql . ' '); + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { + $value = $this->quote($value); + } elseif (PDO::PARAM_INT == $type) { + $value = (float) $value; } + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' '], + [$value . ')', $value . ',', $value . ' '], + $sql . ' '); } - return $sql; + return rtrim($sql); } /** @@ -441,7 +493,7 @@ abstract class Connection * @access public * @param array $bind 要绑定的参数列表 * @return void - * @throws \think\Exception + * @throws BindParamException */ protected function bindValue(array $bind = []) { @@ -449,6 +501,9 @@ abstract class Connection // 占位符 $param = is_numeric($key) ? $key + 1 : ':' . $key; if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); } else { $result = $this->PDOStatement->bindValue($param, $val); @@ -457,7 +512,36 @@ abstract class Connection throw new BindParamException( "Error occurred when binding parameters '{$param}'", $this->config, - $this->queryStr, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam($bind) + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + if (!$result) { + $param = array_shift($val); + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), $bind ); } @@ -465,46 +549,37 @@ abstract class Connection } /** - * 获得数据集 + * 获得数据集数组 * @access protected - * @param bool|string $class true 返回PDOStatement 字符串用于指定返回的类名 - * @param bool $procedure 是否存储过程 - * @return mixed + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return array */ - protected function getResult($class = '', $procedure = false) + protected function getResult($pdo = false, $procedure = false) { - if (true === $class) { + if ($pdo) { // 返回PDOStatement对象处理 return $this->PDOStatement; } if ($procedure) { // 存储过程返回结果 - return $this->procedure($class); + return $this->procedure(); } $result = $this->PDOStatement->fetchAll($this->fetchType); $this->numRows = count($result); - - if (!empty($class)) { - // 返回指定数据集对象类 - $result = new $class($result); - } elseif ('collection' == $this->resultSetType) { - // 返回数据集Collection对象 - $result = new Collection($result); - } return $result; } /** * 获得存储过程数据集 * @access protected - * @param bool|string $class true 返回PDOStatement 字符串用于指定返回的类名 * @return array */ - protected function procedure($class) + protected function procedure() { $item = []; do { - $result = $this->getResult($class); + $result = $this->getResult(); if ($result) { $item[] = $result; } @@ -680,12 +755,28 @@ abstract class Connection } /** - * 关闭数据库 + * 关闭数据库(或者重新连接) * @access public + * @return $this */ public function close() { - $this->linkID = null; + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException $e 异常 + * @return bool + */ + protected function isBreak($e) + { + return false; } /** @@ -695,7 +786,7 @@ abstract class Connection */ public function getLastSql() { - return $this->queryStr; + return $this->getRealSql($this->queryStr, $this->bind); } /** @@ -733,7 +824,7 @@ abstract class Connection $error = ''; } if ('' != $this->queryStr) { - $error .= "\n [ SQL语句 ] : " . $this->queryStr; + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); } return $error; } @@ -768,7 +859,7 @@ abstract class Connection // 记录操作结束时间 Debug::remark('queryEndTime', 'time'); $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); - $sql = $sql ?: $this->queryStr; + $sql = $sql ?: $this->getLastsql(); $log = $sql . ' [ RunTime:' . $runtime . 's ]'; $result = []; // SQL性能分析 diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 2e9308cc..5e258d71 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -16,8 +16,6 @@ use think\Cache; use think\Collection; use think\Config; use think\Db; -use think\db\Builder; -use think\db\Connection; use think\db\exception\BindParamException; use think\db\exception\DataNotFoundException; use think\db\exception\ModelNotFoundException; @@ -27,13 +25,14 @@ use think\exception\PDOException; use think\Loader; use think\Model; use think\model\Relation; +use think\model\relation\OneToOne; use think\Paginator; class Query { // 数据库Connection对象实例 protected $connection; - // 数据库驱动类型 + // 数据库Builder对象实例 protected $builder; // 当前模型类名称 protected $model; @@ -51,6 +50,8 @@ class Query protected $bind = []; // 数据表信息 protected static $info = []; + // 回调事件 + private static $event = []; /** * 架构函数 @@ -61,9 +62,10 @@ class Query public function __construct(Connection $connection = null, $model = '') { $this->connection = $connection ?: Db::connect([], true); - $this->builder = $this->connection->getConfig('builder') ?: $this->connection->getConfig('type'); $this->prefix = $this->connection->getConfig('prefix'); $this->model = $model; + // 设置当前连接的Builder对象 + $this->setBuilder(); } /** @@ -111,9 +113,41 @@ class Query public function connect($config) { $this->connection = Db::connect($config); + $this->setBuilder(); return $this; } + /** + * 设置当前的数据库Builder对象 + * @access protected + * @return void + */ + protected function setBuilder() + { + $class = $this->connection->getBuilder(); + $this->builder = new $class($this->connection, $this); + } + + /** + * 获取当前的模型对象名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + /** * 指定默认的数据表名(不含前缀) * @access public @@ -194,8 +228,8 @@ class Query /** * 执行语句 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 + * @param string $sql sql指令 + * @param array $bind 参数绑定 * @return int * @throws BindParamException * @throws PDOException @@ -208,7 +242,7 @@ class Query /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -350,35 +384,18 @@ class Query } } - /** - * 获取当前的builder实例对象 - * @access protected - * @return Builder - */ - protected function builder() - { - static $builder = []; - $driver = $this->builder; - if (!isset($builder[$driver])) { - $class = false !== strpos($driver, '\\') ? $driver : '\\think\\db\\builder\\' . ucfirst($driver); - $builder[$driver] = new $class($this->connection); - } - // 设置当前查询对象 - $builder[$driver]->setQuery($this); - return $builder[$driver]; - } - /** * 得到某个字段的值 * @access public * @param string $field 字段名 * @param mixed $default 默认值 + * @param bool $force 强制转为数字类型 * @return mixed */ - public function value($field, $default = null) + public function value($field, $default = null, $force = false) { - $result = null; - if (!empty($this->options['cache'])) { + $result = false; + if (empty($options['fetch_sql']) && !empty($this->options['cache'])) { // 判断查询缓存 $cache = $this->options['cache']; if (empty($this->options['table'])) { @@ -387,29 +404,28 @@ class Query $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options)); $result = Cache::get($key); } - if (!$result) { + if (false === $result) { if (isset($this->options['field'])) { unset($this->options['field']); } - $pdo = $this->field($field)->fetchPdo(true)->find(); + $pdo = $this->field($field)->limit(1)->getPdo(); if (is_string($pdo)) { // 返回SQL语句 return $pdo; } $result = $pdo->fetchColumn(); + if ($force) { + $result = is_numeric($result) ? $result + 0 : $result; + } if (isset($cache)) { // 缓存数据 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($key, $result, $cache['expire']); - } else { - Cache::set($key, $result, $cache['expire']); - } + $this->cacheData($key, $result, $cache); } } else { // 清空查询条件 $this->options = []; } - return !is_null($result) ? $result : $default; + return false !== $result ? $result : $default; } /** @@ -422,7 +438,7 @@ class Query public function column($field, $key = '') { $result = false; - if (!empty($this->options['cache'])) { + if (empty($options['fetch_sql']) && !empty($this->options['cache'])) { // 判断查询缓存 $cache = $this->options['cache']; if (empty($this->options['table'])) { @@ -431,14 +447,14 @@ class Query $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options)); $result = Cache::get($guid); } - if (!$result) { + if (false === $result) { if (isset($this->options['field'])) { unset($this->options['field']); } if ($key && '*' != $field) { $field = $key . ',' . $field; } - $pdo = $this->field($field)->fetchPdo(true)->select(); + $pdo = $this->field($field)->getPdo(); if (is_string($pdo)) { // 返回SQL语句 return $pdo; @@ -453,6 +469,9 @@ class Query $key1 = array_shift($fields); $key2 = $fields ? array_shift($fields) : ''; $key = $key ?: $key1; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } foreach ($resultSet as $val) { if ($count > 2) { $result[$val[$key]] = $val; @@ -468,11 +487,7 @@ class Query } if (isset($cache) && isset($guid)) { // 缓存数据 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($guid, $result, $cache['expire']); - } else { - Cache::set($guid, $result, $cache['expire']); - } + $this->cacheData($guid, $result, $cache); } } else { // 清空查询条件 @@ -485,11 +500,18 @@ class Query * COUNT查询 * @access public * @param string $field 字段名 - * @return integer + * @return integer|string */ public function count($field = '*') { - return (int) $this->value('COUNT(' . $field . ') AS tp_count', 0); + if (isset($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } + + return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); } /** @@ -498,31 +520,31 @@ class Query * @param string $field 字段名 * @return float|int */ - public function sum($field = '*') + public function sum($field) { - return $this->value('SUM(' . $field . ') AS tp_sum', 0) + 0; + return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); } /** * MIN查询 * @access public * @param string $field 字段名 - * @return float|int + * @return mixed */ - public function min($field = '*') + public function min($field) { - return $this->value('MIN(' . $field . ') AS tp_min', 0) + 0; + return $this->value('MIN(' . $field . ') AS tp_min', 0, true); } /** * MAX查询 * @access public * @param string $field 字段名 - * @return float|int + * @return mixed */ - public function max($field = '*') + public function max($field) { - return $this->value('MAX(' . $field . ') AS tp_max', 0) + 0; + return $this->value('MAX(' . $field . ') AS tp_max', 0, true); } /** @@ -531,9 +553,9 @@ class Query * @param string $field 字段名 * @return float|int */ - public function avg($field = '*') + public function avg($field) { - return $this->value('AVG(' . $field . ') AS tp_avg', 0) + 0; + return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); } /** @@ -578,8 +600,6 @@ class Query // 清空查询条件 $this->options = []; return true; - } else { - return $this->setField($field, $step); } } return $this->setField($field, ['exp', $field . '+' . $step]); @@ -609,8 +629,6 @@ class Query // 清空查询条件 $this->options = []; return true; - } else { - return $this->setField($field, $step); } } return $this->setField($field, ['exp', $field . '-' . $step]); @@ -663,39 +681,54 @@ class Query } } } else { - $prefix = $this->prefix; - // 传入的表名为数组 - if (is_array($join)) { - if (0 !== $key = key($join)) { - // 设置了键名则键名为表名,键值作为表的别名 - $table = $key . ' ' . array_shift($join); - } else { - $table = array_shift($join); - } - if (count($join)) { - // 有设置第二个元素则把第二元素作为表前缀 - $table = (string) current($join) . $table; - } elseif (false === strpos($table, '.')) { - // 加上默认的表前缀 - $table = $prefix . $table; - } + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + return $this; + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * @access public + * @param array|string $join + * @return array|string + */ + protected function getJoinTable($join, &$alias = null) + { + // 传入的表名为数组 + if (is_array($join)) { + list($table, $alias) = each($join); + } else { + $join = trim($join); + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; } else { - $join = trim($join); - if (0 === strpos($join, '__')) { - $table = $this->parseSqlTable($join); - } elseif (false === strpos($join, '(') && false === strpos($join, '.') && !empty($prefix) && 0 !== strpos($join, $prefix)) { - // 传入的表名中不带有'('并且不以默认的表前缀开头时加上默认的表前缀 - $table = $prefix . $join; + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); } else { $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); } } - if (is_array($condition)) { - $condition = implode(' AND ', $condition); + } + if (isset($alias)) { + if (isset($this->options['alias'][$table])) { + $table = $table . '@think' . uniqid(); } - $this->options['join'][] = strtoupper($type) . ' JOIN ' . $table . ' ON ' . $condition; + $table = [$table => $alias]; + $this->alias($table); } - return $this; + return $table; } /** @@ -762,6 +795,68 @@ class Query return $this; } + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['exp', $field . '+' . $step]); + } + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['exp', $field . '-' . $step]); + } + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, ['exp', $value]); + return $this; + } + /** * 指定JOIN查询字段 * @access public @@ -774,19 +869,14 @@ class Query public function view($join, $field = true, $on = null, $type = 'INNER') { $this->options['view'] = true; - if (is_array($join) && is_null($field)) { + if (is_array($join) && key($join) !== 0) { foreach ($join as $key => $val) { $this->view($key, $val[0], isset($val[1]) ? $val[1] : null, isset($val[2]) ? $val[2] : 'INNER'); } } else { $fields = []; - if (is_array($join)) { - // 支持数据表别名 - list($join, $alias, $table) = array_pad($join, 3, ''); - } else { - $alias = $join; - } - $table = !empty($table) ? $table : $this->getTable($join); + $table = $this->getJoinTable($join, $alias); + if (true === $field) { $fields = $alias . '.*'; } else { @@ -810,9 +900,9 @@ class Query } $this->field($fields); if ($on) { - $this->join($table . ' ' . $alias, $on, $type); + $this->join($table, $on, $type); } else { - $this->table($table . ' ' . $alias); + $this->table($table); } } return $this; @@ -880,6 +970,156 @@ class Query return $this; } + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $condition); + return $this; + } + /** * 分析查询表达式 * @access public @@ -892,6 +1132,7 @@ class Query */ protected function parseWhereExp($logic, $field, $op, $condition, $param = []) { + $logic = strtoupper($logic); if ($field instanceof \Closure) { $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; return; @@ -910,33 +1151,96 @@ class Query if (is_array($field)) { // 数组批量查询 $where = $field; - } elseif ($field) { - // 字符串查询 - if (is_numeric($field)) { - $where[] = ['exp', $field]; - } else { - $where[$field] = ['null', '']; + foreach ($where as $k => $val) { + $this->options['multi'][$logic][$k][] = $val; } + } elseif ($field && is_string($field)) { + // 字符串查询 + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } } elseif (is_array($op)) { $where[$field] = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { // null查询 - $where[$field] = [$op, '']; + $where[$field] = [$op, '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } elseif (is_null($condition)) { // 字段相等查询 $where[$field] = ['eq', $op]; + if ('AND' != $logic) { + $this->options['multi'][$logic][$field][] = $where[$field]; + } } else { - $where[$field] = [$op, $condition]; + $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null]; + if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { + // 参数绑定 + $this->bind($param[2]); + } + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; } if (!empty($where)) { if (!isset($this->options['where'][$logic])) { $this->options['where'][$logic] = []; } + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; + } elseif (is_array($field)) { + foreach ($field as $key => $val) { + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; + } + } + } $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); } } + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) + { + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + } + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + /** * 指定查询数量 * @access public @@ -971,17 +1275,17 @@ class Query /** * 分页查询 - * @param int|null $listRows 每页数量 - * @param int|bool $simple 简洁模式或者总记录数 - * @param array $config 配置参数 - * page:当前页, - * path:url路径, - * query:url额外参数, - * fragment:url锚点, - * var_page:分页变量, - * list_rows:每页数量 - * type:分页类名 - * @return \think\paginator\Collection + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator * @throws DbException */ public function paginate($listRows = null, $simple = false, $config = []) @@ -990,8 +1294,13 @@ class Query $total = $simple; $simple = false; } - $config = array_merge(Config::get('paginate'), $config); - $listRows = $listRows ?: $config['list_rows']; + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } /** @var Paginator $class */ $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); @@ -1006,8 +1315,11 @@ class Query if (!isset($total) && !$simple) { $options = $this->getOptions(); - $total = $this->count(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + $bind = $this->bind; + $total = $this->count(); $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); } elseif ($simple) { $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); @@ -1021,11 +1333,44 @@ class Query /** * 指定当前操作的数据表 * @access public - * @param string $table 表名 + * @param mixed $table 表名 * @return $this */ public function table($table) { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $tables = $table; + $table = []; + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } $this->options['table'] = $table; return $this; } @@ -1067,7 +1412,14 @@ class Query } } } - $this->options['order'] = $field; + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } } return $this; } @@ -1145,12 +1497,27 @@ class Query /** * 指定数据表别名 * @access public - * @param string $alias 数据表别名 + * @param mixed $alias 数据表别名 * @return $this */ public function alias($alias) { - $this->options['alias'] = $alias; + if (is_array($alias)) { + foreach ($alias as $key => $val) { + $this->options['alias'][$key] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } return $this; } @@ -1198,19 +1565,7 @@ class Query */ public function fetchPdo($pdo = true) { - $this->options['fetch_class'] = $pdo; - return $this; - } - - /** - * 指定数据集返回对象 - * @access public - * @param string $class 指定返回的数据集对象类名 - * @return $this - */ - public function fetchClass($class) - { - $this->options['fetch_class'] = $class; + $this->options['fetch_pdo'] = $pdo; return $this; } @@ -1289,7 +1644,7 @@ class Query switch (strtolower($op)) { case 'today': case 'd': - $range = 'today'; + $range = ['today', 'tomorrow']; break; case 'week': case 'w': @@ -1315,6 +1670,8 @@ class Query case 'last year': $range = [mktime(0, 0, 0, 1, 1, $date['year'] - 1), mktime(0, 0, 0, 1, 1, $date['year'])]; break; + default: + $range = $op; } $op = is_array($range) ? 'between' : '>'; } @@ -1325,7 +1682,7 @@ class Query /** * 获取数据表信息 * @access public - * @param string $tableName 数据表名 留空自动获取 + * @param mixed $tableName 数据表名 留空自动获取 * @param string $fetch 获取信息类型 包括 fields type bind pk * @return mixed */ @@ -1345,10 +1702,16 @@ class Query $tableName = $this->parseSqlTable($tableName); } + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + list($guid) = explode(' ', $tableName); - if (!isset(self::$info[$guid])) { + $db = $this->getConfig('database'); + if (!isset(self::$info[$db . '.' . $guid])) { if (!strpos($guid, '.')) { - $schema = $this->getConfig('database') . '.' . $guid; + $schema = $db . '.' . $guid; } else { $schema = $guid; } @@ -1374,9 +1737,9 @@ class Query } else { $pk = null; } - self::$info[$guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; } - return $fetch ? self::$info[$guid][$fetch] : self::$info[$guid]; + return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; } /** @@ -1428,7 +1791,7 @@ class Query */ protected function getFieldBindType($type) { - if (preg_match('/(int|double|float|decimal|real|numeric|serial)/is', $type)) { + if (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { $bind = PDO::PARAM_INT; } elseif (preg_match('/bool/is', $type)) { $bind = PDO::PARAM_BOOL; @@ -1510,13 +1873,14 @@ class Query $with = explode(',', $with); } - $i = 0; + $first = true; $currentModel = $this->model; /** @var Model $class */ $class = new $currentModel; foreach ($with as $key => $relation) { - $closure = false; + $subRelation = ''; + $closure = false; if ($relation instanceof \Closure) { // 支持闭包查询过滤关联条件 $closure = $relation; @@ -1528,53 +1892,51 @@ class Query } /** @var Relation $model */ - $model = $class->$relation(); - $info = $model->getRelationInfo(); - if (in_array($info['type'], [Relation::HAS_ONE, Relation::BELONGS_TO])) { - if (0 == $i) { - $name = Loader::parseName(basename(str_replace('\\', '/', $currentModel))); - $table = $this->getTable(); - $alias = isset($info['alias'][$name]) ? $info['alias'][$name] : $name; - $this->table($table)->alias($alias); - if (isset($this->options['field'])) { - $field = $this->options['field']; - unset($this->options['field']); - } else { - $field = true; - } - $this->field($field, false, $table, $alias); - } - // 预载入封装 - $joinTable = $model->getTable(); - $joinName = Loader::parseName(basename(str_replace('\\', '/', $info['model']))); - $joinAlias = isset($info['alias'][$joinName]) ? $info['alias'][$joinName] : $joinName; - $this->via($joinAlias); - - if (Relation::HAS_ONE == $info['type']) { - $this->join($joinTable . ' ' . $joinAlias, $alias . '.' . $info['localKey'] . '=' . $joinAlias . '.' . $info['foreignKey'], $info['joinType']); - } else { - $this->join($joinTable . ' ' . $joinAlias, $alias . '.' . $info['foreignKey'] . '=' . $joinAlias . '.' . $info['localKey'], $info['joinType']); - } - - if ($closure) { - // 执行闭包查询 - call_user_func_array($closure, [ & $this]); - //指定获取关联的字段 - //需要在 回调中 调方法 withField 方法,如 - // $query->where(['id'=>1])->withField('id,name'); - if (!empty($this->options['with_field'])) { - $field = $this->options['with_field']; - unset($this->options['with_field']); - } - } - $this->field($field, false, $joinTable, $joinAlias, $relation . '__'); - $i++; + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; } elseif ($closure) { $with[$key] = $closure; } } $this->via(); - $this->options['with'] = $with; + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'] = $relation; + } else { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!isset($this->options['field'])) { + $this->field('*'); + } + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = '(' . (new $this->model)->$relation()->getRelationCountQuery($closure) . ')'; + $this->field([$count => Loader::parseName($relation) . '_count']); + } + } return $this; } @@ -1609,12 +1971,22 @@ class Query /** * 设置关联查询 * @access public - * @param string $relation 关联名称 + * @param string|array $relation 关联名称 * @return $this */ public function relation($relation) { - $this->options['relation'] = $relation; + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_mrege($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } return $this; } @@ -1630,8 +2002,9 @@ class Query { $pk = $this->getPk($options); // 获取当前数据表 - if (!empty($options['alias'])) { - $alias = $options['alias']; + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + if (!empty($options['alias'][$table])) { + $alias = $options['alias'][$table]; } if (is_string($pk)) { $key = isset($alias) ? $alias . '.' . $pk : $pk; @@ -1672,12 +2045,13 @@ class Query * @param string $sequence 自增序列名 * @return integer|string */ - public function insert(array $data, $replace = false, $getLastInsID = false, $sequence = null) + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) { // 分析查询表达式 $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); // 生成SQL语句 - $sql = $this->builder()->insert($data, $options, $replace); + $sql = $this->builder->insert($data, $options, $replace); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1687,9 +2061,21 @@ class Query // 执行操作 $result = $this->execute($sql, $bind); - if ($getLastInsID) { - $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); - return $this->getLastInsID($sequence); + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + if ($lastInsId) { + $pk = $this->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + $options['data'] = $data; + $this->trigger('after_insert', $options); + + if ($getLastInsID) { + return $lastInsId; + } } return $result; } @@ -1721,7 +2107,7 @@ class Query return false; } // 生成SQL语句 - $sql = $this->builder()->insertAll($dataSet, $options); + $sql = $this->builder->insertAll($dataSet, $options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1747,7 +2133,7 @@ class Query $options = $this->parseExpress(); // 生成SQL语句 $table = $this->parseSqlTable($table); - $sql = $this->builder()->selectInsert($fields, $table, $options); + $sql = $this->builder->selectInsert($fields, $table, $options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1767,12 +2153,13 @@ class Query * @throws Exception * @throws PDOException */ - public function update(array $data) + public function update(array $data = []) { $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache'])) { - $key = $options['cache']; + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; } if (empty($options['where'])) { @@ -1801,11 +2188,12 @@ class Query } else { $options['where']['AND'] = $where; } - } elseif (is_string($pk) && isset($options['where']['AND'][$pk]) && is_scalar($options['where']['AND'][$pk])) { - $key = 'think:' . $options['table'] . '|' . $options['where']['AND'][$pk]; + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } + // 生成UPDATE SQL语句 - $sql = $this->builder()->update($data, $options); + $sql = $this->builder->update($data, $options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -1816,10 +2204,44 @@ class Query if (isset($key) && Cache::get($key)) { // 删除缓存 Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); } // 执行操作 - return '' == $sql ? 0 : $this->execute($sql, $bind); + $result = '' == $sql ? 0 : $this->execute($sql, $bind); + if ($result) { + if (isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + $options['data'] = $data; + $this->trigger('after_update', $options); + } + return $result; + } + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); } /** @@ -1860,58 +2282,108 @@ class Query } if (!$resultSet) { // 生成查询SQL - $sql = $this->builder()->select($options); + $sql = $this->builder->select($options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { // 获取实际执行的SQL语句 return $this->connection->getRealSql($sql, $bind); } - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_class']); - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; + $options['data'] = $data; + if ($resultSet = $this->trigger('before_select', $options)) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } } if (isset($cache)) { // 缓存数据集 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($key, $resultSet, $cache['expire']); - } else { - Cache::set($key, $resultSet, $cache['expire']); - } + $this->cacheData($key, $resultSet, $cache); } } - // 返回结果处理 - if (count($resultSet) > 0) { - // 数据列表读取后的处理 - if (!empty($this->model)) { - // 生成模型对象 - $model = $this->model; + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $modelName = $this->model; + if (count($resultSet) > 0) { foreach ($resultSet as $key => $result) { /** @var Model $result */ - $result = new $model($result); - $result->isUpdate(true); + $model = new $modelName($result); + $model->isUpdate(true); + // 关联查询 if (!empty($options['relation'])) { - $result->relationQuery($options['relation']); + $model->relationQuery($options['relation']); } - $resultSet[$key] = $result; + // 关联统计 + if (!empty($options['with_count'])) { + $model->relationCount($model, $options['with_count']); + } + $resultSet[$key] = $model; } - if (!empty($options['with']) && $result instanceof Model) { + if (!empty($options['with'])) { // 预载入 - $resultSet = $result->eagerlyResultSet($resultSet, $options['with'], is_object($resultSet) ? get_class($resultSet) : ''); + $model->eagerlyResultSet($resultSet, $options['with']); } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = (new $modelName)->toCollection($resultSet); } - } elseif (!empty($options['fail'])) { + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + // 返回结果处理 + if (!empty($options['fail']) && count($resultSet) == 0) { $this->throwNotFound($options); } return $resultSet; } + /** + * 缓存数据 + * @access public + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + if (isset($config['tag'])) { + Cache::tag($config['tag'])->set($key, $data, $config['expire']); + } else { + Cache::set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access public + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + */ + protected function getCacheKey($value, $options) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && 'eq' == strtolower($value[0])) { + $data = $value[1]; + } + if (isset($data)) { + return 'think:' . $options['table'] . '|' . $data; + } else { + return md5(serialize($options)); + } + } + /** * 查找单条记录 * @access public @@ -1931,10 +2403,12 @@ class Query } // 分析查询表达式 $options = $this->parseExpress(); - + $pk = $this->getPk($options); if (!is_null($data)) { // AR模式分析主键条件 $this->parsePkWhere($data, $options); + } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } $options['limit'] = 1; @@ -1943,62 +2417,78 @@ class Query // 判断查询缓存 $cache = $options['cache']; if (true === $cache['key'] && !is_null($data) && !is_array($data)) { - $key = 'think:' . $options['table'] . '|' . $data; - } else { - $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options)); + $key = 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } elseif (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = md5(serialize($options)); } $result = Cache::get($key); } - if (!$result) { + if (false === $result) { // 生成查询SQL - $sql = $this->builder()->select($options); + $sql = $this->builder->select($options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { // 获取实际执行的SQL语句 return $this->connection->getRealSql($sql, $bind); } - // 执行查询 - $result = $this->query($sql, $bind, $options['master'], $options['fetch_class']); + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $options['data'] = $data; + // 事件回调 + if ($result = $this->trigger('before_find', $options)) { + } else { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - if ($result instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $result; + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + $result = isset($resultSet[0]) ? $resultSet[0] : null; } if (isset($cache)) { // 缓存数据 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($key, $result, $cache['expire']); - } else { - Cache::set($key, $result, $cache['expire']); - } + $this->cacheData($key, $result, $cache); } } // 数据处理 - if (!empty($result[0])) { - $data = $result[0]; + if (!empty($result)) { if (!empty($this->model)) { // 返回模型对象 - $model = $this->model; - $data = new $model($data); - $data->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); + $model = $this->model; + $result = new $model($result); + $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); // 关联查询 if (!empty($options['relation'])) { - $data->relationQuery($options['relation']); + $result->relationQuery($options['relation']); } + // 预载入查询 if (!empty($options['with'])) { - // 预载入 - $data->eagerlyResult($data, $options['with'], is_object($result) ? get_class($result) : ''); + $result->eagerlyResult($result, $options['with']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $result->relationCount($result, $options['with_count']); } } } elseif (!empty($options['fail'])) { $this->throwNotFound($options); - } else { - $data = null; } - return $data; + return $result; } /** @@ -2013,7 +2503,8 @@ class Query if (!empty($this->model)) { throw new ModelNotFoundException('model data Not Found:' . $this->model, $this->model, $options); } else { - throw new DataNotFoundException('table data not Found:' . $options['table'], $options['table'], $options); + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); } } @@ -2055,23 +2546,39 @@ class Query */ public function chunk($count, $callback, $column = null) { - $options = $this->getOptions(); - $column = $column ?: $this->getPk(isset($options['table']) ? $options['table'] : ''); + $options = $this->getOptions(); + if (isset($options['table'])) { + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + } else { + $table = ''; + } + $column = $column ?: $this->getPk($table); $bind = $this->bind; $resultSet = $this->limit($count)->order($column, 'asc')->select(); + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } while (!empty($resultSet)) { if (false === call_user_func($callback, $resultSet)) { return false; } $end = end($resultSet); - $lastId = is_array($end) ? $end[$column] : $end->$column; + $lastId = is_array($end) ? $end[$key] : $end->$key; $resultSet = $this->options($options) ->limit($count) ->bind($bind) ->where($column, '>', $lastId) ->order($column, 'asc') ->select(); + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } } return true; } @@ -2112,8 +2619,9 @@ class Query { // 分析查询表达式 $options = $this->parseExpress(); - if (isset($options['cache']) && is_string($options['cache'])) { - $key = $options['cache']; + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; } if (!is_null($data) && true !== $data) { @@ -2123,6 +2631,8 @@ class Query } // AR模式分析主键条件 $this->parsePkWhere($data, $options); + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } if (true !== $data && empty($options['where'])) { @@ -2130,7 +2640,7 @@ class Query throw new Exception('delete without condition'); } // 生成删除SQL语句 - $sql = $this->builder()->delete($options); + $sql = $this->builder->delete($options); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -2142,9 +2652,21 @@ class Query if (isset($key) && Cache::get($key)) { // 删除缓存 Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); } // 执行操作 - return $this->execute($sql, $bind); + $result = $this->execute($sql, $bind); + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + $options['data'] = $data; + $this->trigger('after_delete', $options); + } + return $result; } /** @@ -2201,20 +2723,19 @@ class Query } } - // 表别名 - if (!empty($options['alias'])) { - $options['table'] .= ' ' . $options['alias']; - } - if (!isset($options['field'])) { $options['field'] = '*'; } + if (!isset($options['data'])) { + $options['data'] = []; + } + if (!isset($options['strict'])) { $options['strict'] = $this->getConfig('fields_strict'); } - foreach (['master', 'lock', 'fetch_class', 'fetch_sql', 'distinct'] as $name) { + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { if (!isset($options[$name])) { $options[$name] = false; } @@ -2239,4 +2760,32 @@ class Query return $options; } + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 额外参数 + * @return bool + */ + protected function trigger($event, $params = []) + { + $result = false; + if (isset(self::$event[$event])) { + $callback = self::$event[$event]; + $result = call_user_func_array($callback, [$params, $this]); + } + return $result; + } } diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 04586906..e432e396 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -24,19 +24,30 @@ class Mysql extends Builder * 字段和表名处理 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { $key = trim($key); if (strpos($key, '$.') && false === strpos($key, '(')) { // JSON字段支持 list($field, $name) = explode('$.', $key); $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } } if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } + if (isset($table)) { + $key = '`' . $table . '`.' . $key; + } return $key; } diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 0a955a5b..67b98b44 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -43,15 +43,26 @@ class Pgsql extends Builder * 字段和表名处理 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { $key = trim($key); if (strpos($key, '$.') && false === strpos($key, '(')) { // JSON字段支持 list($field, $name) = explode('$.', $key); $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + } + if (isset($table)) { + $key = $table . '.' . $key; } return $key; } diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 17e59cb2..680b4965 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -48,4 +48,27 @@ class Sqlite extends Builder return 'RANDOM()'; } + /** + * 字段和表名处理 + * @access protected + * @param string $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = []) + { + $key = trim($key); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } } diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index bf630d90..99f68409 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -27,22 +27,23 @@ class Sqlsrv extends Builder * order分析 * @access protected * @param mixed $order + * @param array $options * @return string */ - protected function parseOrder($order) + protected function parseOrder($order, $options = []) { if (is_array($order)) { $array = []; foreach ($order as $key => $val) { if (is_numeric($key)) { if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val); + $array[] = $this->parseKey($val, $options); } elseif ('[rand]' == $val) { $array[] = $this->parseRand(); } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($key) . ' ' . $sort; + $array[] = $this->parseKey($key, $options) . ' ' . $sort; } } $order = implode(',', $array); @@ -61,17 +62,29 @@ class Sqlsrv extends Builder } /** - * 字段名分析 + * 字段和表名处理 * @access protected * @param string $key + * @param array $options * @return string */ - protected function parseKey($key) + protected function parseKey($key, $options = []) { $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + } if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { $key = '[' . $key . ']'; } + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } return $key; } diff --git a/library/think/db/connector/Mysql.php b/library/think/db/connector/Mysql.php index 47b069fb..0081fb2e 100644 --- a/library/think/db/connector/Mysql.php +++ b/library/think/db/connector/Mysql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -21,6 +21,8 @@ use think\Log; class Mysql extends Connection { + protected $builder = '\\think\\db\\builder\\Mysql'; + /** * 解析pdo连接的dsn信息 * @access protected @@ -51,10 +53,13 @@ class Mysql extends Connection { $this->initConnect(true); list($tableName) = explode(' ', $tableName); - if (strpos($tableName, '.')) { - $tableName = str_replace('.', '`.`', $tableName); + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; } - $sql = 'SHOW COLUMNS FROM `' . $tableName . '`'; + $sql = 'SHOW COLUMNS FROM ' . $tableName; // 调试开始 $this->debug(true); $pdo = $this->linkID->query($sql); @@ -124,4 +129,18 @@ class Mysql extends Connection { return true; } + + /** + * 是否断线 + * @access protected + * @param \PDOException $e 异常对象 + * @return bool + */ + protected function isBreak($e) + { + if (false !== stripos($e->getMessage(), 'server has gone away')) { + return true; + } + return false; + } } diff --git a/library/think/db/connector/Pgsql.php b/library/think/db/connector/Pgsql.php index e9866cf3..04c3e782 100644 --- a/library/think/db/connector/Pgsql.php +++ b/library/think/db/connector/Pgsql.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -19,6 +19,7 @@ use think\db\Connection; */ class Pgsql extends Connection { + protected $builder = '\\think\\db\\builder\\Pgsql'; /** * 解析pdo连接的dsn信息 diff --git a/library/think/db/connector/Sqlite.php b/library/think/db/connector/Sqlite.php index 95b023e5..a0e0873c 100644 --- a/library/think/db/connector/Sqlite.php +++ b/library/think/db/connector/Sqlite.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -20,6 +20,8 @@ use think\db\Connection; class Sqlite extends Connection { + protected $builder = '\\think\\db\\builder\\Sqlite'; + /** * 解析pdo连接的dsn信息 * @access protected diff --git a/library/think/db/connector/Sqlsrv.php b/library/think/db/connector/Sqlsrv.php index 18148051..20d3491d 100644 --- a/library/think/db/connector/Sqlsrv.php +++ b/library/think/db/connector/Sqlsrv.php @@ -25,7 +25,7 @@ class Sqlsrv extends Connection PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_STRINGIFY_FETCHES => false, ]; - + protected $builder = '\\think\\db\\builder\\Sqlsrv'; /** * 解析pdo连接的dsn信息 * @access protected diff --git a/library/think/db/exception/BindParamException.php b/library/think/db/exception/BindParamException.php index 585a0d73..d0e2387b 100644 --- a/library/think/db/exception/BindParamException.php +++ b/library/think/db/exception/BindParamException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -16,7 +16,7 @@ use think\exception\DbException; /** * PDO参数绑定异常 */ -class BindParamException extends DbException +class BindParamException extends DbException { /** diff --git a/library/think/db/exception/DataNotFoundException.php b/library/think/db/exception/DataNotFoundException.php index efd66d3b..e399b063 100644 --- a/library/think/db/exception/DataNotFoundException.php +++ b/library/think/db/exception/DataNotFoundException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,7 +13,7 @@ namespace think\db\exception; use think\exception\DbException; -class DataNotFoundException extends DbException +class DataNotFoundException extends DbException { protected $table; @@ -23,10 +23,10 @@ class DataNotFoundException extends DbException * @param string $table * @param array $config */ - public function __construct($message, $table = '', Array $config = []) + public function __construct($message, $table = '', array $config = []) { - $this->message = $message; - $this->table = $table; + $this->message = $message; + $this->table = $table; $this->setData('Database Config', $config); } diff --git a/library/think/db/exception/ModelNotFoundException.php b/library/think/db/exception/ModelNotFoundException.php index 69b70965..2180ab07 100644 --- a/library/think/db/exception/ModelNotFoundException.php +++ b/library/think/db/exception/ModelNotFoundException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -13,7 +13,7 @@ namespace think\db\exception; use think\exception\DbException; -class ModelNotFoundException extends DbException +class ModelNotFoundException extends DbException { protected $model; @@ -22,10 +22,10 @@ class ModelNotFoundException extends DbException * @param string $message * @param string $model */ - public function __construct($message, $model = '', Array $config = []) + public function __construct($message, $model = '', array $config = []) { - $this->message = $message; - $this->model = $model; + $this->message = $message; + $this->model = $model; $this->setData('Database Config', $config); } diff --git a/library/think/exception/DbException.php b/library/think/exception/DbException.php index bd6fb442..656d6913 100644 --- a/library/think/exception/DbException.php +++ b/library/think/exception/DbException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/exception/ErrorException.php b/library/think/exception/ErrorException.php index 2f1525c0..e3f18375 100644 --- a/library/think/exception/ErrorException.php +++ b/library/think/exception/ErrorException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/exception/PDOException.php b/library/think/exception/PDOException.php index 677092b9..ebd53dff 100644 --- a/library/think/exception/PDOException.php +++ b/library/think/exception/PDOException.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,8 +11,6 @@ namespace think\exception; -use think\exception\DbException; - /** * PDO异常处理类 * 重新封装了系统的\PDOException类 diff --git a/library/think/exception/RouteNotFoundException.php b/library/think/exception/RouteNotFoundException.php new file mode 100644 index 00000000..f85a3c4b --- /dev/null +++ b/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404); + } + +} diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index c4e46279..b639fd04 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -11,6 +11,8 @@ namespace think\log\driver; +use think\App; + /** * 本地化调试输出到文件 */ @@ -35,9 +37,10 @@ class File * 日志写入接口 * @access public * @param array $log 日志信息 + * @param bool $depr 是否写入分割线 * @return bool */ - public function save(array $log = []) + public function save(array $log = [], $depr = true) { $now = date($this->config['time_format']); $destination = $this->config['path'] . date('Ym') . DS . date('d') . '.log'; @@ -50,25 +53,29 @@ class File rename($destination, dirname($destination) . DS . $_SERVER['REQUEST_TIME'] . '-' . basename($destination)); } - // 获取基本信息 - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); - } + $depr = $depr ? "---------------------------------------------------------------\r\n" : ''; + $info = ''; + if (App::$debug) { + // 获取基本信息 + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); + } - $runtime = number_format(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + $runtime = round(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - $info = '[ log ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n"; - $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; - $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + $info = '[ log ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n"; + $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; + $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } foreach ($log as $type => $val) { $level = ''; foreach ($val as $msg) { @@ -80,12 +87,15 @@ class File if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 $filename = $path . DS . date('d') . '_' . $type . '.log'; - error_log("[{$now}] {$server} {$remote} {$method} {$uri}\r\n{$level}\r\n---------------------------------------------------------------\r\n", 3, $filename); + error_log("[{$now}] {$level}\r\n{$depr}", 3, $filename); } else { $info .= $level; } } - return error_log("[{$now}] {$server} {$remote} {$method} {$uri}\r\n{$info}\r\n---------------------------------------------------------------\r\n", 3, $destination); + if (App::$debug) { + $info = "{$server} {$remote} {$method} {$uri}\r\n" . $info; + } + return error_log("[{$now}] {$info}\r\n{$depr}", 3, $destination); } } diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index f24b6091..08fd8ace 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -11,6 +11,8 @@ namespace think\log\driver; +use think\App; + /** * github: https://github.com/luofei614/SocketLog * @author luofei614 @@ -63,24 +65,27 @@ class Socket if (!$this->check()) { return false; } - $runtime = number_format(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 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_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; } - // 基本信息 - $trace[] = [ - 'type' => 'group', - 'msg' => $current_uri . $time_str . $memory_str . $file_load, - 'css' => $this->css['page'], - ]; foreach ($log as $type => $val) { $trace[] = [ @@ -204,19 +209,19 @@ class Socket } if (!isset($_SERVER[$key])) { - return null; + return; } if (empty($args)) { if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { $args = ['tabid' => null]; - return null; + return; } parse_str($match[1], $args); } if (isset($args[$name])) { return $args[$name]; } - return null; + return; } /** diff --git a/library/think/model/Collection.php b/library/think/model/Collection.php new file mode 100644 index 00000000..08e11ad8 --- /dev/null +++ b/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model->append($append, $override); + }); + return $this; + } + +} diff --git a/library/think/model/Merge.php b/library/think/model/Merge.php index c28d0fd7..47d54923 100644 --- a/library/think/model/Merge.php +++ b/library/think/model/Merge.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,7 @@ namespace think\model; -use think\Db; +use think\db\Query; use think\Model; class Merge extends Model @@ -39,9 +39,9 @@ class Merge extends Model /** * 查找单条记录 * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 * @return \think\Model */ public static function get($data = null, $with = [], $cache = false) @@ -61,14 +61,14 @@ class Merge extends Model { $class = new static(); $master = $class->name; - $fields = self::getModelField($query, $master, '', $class->mapFields); + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); $query->alias($master)->field($fields); foreach ($class->relationModel as $key => $model) { $name = is_int($key) ? $model : $key; $table = is_int($key) ? $query->getTable($name) : $model; $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); - $fields = self::getModelField($query, $name, $table, $class->mapFields); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); $query->field($fields); } return $query; @@ -77,16 +77,17 @@ class Merge extends Model /** * 获取关联模型的字段 并解决混淆 * @access protected - * @param \think\db\Query $query 查询对象 - * @param string $name 模型名称 - * @param string $table 关联表名称 - * @param array $map 字段映射 + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 * @return array */ - protected static function getModelField($query, $name, $table = '', $map = []) + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) { // 获取模型的字段信息 - $fields = $query->getTableInfo($table, 'fields'); + $fields = $fields ?: $query->getTableInfo($table, 'fields'); $array = []; foreach ($fields as $field) { if ($key = array_search($name . '.' . $field, $map)) { @@ -102,8 +103,9 @@ class Merge extends Model /** * 查找所有记录 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache * @return array|false|string */ public static function all($data = null, $with = [], $cache = false) @@ -116,10 +118,10 @@ class Merge extends Model /** * 处理写入的模型数据 * @access public - * @param string $model 模型名称 - * @param array $data 数据 - * @param bool $insert 是否新增 - * @return void + * @param string $model 模型名称 + * @param array $data 数据 + * @param bool $insert 是否新增 + * @return array */ protected function parseData($model, $data, $insert = false) { @@ -142,10 +144,11 @@ class Merge extends Model /** * 保存模型数据 以及关联数据 * @access public - * @param mixed $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return integer|false + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception */ public function save($data = [], $where = [], $sequence = null) { @@ -156,7 +159,7 @@ class Merge extends Model } // 数据对象赋值 foreach ($data as $key => $value) { - $this->setAttr($key, $value); + $this->setAttr($key, $value, $data); } if (!empty($where)) { $this->isUpdate = true; @@ -167,12 +170,13 @@ class Merge extends Model $this->autoCompleteData($this->auto); // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime) { + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { $this->setAttr($this->updateTime, null); } $db = $this->db(); $db->startTrans(); + $pk = $this->getPk(); try { if ($this->isUpdate) { // 自动写入 @@ -186,19 +190,15 @@ class Merge extends Model $where = $this->updateWhere; } - if (!empty($where)) { - $pk = $this->getPk(); - - if (isset($this->mapFields[$pk])) { - $pk = $this->mapFields[$pk]; - } - if (isset($where[$pk])) { - unset($where[$pk]); - } - } - // 处理模型数据 $data = $this->parseData($this->name, $this->data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } // 写入主表数据 $result = $db->strict(false)->where($where)->update($data); @@ -208,7 +208,7 @@ class Merge extends Model $table = is_int($key) ? $db->getTable($model) : $model; // 处理关联模型数据 $data = $this->parseData($name, $this->data); - $query = clone $db; + $query = new Query; if ($query->table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { $result = 1; } @@ -222,7 +222,7 @@ class Merge extends Model $this->autoCompleteData($this->insert); // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime) { + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { $this->setAttr($this->createTime, null); } @@ -237,7 +237,6 @@ class Merge extends Model if ($result) { $insertId = $db->getLastInsID($sequence); // 写入外键数据 - $pk = $this->getPk(); if ($insertId) { if (is_string($pk)) { $this->data[$pk] = $insertId; @@ -258,7 +257,7 @@ class Merge extends Model $table = is_int($key) ? $db->getTable($model) : $model; // 处理关联模型数据 $data = $this->parseData($name, $source, true); - $query = clone $db; + $query = new Query; $query->table($table)->strict(false)->insert($data); } } @@ -280,7 +279,8 @@ class Merge extends Model /** * 删除当前的记录 并删除关联数据 * @access public - * @return integer + * @return int + * @throws \Exception */ public function delete() { @@ -299,7 +299,7 @@ class Merge extends Model // 删除关联数据 foreach ($this->relationModel as $key => $model) { $table = is_int($key) ? $db->getTable($model) : $model; - $query = clone $db; + $query = new Query; $query->table($table)->where($this->fk, $pk)->delete(); } } diff --git a/library/think/model/Pivot.php b/library/think/model/Pivot.php index 24034b07..7823370c 100644 --- a/library/think/model/Pivot.php +++ b/library/think/model/Pivot.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index 22670bdd..3d56091b 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,691 +11,109 @@ namespace think\model; -use think\Db; +use think\db\Query; use think\Exception; -use think\Loader; use think\Model; -use think\model\Pivot; -class Relation +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation { - const HAS_ONE = 1; - const HAS_MANY = 2; - const HAS_MANY_THROUGH = 5; - const BELONGS_TO = 3; - const BELONGS_TO_MANY = 4; - // 父模型对象 protected $parent; /** @var Model 当前关联的模型类 */ protected $model; - // 中间表模型 - protected $middle; - // 当前关联类型 - protected $type; + /** @var Query 关联模型查询对象 */ + protected $query; // 关联表外键 protected $foreignKey; - // 中间关联表外键 - protected $throughKey; // 关联表主键 protected $localKey; - // 数据表别名 - protected $alias; - // 当前关联的JOIN类型 - protected $joinType; - // 关联模型查询对象 - protected $query; - // 关联查询条件 - protected $where; + // 关联查询参数 + protected $option; + // 基础查询 + protected $baseQuery; /** - * 架构函数 + * 获取关联的所属模型 * @access public - * @param Model $model 上级模型对象 + * @return Model */ - public function __construct(Model $model) + public function getParent() { - $this->parent = $model; + return $this->parent; } /** - * 获取当前关联信息 + * 获取当前的关联模型类 * @access public - * @param string $name 关联信息 - * @return array|string|integer + * @return string */ - public function getRelationInfo($name = '') + public function getModel() { - $info = [ - 'type' => $this->type, - 'model' => $this->model, - 'middle' => $this->middle, - 'foreignKey' => $this->foreignKey, - 'localKey' => $this->localKey, - 'alias' => $this->alias, - 'joinType' => $this->joinType, - ]; - return $name ? $info[$name] : $info; - } - - // 获取关联数据 - public function getRelation($name) - { - // 执行关联定义方法 - $relation = $this->parent->$name(); - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - - // 判断关联类型执行查询 - switch ($this->type) { - case self::HAS_ONE: - $result = $relation->where($foreignKey, $this->parent->$localKey)->find(); - break; - case self::BELONGS_TO: - $result = $relation->where($localKey, $this->parent->$foreignKey)->find(); - break; - case self::HAS_MANY: - $result = $relation->select(); - break; - case self::HAS_MANY_THROUGH: - $result = $relation->select(); - break; - case self::BELONGS_TO_MANY: - // 关联查询 - $pk = $this->parent->getPk(); - $condition['pivot.' . $localKey] = $this->parent->$pk; - $result = $this->belongsToManyQuery($relation, $this->middle, $foreignKey, $localKey, $condition)->select(); - foreach ($result as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); - } - break; - default: - // 直接返回 - $result = $relation; - } - return $result; + return $this->model; } /** - * 预载入关联查询 返回数据集 + * 获取关联的查询对象 * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 - * @return array + * @return Query */ - public function eagerlyResultSet($resultSet, $relation, $class = '') + public function getQuery() { - /** @var array $relations */ - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); - } - // 执行关联方法 - $model = $this->parent->$relation(); - // 获取关联信息 - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - foreach ($resultSet as $result) { - // 模型关联组装 - $this->match($this->model, $relation, $result); - } - break; - case self::HAS_MANY: - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$localKey)) { - $range[] = $result->$localKey; - } - } - - if (!empty($range)) { - $this->where[$foreignKey] = ['in', $range]; - $data = $this->eagerlyOneToMany($model, [ - $foreignKey => [ - 'in', - $range, - ], - ], $relation, $subRelation, $closure); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); - } - } - break; - case self::BELONGS_TO_MANY: - $pk = $resultSet[0]->getPk(); - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - // 查询关联数据 - $data = $this->eagerlyManyToMany($model, [ - 'pivot.' . $localKey => [ - 'in', - $range, - ], - ], $relation, $subRelation); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); - } - } - break; - } - } - return $resultSet; + return $this->query; } /** * 封装关联数据集 * @access public - * @param array $resultSet 数据集 - * @param string $class 数据集类名 + * @param array $resultSet 数据集 * @return mixed */ - protected function resultSetBuild($resultSet, $class = '') - { - return $class ? new $class($resultSet) : $resultSet; - } - - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 - * @return Model - */ - public function eagerlyResult($result, $relation, $class = '') - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); - } - // 执行关联方法 - $model = $this->parent->$relation(); - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - // 模型关联组装 - $this->match($this->model, $relation, $result); - break; - case self::HAS_MANY: - if (isset($result->$localKey)) { - $data = $this->eagerlyOneToMany($model, [$foreignKey => $result->$localKey], $relation, $subRelation, $closure); - // 关联数据封装 - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); - } - break; - case self::BELONGS_TO_MANY: - $pk = $result->getPk(); - if (isset($result->$pk)) { - $pk = $result->$pk; - // 查询管理数据 - $data = $this->eagerlyManyToMany($model, ['pivot.' . $localKey => $pk], $relation, $subRelation); - - // 关联数据封装 - if (!isset($data[$pk])) { - $data[$pk] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$pk], $class)); - } - break; - - } - } - return $result; - } - - /** - * 一对一 关联模型预查询拼装 - * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 - * @return void - */ - protected function match($model, $relation, &$result) - { - // 重新组装模型数据 - foreach ($result->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ($name == $relation) { - $list[$name][$attr] = $val; - unset($result->$key); - } - } - } - - $result->setAttr($relation, !isset($list[$relation]) ? null : (new $model($list[$relation]))->isUpdate(true)); - } - - /** - * 一对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure - * @return array - */ - protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) - { - $foreignKey = $this->foreignKey; - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - } - $list = $model->where($where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$foreignKey][] = $set; - } - return $data; - } - - /** - * 多对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @return array - */ - protected function eagerlyManyToMany($model, $where, $relation, $subRelation = '') - { - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($model, $this->middle, $foreignKey, $localKey, $where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); - $data[$pivot[$localKey]][] = $set; - } - return $data; - } - - /** - * 设置当前关联定义的数据表别名 - * @access public - * @param array $alias 别名定义 - * @return $this - */ - public function setAlias($alias) - { - $this->alias = $alias; - return $this; - } - - /** - * HAS ONE 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return $this - */ - public function hasOne($model, $foreignKey, $localKey, $alias = [], $joinType = 'INNER') - { - $this->type = self::HAS_ONE; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * BELONGS TO 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $otherKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return $this - */ - public function belongsTo($model, $foreignKey, $otherKey, $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $this->type = self::BELONGS_TO; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $otherKey; - $this->alias = $alias; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * HAS MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return $this - */ - public function hasMany($model, $foreignKey, $localKey, $alias) - { - // 记录当前关联信息 - $this->type = self::HAS_MANY; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * HAS MANY 远程关联定义 - * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $firstkey 关联外键 - * @param string $secondKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return $this - */ - public function hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias) + protected function resultSetBuild($resultSet) { - // 记录当前关联信息 - $this->type = self::HAS_MANY_THROUGH; - $this->model = $model; - $this->middle = $through; - $this->foreignKey = $foreignKey; - $this->throughKey = $throughKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; + return (new $this->model)->toCollection($resultSet); } /** - * BELONGS TO MANY 关联定义 + * 移除关联查询参数 * @access public - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 * @return $this */ - public function belongsToMany($model, $table, $foreignKey, $localKey, $alias) + public function removeOption() { - // 记录当前关联信息 - $this->type = self::BELONGS_TO_MANY; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->middle = $table; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 + $this->query->removeOption(); return $this; } /** - * BELONGS TO MANY 关联查询 - * @access public - * @param object $model 关联模型对象 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 - * @return \think\db\Query|string - */ - protected function belongsToManyQuery($model, $table, $foreignKey, $localKey, $condition = []) - { - // 关联查询封装 - $tableName = $model->getTable(); - $relationFk = $model->getPk(); - return $model::field($tableName . '.*') - ->field(true, false, $table, 'pivot', 'pivot__') - ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) - ->where($condition); - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function save($data, array $pivot = []) - { - // 判断关联类型 - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - case self::HAS_MANY: - if ($data instanceof Model) { - $data = $data->getData(); - } - // 保存关联表数据 - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $model = new $this->model; - return $model->save($data); - case self::BELONGS_TO_MANY: - // 保存关联表/中间表数据 - return $this->attach($data, $pivot); - } - } - - /** - * 批量保存当前关联数据对象 - * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function saveAll(array $dataSet, array $pivot = []) - { - $result = false; - foreach ($dataSet as $key => $data) { - // 判断关联类型 - switch ($this->type) { - case self::HAS_MANY: - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $result = $this->save($data); - break; - case self::BELONGS_TO_MANY: - // TODO - $result = $this->attach($data, !empty($pivot) ? $pivot[$key] : []); - break; - } - } - return $result; - } - - /** - * 附加关联的一个中间表数据 - * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function attach($data, $pivot = []) - { - if (is_array($data)) { - // 保存关联表数据 - $model = new $this->model; - $model->save($data); - $id = $model->getLastInsID(); - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - - if ($id) { - // 保存中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - $pivot[$this->foreignKey] = $id; - $query = clone $this->parent->db(); - return $query->table($this->middle)->insert($pivot); - } else { - throw new Exception('miss relation data'); - } - } - - /** - * 解除关联的一个中间表数据 - * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 - * @return integer + * 执行基础查询(进执行一次) + * @access protected + * @return void */ - public function detach($data, $relationDel = false) - { - if (is_array($data)) { - $id = $data; - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - // 删除中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - if (isset($id)) { - $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; - } - $query = clone $this->parent->db(); - $query->table($this->middle)->where($pivot)->delete(); - - // 删除关联表数据 - if (isset($id) && $relationDel) { - $model = $this->model; - $model::destroy($id); - } - } + abstract protected function baseQuery(); public function __call($method, $args) { if ($this->query) { - switch ($this->type) { - case self::HAS_MANY: - if (isset($this->where)) { - $this->query->where($this->where); - } elseif (isset($this->parent->{$this->localKey})) { - // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); - } - break; - case self::HAS_MANY_THROUGH: - $through = $this->middle; - $model = $this->model; - $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); - $throughTable = $through::getTable(); - $pk = (new $this->model)->getPk(); - $throughKey = $this->throughKey; - $modelTable = $this->parent->getTable(); - $this->query->field($alias . '.*')->alias($alias) - ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) - ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) - ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); - break; - case self::BELONGS_TO_MANY: - // TODO + // 执行基础查询 + $this->baseQuery(); - } $result = call_user_func_array([$this->query, $method], $args); - if ($result instanceof \think\db\Query) { + if ($result instanceof Query) { + $this->option = $result->getOptions(); return $this; } else { + $this->option = []; + $this->baseQuery = false; return $result; } } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } } - } diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php new file mode 100644 index 00000000..5cee16d8 --- /dev/null +++ b/library/think/model/relation/BelongsTo.php @@ -0,0 +1,171 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +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类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->query->where($this->localKey, $this->parent->$foreignKey)->relation($subRelation)->find(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $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]); + } + } + } + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyWhere($this, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + // 设置关联属性 + $result->setAttr($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @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; + $data = $this->eagerlyWhere($this, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + } + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + // 设置关联属性 + $result->setAttr(Loader::parseName($relation), $relationModel); + } + +} diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php new file mode 100644 index 00000000..691ad846 --- /dev/null +++ b/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,390 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; + +class BelongsToMany extends Relation +{ + // 中间表模型 + protected $middle; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->middle = $table; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $middle = $this->middle; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 关联查询 + $pk = $this->parent->getPk(); + $condition['pivot.' . $localKey] = $this->parent->$pk; + $result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->relation($subRelation)->select(); + foreach ($result as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->pivot = new Pivot($pivot, $this->middle); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + 'pivot.' . $localKey => [ + 'in', + $range, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + return $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + ], + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->pivot = new Pivot($pivot, $this->middle); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $table 中间表名 + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($table, $foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $relationFk = $this->query->getPk(); + return $this->query->field($tableName . '.*') + ->field(true, false, $table, 'pivot', 'pivot__') + ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->query->table($this->middle)->insert($pivot, true); + $result[] = new Pivot($pivot, $this->middle); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->query->table($this->middle)->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + $pk = $this->parent->getPk(); + $this->query->join($this->middle . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php new file mode 100644 index 00000000..9bcdcead --- /dev/null +++ b/library/think/model/relation/HasMany.php @@ -0,0 +1,273 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + $result->setAttr($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $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(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->query->where([ + $this->foreignKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + ], + ])->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + return $this->parent->db()->alias('a') + ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $joinType) + ->group('b.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $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]); + } + } + } + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + } + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php new file mode 100644 index 00000000..04b71e04 --- /dev/null +++ b/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,150 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 架构函数 + * @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->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param string $class 数据集对象名 为空表示数组 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param string $class 数据集对象名 为空表示数组 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + $through = $this->through; + $model = $this->model; + $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); + $throughTable = $through::getTable(); + $pk = (new $this->model)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php new file mode 100644 index 00000000..9d4f5f79 --- /dev/null +++ b/library/think/model/relation/HasOne.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 判断关联类型执行查询 + return $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @return Query + */ + public function has() + { + $table = $this->query->getTable(); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db()->alias('a') + ->whereExists(function ($query) use ($table, $localKey, $foreignKey) { + $query->table([$table => 'b'])->field('b.' . $foreignKey)->whereExp('a.' . $localKey, '=b.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $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]); + } + } + } + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyWhere($this, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + } + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + // 设置关联属性 + $result->setAttr($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @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; + $data = $this->eagerlyWhere($this, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + } + + if ($relationModel && !empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + $result->setAttr(Loader::parseName($relation), $relationModel); + } + +} diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php new file mode 100644 index 00000000..4dbd2ef5 --- /dev/null +++ b/library/think/model/relation/MorphMany.php @@ -0,0 +1,265 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $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; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + $model = new $this->model; + return $model->save($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php new file mode 100644 index 00000000..04df0e79 --- /dev/null +++ b/library/think/model/relation/MorphTo.php @@ -0,0 +1,223 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = []) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + // 主键数据 + $pk = $this->parent->$morphKey; + return (new $model)->relation($subRelation)->find($pk); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $result->setAttr($attr, $data[$result->$morphKey]); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->isUpdate(true); + } + $result->setAttr(Loader::parseName($relation), $data ?: null); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + } +} diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php new file mode 100644 index 00000000..18b92225 --- /dev/null +++ b/library/think/model/relation/OneToOne.php @@ -0,0 +1,316 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', $query->getModel()))); + $alias = $name; + if ($first) { + $table = $query->getTable(); + $query->table([$table => $alias]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $alias); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [& $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } else { + $field = true; + } + $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + $this->removeOption(); + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } + $result->setAttr(Loader::parseName($relation), !isset($relationModel) ? null : $relationModel->isUpdate(true)); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model->$attr); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [& $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + } +} diff --git a/library/think/paginator/driver/Bootstrap.php b/library/think/paginator/driver/Bootstrap.php index 87e1fe48..58fa9436 100644 --- a/library/think/paginator/driver/Bootstrap.php +++ b/library/think/paginator/driver/Bootstrap.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -102,7 +102,6 @@ class Bootstrap extends Paginator return $html; } - /** * 渲染分页html * @return mixed @@ -127,7 +126,6 @@ class Bootstrap extends Paginator } } - /** * 生成一个可点击的按钮 * @@ -204,4 +202,4 @@ class Bootstrap extends Paginator return $this->getAvailablePageWrapper($url, $page); } -} \ No newline at end of file +} diff --git a/library/think/process/Builder.php b/library/think/process/Builder.php index 826e6745..da561639 100644 --- a/library/think/process/Builder.php +++ b/library/think/process/Builder.php @@ -15,10 +15,9 @@ use think\Process; class Builder { - private $arguments; private $cwd; - private $env = null; + private $env = null; private $input; private $timeout = 60; private $options = []; @@ -155,7 +154,7 @@ class Builder return $this; } - $timeout = (float)$timeout; + $timeout = (float) $timeout; if ($timeout < 0) { throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); @@ -231,4 +230,4 @@ class Builder return $process; } -} \ No newline at end of file +} diff --git a/library/think/process/Utils.php b/library/think/process/Utils.php index 3ed136a5..f94c6488 100644 --- a/library/think/process/Utils.php +++ b/library/think/process/Utils.php @@ -60,7 +60,7 @@ class Utils return $input; } if (is_scalar($input)) { - return (string)$input; + return (string) $input; } throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); } @@ -72,4 +72,4 @@ class Utils return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; } -} \ No newline at end of file +} diff --git a/library/think/process/exception/Failed.php b/library/think/process/exception/Failed.php new file mode 100644 index 00000000..52950823 --- /dev/null +++ b/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/library/think/process/exception/Timeout.php b/library/think/process/exception/Timeout.php index c085ee8e..d5f1162f 100644 --- a/library/think/process/exception/Timeout.php +++ b/library/think/process/exception/Timeout.php @@ -58,4 +58,4 @@ class Timeout extends \RuntimeException throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); } } -} \ No newline at end of file +} diff --git a/library/think/process/pipes/Pipes.php b/library/think/process/pipes/Pipes.php index 51da44f3..82396b8f 100644 --- a/library/think/process/pipes/Pipes.php +++ b/library/think/process/pipes/Pipes.php @@ -53,7 +53,6 @@ abstract class Pipes */ abstract public function areOpen(); - /** * {@inheritdoc} */ @@ -91,4 +90,4 @@ abstract class Pipes $this->blocked = false; } -} \ No newline at end of file +} diff --git a/library/think/process/pipes/Unix.php b/library/think/process/pipes/Unix.php index abfe61b2..fd99a5d6 100644 --- a/library/think/process/pipes/Unix.php +++ b/library/think/process/pipes/Unix.php @@ -25,14 +25,14 @@ class Unix extends Pipes public function __construct($ttyMode, $ptyMode, $input, $disableOutput) { - $this->ttyMode = (bool)$ttyMode; - $this->ptyMode = (bool)$ptyMode; - $this->disableOutput = (bool)$disableOutput; + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; if (is_resource($input)) { $this->input = $input; } else { - $this->inputBuffer = (string)$input; + $this->inputBuffer = (string) $input; } } @@ -134,12 +134,12 @@ class Unix extends Pipes $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; $data = ''; - while ('' !== $dataread = (string)fread($pipe, self::CHUNK_SIZE)) { + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { $data .= $dataread; } if ('' !== $data) { - if ($type === 'input') { + if ('input' === $type) { $this->inputBuffer .= $data; } else { $read[$type] = $data; @@ -147,7 +147,7 @@ class Unix extends Pipes } if (false === $data || (true === $close && feof($pipe) && '' === $data)) { - if ($type === 'input') { + if ('input' === $type) { $this->input = null; } else { fclose($this->pipes[$type]); @@ -160,7 +160,7 @@ class Unix extends Pipes while (strlen($this->inputBuffer)) { $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k if ($written > 0) { - $this->inputBuffer = (string)substr($this->inputBuffer, $written); + $this->inputBuffer = (string) substr($this->inputBuffer, $written); } else { break; } @@ -180,7 +180,7 @@ class Unix extends Pipes */ public function areOpen() { - return (bool)$this->pipes; + return (bool) $this->pipes; } /** @@ -193,4 +193,4 @@ class Unix extends Pipes { return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); } -} \ No newline at end of file +} diff --git a/library/think/process/pipes/Windows.php b/library/think/process/pipes/Windows.php index 2cc44ca7..bba7e9b8 100644 --- a/library/think/process/pipes/Windows.php +++ b/library/think/process/pipes/Windows.php @@ -30,7 +30,7 @@ class Windows extends Pipes public function __construct($disableOutput, $input) { - $this->disableOutput = (bool)$disableOutput; + $this->disableOutput = (bool) $disableOutput; if (!$this->disableOutput) { @@ -128,7 +128,7 @@ class Windows extends Pipes */ public function areOpen() { - return (bool)$this->pipes && (bool)$this->fileHandles; + return (bool) $this->pipes && (bool) $this->fileHandles; } /** @@ -213,7 +213,7 @@ class Windows extends Pipes while (strlen($this->inputBuffer)) { $written = fwrite($w[0], $this->inputBuffer, 2 << 18); if ($written > 0) { - $this->inputBuffer = (string)substr($this->inputBuffer, $written); + $this->inputBuffer = (string) substr($this->inputBuffer, $written); } else { break; } @@ -225,4 +225,4 @@ class Windows extends Pipes unset($this->pipes[0]); } } -} \ No newline at end of file +} diff --git a/library/think/response/Json.php b/library/think/response/Json.php index a137f453..538fc5a0 100644 --- a/library/think/response/Json.php +++ b/library/think/response/Json.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -27,17 +27,25 @@ class Json extends Response * @access protected * @param mixed $data 要处理的数据 * @return mixed + * @throws \Exception */ protected function output($data) { - // 返回JSON数据格式到客户端 包含状态信息 - $data = json_encode($data, $this->options['json_encode_param']); - - if ($data === false) { - throw new \InvalidArgumentException(json_last_error_msg()); + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; } - - return $data; } } diff --git a/library/think/response/Jsonp.php b/library/think/response/Jsonp.php index fda1183a..de8fb304 100644 --- a/library/think/response/Jsonp.php +++ b/library/think/response/Jsonp.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -30,21 +30,29 @@ class Jsonp extends Response * @access protected * @param mixed $data 要处理的数据 * @return mixed + * @throws \Exception */ protected function output($data) { - // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); - $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; - - $data = json_encode($data, $this->options['json_encode_param']); - - if ($data === false) { - throw new \InvalidArgumentException(json_last_error_msg()); + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; } - - $data = $handler . '(' . $data . ');'; - return $data; } } diff --git a/library/think/response/Redirect.php b/library/think/response/Redirect.php index 82a5fe32..f2002779 100644 --- a/library/think/response/Redirect.php +++ b/library/think/response/Redirect.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -53,10 +53,10 @@ class Redirect extends Response { if (is_array($name)) { foreach ($name as $key => $val) { - Session::set($key, $val); + Session::flash($key, $val); } } else { - Session::set($name, $value); + Session::flash($name, $value); } return $this; } @@ -78,14 +78,17 @@ class Redirect extends Response /** * 记住当前url后跳转 + * @return $this */ public function remember() { Session::set('redirect_url', Request::instance()->url()); + return $this; } /** * 跳转到上次记住的url + * @return $this */ public function restore() { @@ -93,5 +96,6 @@ class Redirect extends Response $this->data = Session::get('redirect_url'); Session::delete('redirect_url'); } + return $this; } } diff --git a/library/think/response/View.php b/library/think/response/View.php index 3a5f3660..de75515a 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/response/Xml.php b/library/think/response/Xml.php index 3a2c59bd..ca12e295 100644 --- a/library/think/response/Xml.php +++ b/library/think/response/Xml.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/session/driver/Memcache.php b/library/think/session/driver/Memcache.php index 2c040271..0c02e23e 100644 --- a/library/think/session/driver/Memcache.php +++ b/library/think/session/driver/Memcache.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -79,7 +79,7 @@ class Memcache extends SessionHandler */ public function read($sessID) { - return $this->handler->get($this->config['session_name'] . $sessID); + return (string) $this->handler->get($this->config['session_name'] . $sessID); } /** @@ -87,6 +87,7 @@ class Memcache extends SessionHandler * @access public * @param string $sessID * @param String $sessData + * @return bool */ public function write($sessID, $sessData) { @@ -97,6 +98,7 @@ class Memcache extends SessionHandler * 删除Session * @access public * @param string $sessID + * @return bool */ public function destroy($sessID) { @@ -107,6 +109,7 @@ class Memcache extends SessionHandler * Session 垃圾回收 * @access public * @param string $sessMaxLifeTime + * @return true */ public function gc($sessMaxLifeTime) { diff --git a/library/think/session/driver/Memcached.php b/library/think/session/driver/Memcached.php index 4187204a..bcaf8a1b 100644 --- a/library/think/session/driver/Memcached.php +++ b/library/think/session/driver/Memcached.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -87,7 +87,7 @@ class Memcached extends SessionHandler */ public function read($sessID) { - return $this->handler->get($this->config['session_name'] . $sessID); + return (string) $this->handler->get($this->config['session_name'] . $sessID); } /** @@ -95,6 +95,7 @@ class Memcached extends SessionHandler * @access public * @param string $sessID * @param String $sessData + * @return bool */ public function write($sessID, $sessData) { @@ -105,6 +106,7 @@ class Memcached extends SessionHandler * 删除Session * @access public * @param string $sessID + * @return bool */ public function destroy($sessID) { @@ -115,6 +117,7 @@ class Memcached extends SessionHandler * Session 垃圾回收 * @access public * @param string $sessMaxLifeTime + * @return true */ public function gc($sessMaxLifeTime) { diff --git a/library/think/session/driver/Redis.php b/library/think/session/driver/Redis.php index e3dc9983..a4c2b54a 100644 --- a/library/think/session/driver/Redis.php +++ b/library/think/session/driver/Redis.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -81,11 +81,11 @@ class Redis extends SessionHandler * 读取Session * @access public * @param string $sessID - * @return bool|string + * @return string */ public function read($sessID) { - return $this->handler->get($this->config['session_name'] . $sessID); + return (string) $this->handler->get($this->config['session_name'] . $sessID); } /** @@ -108,11 +108,11 @@ class Redis extends SessionHandler * 删除Session * @access public * @param string $sessID - * @return bool|void + * @return bool */ public function destroy($sessID) { - $this->handler->delete($this->config['session_name'] . $sessID); + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; } /** diff --git a/library/think/template/TagLib.php b/library/think/template/TagLib.php index 101593bf..b3325191 100644 --- a/library/think/template/TagLib.php +++ b/library/think/template/TagLib.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/template/driver/File.php b/library/think/template/driver/File.php index 1cd041af..b27e7265 100644 --- a/library/think/template/driver/File.php +++ b/library/think/template/driver/File.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/template/taglib/Cx.php b/library/think/template/taglib/Cx.php index b15c7bba..af7a54c8 100644 --- a/library/think/template/taglib/Cx.php +++ b/library/think/template/taglib/Cx.php @@ -98,7 +98,7 @@ class Cx extends Taglib $name = $this->autoBuildVar($name); } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection): $' . $key . ' = 0;'; + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; @@ -158,7 +158,7 @@ class Cx extends Taglib } else { $name = $this->autoBuildVar($name); } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection): '; + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { if (!isset($var)) { @@ -431,7 +431,7 @@ class Cx extends Taglib { $name = $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty())): ?>' . $content . ''; + $parseStr = 'isEmpty())): ?>' . $content . ''; return $parseStr; } @@ -448,7 +448,7 @@ class Cx extends Taglib { $name = $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty()))): ?>' . $content . ''; + $parseStr = 'isEmpty()))): ?>' . $content . ''; return $parseStr; } @@ -620,8 +620,8 @@ class Cx extends Taglib return $parseStr; } - /** - * U函数的tag标签 + /** + * url函数的tag标签 * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} * @access public * @param array $tag 标签属性 diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 31fe7807..468d3611 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -21,6 +21,8 @@ class Php { // 模板引擎参数 protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', // 模板起始路径 'view_path' => '', // 模板文件后缀 @@ -109,25 +111,34 @@ class Php $this->config['view_path'] = App::$modulePath . 'view' . DS; } + $request = Request::instance(); + // 获取视图根目录 if (strpos($template, '@')) { + // 跨模块调用 list($module, $template) = explode('@', $template); - $path = APP_PATH . $module . DS . 'view' . DS; + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); } else { - $path = $this->config['view_path']; + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; } - // 分析模板文件规则 - $request = Request::instance(); - $controller = Loader::parseName($request->controller()); - if ($controller && 0 !== strpos($template, '/')) { - $depr = $this->config['view_depr']; - $template = str_replace(['/', ':'], $depr, $template); - if ('' == $template) { - // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . $request->action(); - } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . $request->action(); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); } return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 33729b44..39a14ca3 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -24,6 +24,8 @@ class Think private $template; // 模板引擎参数 protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', // 模板起始路径 'view_path' => '', // 模板文件后缀 @@ -103,28 +105,35 @@ class Think */ private function parseTemplate($template) { + // 分析模板文件规则 + $request = Request::instance(); // 获取视图根目录 if (strpos($template, '@')) { // 跨模块调用 list($module, $template) = explode('@', $template); - $path = APP_PATH . $module . DS . 'view' . DS; + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); } else { - // 当前视图目录 - $path = $this->config['view_path']; + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; } - // 分析模板文件规则 - $request = Request::instance(); - $controller = Loader::parseName($request->controller()); - if ($controller && 0 !== strpos($template, '/')) { - $depr = $this->config['view_depr']; - $template = str_replace(['/', ':'], $depr, $template); - if ('' == $template) { - // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . $request->action(); - } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . $request->action(); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); } return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } diff --git a/library/traits/controller/Jump.php b/library/traits/controller/Jump.php index 0fad996d..6e6f2dec 100644 --- a/library/traits/controller/Jump.php +++ b/library/traits/controller/Jump.php @@ -81,7 +81,7 @@ trait Jump $msg = ''; } if (is_null($url)) { - $url = 'javascript:history.back(-1);'; + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; } elseif ('' !== $url) { $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Url::build($url); } @@ -131,16 +131,17 @@ trait Jump * @param string $url 跳转的URL表达式 * @param array|integer $params 其它URL参数 * @param integer $code http code + * @param array $with 隐式传参 * @return void */ - protected function redirect($url, $params = [], $code = 302) + protected function redirect($url, $params = [], $code = 302, $with = []) { $response = new Redirect($url); if (is_integer($params)) { $code = $params; $params = []; } - $response->code($code)->params($params); + $response->code($code)->params($params)->with($with); throw new HttpResponseException($response); } diff --git a/library/traits/model/SoftDelete.php b/library/traits/model/SoftDelete.php index e08d96aa..2b97ff72 100644 --- a/library/traits/model/SoftDelete.php +++ b/library/traits/model/SoftDelete.php @@ -2,6 +2,8 @@ namespace traits\model; +use think\db\Query; + trait SoftDelete { @@ -22,24 +24,25 @@ trait SoftDelete /** * 查询软删除数据 * @access public - * @return \think\db\Query + * @return Query */ public static function withTrashed() { $model = new static(); - return $model->db(); + $field = $model->getDeleteTimeField(true); + return $model->db(false)->removeWhereField($field); } /** * 只查询软删除数据 * @access public - * @return \think\db\Query + * @return Query */ public static function onlyTrashed() { $model = new static(); - $field = $model->getDeleteTimeField(); - return $model->db()->where($field, 'exp', 'is not null'); + $field = $model->getDeleteTimeField(true); + return $model->db(false)->whereNotNull($field); } /** @@ -60,7 +63,7 @@ trait SoftDelete $this->data[$name] = $this->autoWriteTimestamp($name); $result = $this->isUpdate()->save(); } else { - $result = $this->db()->delete($this->data); + $result = $this->db(false)->delete($this->data); } $this->trigger('after_delete', $this); @@ -76,8 +79,8 @@ trait SoftDelete */ public static function destroy($data, $force = false) { - $model = new static(); - $query = $model->db(); + // 包含软删除数据 + $query = self::withTrashed(); if (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; @@ -108,21 +111,25 @@ trait SoftDelete public function restore($where = []) { $name = $this->getDeleteTimeField(); + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + $where[$name] = ['not null', '']; + } // 恢复删除 - return $this->isUpdate()->save([$name => null], $where); - + return $this->db(false)->removeWhereField($this->getDeleteTimeField(true))->where($where)->update([$name => null]); } /** * 查询默认不包含软删除数据 * @access protected - * @param \think\db\Query $query 查询对象 + * @param Query $query 查询对象 * @return void */ protected function base($query) { $field = $this->getDeleteTimeField(true); - $query->where($field, 'null'); + $query->whereNull($field); } /** @@ -133,13 +140,13 @@ trait SoftDelete */ protected function getDeleteTimeField($read = false) { - if (isset($this->deleteTime)) { - $field = $this->deleteTime; - } else { - $field = 'delete_time'; + $field = isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + if (!strpos($field, '.')) { + $field = '__TABLE__.' . $field; } if (!$read && strpos($field, '.')) { - list($alias, $field) = explode('.', $field); + $array = explode('.', $field); + $field = array_pop($array); } return $field; } diff --git a/library/traits/think/Instance.php b/library/traits/think/Instance.php index 206a9412..ba45ddd8 100644 --- a/library/traits/think/Instance.php +++ b/library/traits/think/Instance.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -11,7 +11,7 @@ namespace traits\think; -use \think\Exception; +use think\Exception; trait Instance { -- Gitee From fa28104312d4c1bce818b845c00cbbfb97c5ad1c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 13 Feb 2017 14:27:55 +0800 Subject: [PATCH 0004/1384] =?UTF-8?q?=E4=BD=BF=E7=94=A8array=5Fcolumn?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E6=94=B9=E5=86=99column=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 5e258d71..5891197d 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -472,15 +472,14 @@ class Query if (strpos($key, '.')) { list($alias, $key) = explode('.', $key); } - foreach ($resultSet as $val) { - if ($count > 2) { - $result[$val[$key]] = $val; - } elseif (2 == $count) { - $result[$val[$key]] = $val[$key2]; - } elseif (1 == $count) { - $result[$val[$key]] = $val[$key1]; - } + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; + } else { + $column = null; } + $result = array_column($resultSet, $column, $key); } else { $result = []; } -- Gitee From 9816cfeefff2c322f4e3a6f4c759ab050e9ae060 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 13 Feb 2017 14:30:50 +0800 Subject: [PATCH 0005/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=A6=81=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a6911dd4..ca0225da 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -ThinkPHP 5.1 +ThinkPHP 5.1 Alpha =============== -ThinkPHP5.1 Alpha +## 环境要求 -> ThinkPHP5.1的运行环境要求PHP5.6以上。 +> PHP5.6+ ## 命名规范 @@ -20,7 +20,7 @@ ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 本项目包含的第三方源码和二进制文件之版权信息另行标注。 -版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) +版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) All rights reserved。 -- Gitee From 27f1f48ee92a777646add9c8ff0dfedefb1a2816 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 13 Feb 2017 16:00:51 +0800 Subject: [PATCH 0006/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=92=8C=E6=95=B0=E6=8D=AE=E9=9B=86=E7=9A=84=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Debug.php | 5 ++++- library/think/Model.php | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/library/think/Debug.php b/library/think/Debug.php index 844dfb89..02a07ed2 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -161,6 +161,9 @@ class Debug public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) { $label = (null === $label) ? '' : rtrim($label) . ':'; + if ($var instanceof \think\Model || $var instanceof \think\model\Collection) { + $var = $var->toArray(); + } ob_start(); var_dump($var); $output = ob_get_clean(); @@ -174,7 +177,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo($output); + echo ($output); return; } else { return $output; diff --git a/library/think/Model.php b/library/think/Model.php index 1222b548..e41b56e5 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -1866,6 +1866,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->initialize(); } + public function __debugInfo() + { + return [ + 'data' => $this->data, + ]; + } + /** * 模型事件快捷方法 * @param $callback -- Gitee From 455e2ec2addfefb8c79b868459b0662510b46134 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 14 Feb 2017 08:11:12 +0800 Subject: [PATCH 0007/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BModel=E7=B1=BBdelet?= =?UTF-8?q?e=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index e41b56e5..211ad9de 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -1081,7 +1081,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 删除条件 $pk = $this->getPk(); - if (isset($this->data[$pk])) { + if (is_string($pk) && isset($this->data[$pk])) { $where = [$pk => $this->data[$pk]]; } elseif (!empty($this->updateWhere)) { $where = $this->updateWhere; -- Gitee From 7c0a4bbc43fa3e441ad97c508db2b15b932bb50e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 14 Feb 2017 16:23:40 +0800 Subject: [PATCH 0008/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=9A=84find?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=9A=84=E6=9F=A5=E8=AF=A2=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 5891197d..fe610f5e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2301,7 +2301,7 @@ class Query } } - if (isset($cache)) { + if (isset($cache) && $resultSet) { // 缓存数据集 $this->cacheData($key, $resultSet, $cache); } @@ -2458,7 +2458,7 @@ class Query $result = isset($resultSet[0]) ? $resultSet[0] : null; } - if (isset($cache)) { + if (isset($cache) && $result) { // 缓存数据 $this->cacheData($key, $result, $cache); } -- Gitee From cbc6b43791762c2706c2d1276da623e5ab13bc68 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 15 Feb 2017 08:45:39 +0800 Subject: [PATCH 0009/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Paginator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 0c1bea8a..d3547f8b 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -341,7 +341,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J { try { $total = $this->total(); - } catch (Exception $e) { + } catch (\DomainException $e) { $total = null; } @@ -349,7 +349,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J 'total' => $total, 'per_page' => $this->listRows(), 'current_page' => $this->currentPage(), - 'data' => $this->items->toArray() + 'data' => $this->items->toArray(), ]; } -- Gitee From e0f2b6b8c6053f7f3b0feef83d59f2820bf27046 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 15 Feb 2017 15:13:58 +0800 Subject: [PATCH 0010/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=AF=B9=E5=85=B3?= =?UTF-8?q?=E8=81=94=E5=B1=9E=E6=80=A7=E7=9A=84=E8=8E=B7=E5=8F=96=E5=99=A8?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/think/Model.php b/library/think/Model.php index 211ad9de..d1fdb6ee 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -433,6 +433,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检测属性获取器 $method = 'get' . Loader::parseName($name, 1) . 'Attr'; if (method_exists($this, $method)) { + $reltion = Loader::parseName($name, 1, false); + if ($notFound && method_exists($this, $relation) && $this->$relation() instanceof Relation) { + // 首先获取关联数据 + $this->$relation()->removeOption(); + $value = $this->$relation()->getRelation(); + } $value = $this->$method($value, $this->data); } elseif (isset($this->type[$name])) { // 类型转换 -- Gitee From 442249d6af77c6f07f08a082c3b1a62f52b7cf56 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 15 Feb 2017 15:37:57 +0800 Subject: [PATCH 0011/1384] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 56 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/convention.php b/convention.php index f7486a48..b6f23e28 100644 --- a/convention.php +++ b/convention.php @@ -58,6 +58,8 @@ return [ 'default_validate' => '', // 默认的空控制器名 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, // 操作方法后缀 'action_suffix' => '', // 自动搜索控制器 @@ -83,6 +85,8 @@ return [ 'url_route_on' => true, // 路由配置文件(支持配置多个) 'route_config_file' => ['route'], + // 路由使用完整匹配 + 'route_complete_match' => false, // 是否强制使用路由 'url_route_must' => false, // 域名部署 @@ -99,6 +103,12 @@ return [ 'var_ajax' => '_ajax', // 表单pjax伪装变量 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], // +---------------------------------------------------------------------- // | 模板设置 @@ -107,7 +117,9 @@ return [ 'template' => [ // 模板引擎类型 支持 php think 支持扩展 'type' => 'Think', - // 模板路径 + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 'view_path' => '', // 模板后缀 'view_suffix' => 'html', @@ -193,6 +205,8 @@ return [ 'type' => '', // 是否自动开启 SESSION 'auto_start' => true, + 'httponly' => true, + 'secure' => true, ], // +---------------------------------------------------------------------- @@ -221,39 +235,45 @@ return [ 'database' => [ // 数据库类型 - 'type' => 'mysql', + 'type' => 'mysql', // 数据库连接DSN配置 - 'dsn' => '', + 'dsn' => '', // 服务器地址 - 'hostname' => 'localhost', + 'hostname' => '127.0.0.1', // 数据库名 - 'database' => '', + 'database' => '', // 数据库用户名 - 'username' => 'root', + 'username' => 'root', // 数据库密码 - 'password' => '', + 'password' => '', // 数据库连接端口 - 'hostport' => '', + 'hostport' => '', // 数据库连接参数 - 'params' => [], + 'params' => [], // 数据库编码默认采用utf8 - 'charset' => 'utf8', + 'charset' => 'utf8', // 数据库表前缀 - 'prefix' => '', + 'prefix' => '', // 数据库调试模式 - 'debug' => false, + 'debug' => false, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, + 'deploy' => 0, // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, + 'rw_separate' => false, // 读写分离后 主服务器数量 - 'master_num' => 1, + 'master_num' => 1, // 指定从服务器序号 - 'slave_no' => '', + 'slave_no' => '', // 是否严格检查字段是否存在 - 'fields_strict' => true, + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', // 自动写入时间戳字段 - 'auto_timestamp' => false, + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, ], //分页配置 -- Gitee From 72ea987a5c3f582cffbec0268da344ac53b86d65 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 15 Feb 2017 15:44:47 +0800 Subject: [PATCH 0012/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index d1fdb6ee..9e38d8fc 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -433,7 +433,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检测属性获取器 $method = 'get' . Loader::parseName($name, 1) . 'Attr'; if (method_exists($this, $method)) { - $reltion = Loader::parseName($name, 1, false); + $relation = Loader::parseName($name, 1, false); if ($notFound && method_exists($this, $relation) && $this->$relation() instanceof Relation) { // 首先获取关联数据 $this->$relation()->removeOption(); -- Gitee From 2c6abcfa2e90bacb6621d924b9e3ce2a6347b70d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 15 Feb 2017 16:20:01 +0800 Subject: [PATCH 0013/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bin/not=20in?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=E4=B8=BA=E7=A9=BA=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 9034076e..bd3579d1 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -379,7 +379,7 @@ abstract class Builder } else { $zone = implode(',', $this->parseValue($value, $field)); } - $whereStr .= $key . ' ' . $exp . ' (' . $zone . ')'; + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; } } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { // BETWEEN 查询 -- Gitee From edec586c0c0f34b114eef5b7ebbf6b3f03acd2a7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 16 Feb 2017 10:57:46 +0800 Subject: [PATCH 0014/1384] =?UTF-8?q?=E5=8E=BB=E9=99=A45.0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codecov.yml | 12 - phpunit.xml | 35 -- tests/.gitignore | 4 - tests/README.md | 132 ---- tests/application/config.php | 33 - tests/application/database.php | 44 -- tests/application/index/controller/Index.php | 10 - tests/application/route.php | 22 - tests/conf/apcu.ini | 3 - tests/conf/apcu_bc.ini | 4 - tests/conf/memcached.ini | 1 - tests/conf/redis.ini | 1 - tests/conf/timezone.ini | 1 - tests/extensions/5.4/apcu.so | Bin 404136 -> 0 bytes tests/extensions/5.5/apcu.so | Bin 402984 -> 0 bytes tests/extensions/5.6/apcu.so | Bin 404405 -> 0 bytes tests/extensions/7.0/apc.so | Bin 43511 -> 0 bytes tests/extensions/7.0/apcu.so | Bin 320965 -> 0 bytes tests/extensions/7.0/memcached.so | Bin 323764 -> 0 bytes tests/extensions/7.0/redis.so | Bin 1541380 -> 0 bytes tests/mock.php | 20 - tests/script/install.sh | 18 - tests/thinkphp/baseTest.php | 39 -- tests/thinkphp/library/think/appTest.php | 91 --- tests/thinkphp/library/think/behavior/One.php | 15 - .../thinkphp/library/think/behavior/Three.php | 9 - tests/thinkphp/library/think/behavior/Two.php | 9 - tests/thinkphp/library/think/buildTest.php | 73 --- .../think/cache/driver/cacheTestCase.php | 207 ------- .../library/think/cache/driver/fileTest.php | 46 -- .../library/think/cache/driver/liteTest.php | 69 --- .../think/cache/driver/memcacheTest.php | 49 -- .../think/cache/driver/memcachedTest.php | 72 --- .../library/think/cache/driver/redisTest.php | 66 -- .../think/config/driver/fixtures/config.ini | 1 - .../think/config/driver/fixtures/config.json | 1 - .../think/config/driver/fixtures/config.xml | 6 - .../library/think/config/driver/iniTest.php | 33 - .../library/think/config/driver/jsonTest.php | 33 - .../library/think/config/driver/xmlTest.php | 33 - tests/thinkphp/library/think/configTest.php | 169 ----- .../library/think/controller/.gitignore | 2 - .../thinkphp/library/think/controllerTest.php | 194 ------ tests/thinkphp/library/think/cookieTest.php | 150 ----- .../library/think/db/driver/.gitignore | 2 - tests/thinkphp/library/think/dbTest.php | 27 - tests/thinkphp/library/think/debugTest.php | 179 ------ tests/thinkphp/library/think/display.html | 1 - .../thinkphp/library/think/exceptionTest.php | 52 -- tests/thinkphp/library/think/extend.html | 2 - tests/thinkphp/library/think/extend2.html | 17 - tests/thinkphp/library/think/hookTest.php | 67 -- tests/thinkphp/library/think/include.html | 2 - tests/thinkphp/library/think/include2.html | 1 - tests/thinkphp/library/think/lang/lang.php | 4 - tests/thinkphp/library/think/langTest.php | 76 --- tests/thinkphp/library/think/layout.html | 2 - tests/thinkphp/library/think/layout2.html | 2 - .../library/think/loader/test/Hello.php | 7 - tests/thinkphp/library/think/loaderTest.php | 71 --- .../library/think/log/driver/fileTest.php | 34 -- tests/thinkphp/library/think/logTest.php | 51 -- tests/thinkphp/library/think/model/.gitignore | 2 - tests/thinkphp/library/think/paginateTest.php | 44 -- tests/thinkphp/library/think/requestTest.php | 186 ------ tests/thinkphp/library/think/responseTest.php | 95 --- tests/thinkphp/library/think/routeTest.php | 287 --------- .../thinkphp/library/think/session/.gitignore | 2 - tests/thinkphp/library/think/sessionTest.php | 319 ---------- .../library/think/template/driver/.gitignore | 2 - .../library/think/template/taglib/cxTest.php | 575 ------------------ tests/thinkphp/library/think/templateTest.php | 414 ------------- tests/thinkphp/library/think/urlTest.php | 116 ---- tests/thinkphp/library/think/validateTest.php | 200 ------ .../library/think/view/driver/.gitignore | 2 - .../think/view/theme/index/template.html | 14 - tests/thinkphp/library/think/viewTest.php | 76 --- .../library/traits/controller/.gitignore | 2 - .../thinkphp/library/traits/model/.gitignore | 2 - 79 files changed, 4642 deletions(-) delete mode 100644 codecov.yml delete mode 100644 phpunit.xml delete mode 100644 tests/.gitignore delete mode 100644 tests/README.md delete mode 100644 tests/application/config.php delete mode 100644 tests/application/database.php delete mode 100644 tests/application/index/controller/Index.php delete mode 100644 tests/application/route.php delete mode 100644 tests/conf/apcu.ini delete mode 100644 tests/conf/apcu_bc.ini delete mode 100644 tests/conf/memcached.ini delete mode 100644 tests/conf/redis.ini delete mode 100644 tests/conf/timezone.ini delete mode 100755 tests/extensions/5.4/apcu.so delete mode 100755 tests/extensions/5.5/apcu.so delete mode 100755 tests/extensions/5.6/apcu.so delete mode 100755 tests/extensions/7.0/apc.so delete mode 100755 tests/extensions/7.0/apcu.so delete mode 100755 tests/extensions/7.0/memcached.so delete mode 100755 tests/extensions/7.0/redis.so delete mode 100644 tests/mock.php delete mode 100755 tests/script/install.sh delete mode 100644 tests/thinkphp/baseTest.php delete mode 100644 tests/thinkphp/library/think/appTest.php delete mode 100644 tests/thinkphp/library/think/behavior/One.php delete mode 100644 tests/thinkphp/library/think/behavior/Three.php delete mode 100644 tests/thinkphp/library/think/behavior/Two.php delete mode 100644 tests/thinkphp/library/think/buildTest.php delete mode 100644 tests/thinkphp/library/think/cache/driver/cacheTestCase.php delete mode 100644 tests/thinkphp/library/think/cache/driver/fileTest.php delete mode 100644 tests/thinkphp/library/think/cache/driver/liteTest.php delete mode 100644 tests/thinkphp/library/think/cache/driver/memcacheTest.php delete mode 100644 tests/thinkphp/library/think/cache/driver/memcachedTest.php delete mode 100644 tests/thinkphp/library/think/cache/driver/redisTest.php delete mode 100644 tests/thinkphp/library/think/config/driver/fixtures/config.ini delete mode 100644 tests/thinkphp/library/think/config/driver/fixtures/config.json delete mode 100644 tests/thinkphp/library/think/config/driver/fixtures/config.xml delete mode 100644 tests/thinkphp/library/think/config/driver/iniTest.php delete mode 100644 tests/thinkphp/library/think/config/driver/jsonTest.php delete mode 100644 tests/thinkphp/library/think/config/driver/xmlTest.php delete mode 100644 tests/thinkphp/library/think/configTest.php delete mode 100644 tests/thinkphp/library/think/controller/.gitignore delete mode 100644 tests/thinkphp/library/think/controllerTest.php delete mode 100644 tests/thinkphp/library/think/cookieTest.php delete mode 100644 tests/thinkphp/library/think/db/driver/.gitignore delete mode 100644 tests/thinkphp/library/think/dbTest.php delete mode 100644 tests/thinkphp/library/think/debugTest.php delete mode 100644 tests/thinkphp/library/think/display.html delete mode 100644 tests/thinkphp/library/think/exceptionTest.php delete mode 100644 tests/thinkphp/library/think/extend.html delete mode 100644 tests/thinkphp/library/think/extend2.html delete mode 100644 tests/thinkphp/library/think/hookTest.php delete mode 100644 tests/thinkphp/library/think/include.html delete mode 100644 tests/thinkphp/library/think/include2.html delete mode 100644 tests/thinkphp/library/think/lang/lang.php delete mode 100644 tests/thinkphp/library/think/langTest.php delete mode 100644 tests/thinkphp/library/think/layout.html delete mode 100644 tests/thinkphp/library/think/layout2.html delete mode 100644 tests/thinkphp/library/think/loader/test/Hello.php delete mode 100644 tests/thinkphp/library/think/loaderTest.php delete mode 100644 tests/thinkphp/library/think/log/driver/fileTest.php delete mode 100644 tests/thinkphp/library/think/logTest.php delete mode 100644 tests/thinkphp/library/think/model/.gitignore delete mode 100644 tests/thinkphp/library/think/paginateTest.php delete mode 100644 tests/thinkphp/library/think/requestTest.php delete mode 100644 tests/thinkphp/library/think/responseTest.php delete mode 100644 tests/thinkphp/library/think/routeTest.php delete mode 100644 tests/thinkphp/library/think/session/.gitignore delete mode 100644 tests/thinkphp/library/think/sessionTest.php delete mode 100644 tests/thinkphp/library/think/template/driver/.gitignore delete mode 100644 tests/thinkphp/library/think/template/taglib/cxTest.php delete mode 100644 tests/thinkphp/library/think/templateTest.php delete mode 100644 tests/thinkphp/library/think/urlTest.php delete mode 100644 tests/thinkphp/library/think/validateTest.php delete mode 100644 tests/thinkphp/library/think/view/driver/.gitignore delete mode 100644 tests/thinkphp/library/think/view/theme/index/template.html delete mode 100644 tests/thinkphp/library/think/viewTest.php delete mode 100644 tests/thinkphp/library/traits/controller/.gitignore delete mode 100644 tests/thinkphp/library/traits/model/.gitignore diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index bef9d643..00000000 --- a/codecov.yml +++ /dev/null @@ -1,12 +0,0 @@ -comment: - layout: header, changes, diff -coverage: - ignore: - - base.php - - helper.php - - convention.php - - lang/zh-cn.php - - start.php - - console.php - status: - patch: false diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 7c6ef03c..00000000 --- a/phpunit.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - ./tests/thinkphp/ - - - - - - - - ./ - - tests - vendor - - - - - - - - - - diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index a0306ecc..00000000 --- a/tests/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/runtime/ -/application/common.php -/application/demo/ -/application/runtime/ \ No newline at end of file diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 0c1d0869..00000000 --- a/tests/README.md +++ /dev/null @@ -1,132 +0,0 @@ -## 测试目录结构 - -测试文件主要在 tests 文件下面,主要有以下几个文件夹 - -- conf 测试环境配置文件。 -- script 测试环境配置脚本。 -- thinkphp 测试用例和相关文件,与项目文件夹结构一致。 -- mock.php 测试入口文件。 - -## 主要测试流程 - -thinkphp5 的测试的主要流程是跟 thinkphp 的系统流程是相似的,大体的流程为: - -1. 引用 mock.php 文件加载框架 - -2. 根据文件目录,添加测试文件 - -3. 执行单元测试,输出结果 - -## 测试举例 - -例如测试 thinkphp 里的 apc 缓存,将分为以下几个过程: - -1. 创建 apcTest.php 文件 - -该文件应与 apc.php 目录路径 `thinkphp/library/think/cache/driver` 一致,命名空间与目录所在一致,并引用 `PHPUnit_Framework_TestCase`。 - - ```php - markTestSkipped('apc扩展不可用!'); - }; - ``` - - - 编写测试用例 - - *具体写法参照 [PHPUnit 官方文档](https://phpunit.de/manual/4.8/zh_cn/index.html)* - - ```php - public function testGet() - { - App::run(); - $this->assertInstanceOf( - '\think\cache\driver\Apc', - Cache::connect(['type' => 'apc', 'expire' => 1]) - ); - $this->assertTrue(Cache::set('key', 'value')); - $this->assertEquals('value', Cache::get('key')); - $this->assertTrue(Cache::rm('key')); - $this->assertFalse(Cache::get('key')); - $this->assertTrue(Cache::clear('key')); - Config::reset(); - } - ``` - -3. 执行单元测试命令 - - 在项目根目录执行 - - ```bash - $ phpunit - ``` - - 若想看到所有结果,请添加-v参数 - - ```bash - $ phpunit -v - ``` - -4. 输出结果 - -## 相关文档 - -[各个部分单元测试说明](http://www.kancloud.cn/brother_simon/tp5_test/96971 "各部分单元测试说明") - -## 大家一起来 - -单元测试的内容会跟框架同步,测试内容方方面面,是一个相对复杂的模块,同时也是一个值得重视的部分。希望大家能够多多提出意见,多多参与。如果你有任何问题或想法,可以随时提 issue,我们期待着收到听大家的质疑和讨论。 - -## 任务进度 - -单元测试任务进度,请大家认领模块 - -|模块|认领人|进度| -|---|---|---| -|Base||| -|App|Haotong Lin|√| -|Build|刘志淳|| -|Config|Haotong Lin|√| -|Cache||| -|Controller|Haotong Lin|√| -|Cookie|Haotong Lin|√| -|Db||| -|Debug|大漠|√| -|Error|大漠|| -|Exception|Haotong Lin|√| -|Hook|流年|√| -|Input|Haotong Lin|√| -|Lang|流年|√| -|Loader|流年|| -|Log||| -|Model||| -|Response|大漠|√| -|Route|流年|| -|Session|大漠|√| -|Template|oldrind|| -|Url|流年|| -|View|mahuan|| diff --git a/tests/application/config.php b/tests/application/config.php deleted file mode 100644 index 7bf368d6..00000000 --- a/tests/application/config.php +++ /dev/null @@ -1,33 +0,0 @@ - -// +---------------------------------------------------------------------- -// $Id$ - -return [ - 'url_route_on' => true, - 'log' => [ - 'type' => 'trace', // 支持 socket trace file - ], - 'view' => [ - // 模板引擎 - 'engine_type' => 'think', - // 模板引擎配置 - 'engine_config' => [ - // 模板路径 - 'view_path' => '', - // 模板后缀 - 'view_suffix' => '.html', - // 模板文件名分隔符 - 'view_depr' => DS, - ], - // 输出字符串替换 - 'parse_str' => [], - ], -]; diff --git a/tests/application/database.php b/tests/application/database.php deleted file mode 100644 index 24434efb..00000000 --- a/tests/application/database.php +++ /dev/null @@ -1,44 +0,0 @@ - -// +---------------------------------------------------------------------- -// $Id$ - -return [ - // 数据库类型 - 'type' => 'mysql', - // 数据库连接DSN配置 - 'dsn' => '', - // 服务器地址 - 'hostname' => '127.0.0.1', - // 数据库名 - 'database' => '', - // 数据库用户名 - 'username' => 'root', - // 数据库密码 - 'password' => '', - // 数据库连接端口 - 'hostport' => '', - // 数据库连接参数 - 'params' => [], - // 数据库编码默认采用utf8 - 'charset' => 'utf8', - // 数据库表前缀 - 'prefix' => '', - // 数据库调试模式 - 'debug' => true, - // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, - // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, - // 读写分离后 主服务器数量 - 'master_num' => 1, - // 指定从服务器序号 - 'slave_no' => '', -]; diff --git a/tests/application/index/controller/Index.php b/tests/application/index/controller/Index.php deleted file mode 100644 index 803fe952..00000000 --- a/tests/application/index/controller/Index.php +++ /dev/null @@ -1,10 +0,0 @@ -*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

:)

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

[ V5.0 版本由 七牛云 独家赞助发布 ]
'; - } -} diff --git a/tests/application/route.php b/tests/application/route.php deleted file mode 100644 index 45bcf79d..00000000 --- a/tests/application/route.php +++ /dev/null @@ -1,22 +0,0 @@ - -// +---------------------------------------------------------------------- -// $Id$ - -return [ - '__pattern__' => [ - 'name' => '\w+', - ], - '[hello]' => [ - ':id' => ['index/hello', ['method' => 'get'], ['id' => '\d+']], - ':name' => ['index/hello', ['method' => 'post']], - ], - -]; diff --git a/tests/conf/apcu.ini b/tests/conf/apcu.ini deleted file mode 100644 index 9c2abed2..00000000 --- a/tests/conf/apcu.ini +++ /dev/null @@ -1,3 +0,0 @@ -extension=apcu.so - -apc.enable_cli=1 diff --git a/tests/conf/apcu_bc.ini b/tests/conf/apcu_bc.ini deleted file mode 100644 index a7f6182d..00000000 --- a/tests/conf/apcu_bc.ini +++ /dev/null @@ -1,4 +0,0 @@ -extension=apcu.so -extension=apc.so - -apc.enable_cli=1 diff --git a/tests/conf/memcached.ini b/tests/conf/memcached.ini deleted file mode 100644 index 7d09664e..00000000 --- a/tests/conf/memcached.ini +++ /dev/null @@ -1 +0,0 @@ -extension=memcached.so diff --git a/tests/conf/redis.ini b/tests/conf/redis.ini deleted file mode 100644 index 6aecae48..00000000 --- a/tests/conf/redis.ini +++ /dev/null @@ -1 +0,0 @@ -extension=redis.so diff --git a/tests/conf/timezone.ini b/tests/conf/timezone.ini deleted file mode 100644 index ced3e0d7..00000000 --- a/tests/conf/timezone.ini +++ /dev/null @@ -1 +0,0 @@ -date.timezone = UTC \ No newline at end of file diff --git a/tests/extensions/5.4/apcu.so b/tests/extensions/5.4/apcu.so deleted file mode 100755 index 42a24a660e00f2fc2f57494bc4f62172da7cddf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 404136 zcmeFad3aPs+CF>|5{PWwDrnprjZSb)6vm*S9Z2LfHX0EG*CZrCkR>tcmWW$R5>3;# zM#oV^M>DwLGMd2+Hw0yC=s{QL?Ge_h)aI=T~()3Ii}}*uj_li_xt_vwO4cc zzRz=>s(R|Fr=F^FPP`@Ge_nD@l3{;(8Rr;871fgQsuN2-OI5!N!)xRieeiFFF+|9A zor#OYe+-)=b=yb7@Q8C{d*fSulRRI2(-6Vi$219zDH(#sKOXTT8UOM+Gb*n$qf*d5 zHcHyCk7?p6!$`rA-*xJ|^Jo`iA1TXE!8d_Y{$66r3!TSbRpafWeceU4j`jTSe(ZWq zP~|1EOZ`DoqkUA(5p5fUQ@!vVd%+~*_~*@!FT3~Znde>6a*q4voamG5kM-h|QG@T@ z_`31!i*MGjdSjqDXw9(v38x*KJ9NPa&#>etYcp4S4NqF*9ZAVclDrMxltwe%+t-tP zeE;JQT$+5|i4C5=7*mIRIAp5PaC%C`qRNZ|lg8Fm_wBXVv(!l6oV+^O?M|z&9p-b_ zrg%g1Q?lA>le3>XYtf?o5%(HN$=;L#&!ypG)6(5n9JqvaxfkER;(H&y{M;`O#zJ-E z;Q{sgZ|eAW93RE^F?=6aoPBr#Kc2*Q3BFI^`!v4K;` zcR#+r;QK4Szv0`7FFzdK$@r$=%TI3{)9^h2-+uTGz?UDFI=XQ@Nc|p!<6wLb!S_(b zrQ>)QzDMADB)&)Cdo;cteCG%RZfQ@UtI3T6OUOOSh+AbZ&q5rLVMo5jnr?>iZ(iy>71F^xFgF zX5ZAmyc+#s;0ZUMmvqdvM&>OqpLy1Ww?AyY_0YRVR2|rR)Q9KHO}_k{5!=swpyKhy zH%_Qd{q?i<;MjL24!dL1o#tgzhV1xD>bI5e{(fxUX?K4);)o^vCiX75vZbnd$B%c$ zW<2&y?D+`;4!x_~4;t=baA@KkAjs@2or0cjcD@l4|%IPTctI(!aQ$dTc=3RsZ<>)Vsg@>%%Q~CLJ{C3mD~f*o zIM7kvcE^qjKc8H^yU*Egjs1M`=4+=MIdJCZlLL>w^xlgHrcC&5;GBQ1+ME2)_(!%q zH^p65e)sUdes;>+H&yTY(ob$uBGY9v6e(=j@W*k=ekug0UmL2XnAtHLfBfq6M+8=V_SmH4FV>9y{-HI|(vkIJ z@<0B*qITKP>3d7hKjqEc zH&x&I&PSJD6nMKNA7vk6?fCud0cRKso;Ypkdm|poES=J)f63%8p4;jhusfypftJfl zv(N9`wPx}4EaxMy}h`)|Z!#!}~J3W94-{K=TWdg7OX?1?{gAjYCoIq;!rJg6V(6V1bXgCVCP2@=vk1UzuFV%ITMxa z$^LIA(4UfkpLlT3?HxCuXZ?#4*qN2-$!;elsONh~LVA+#mB4>G`t&TnGC{fLC$QVX z1noUNfgjFHAU_+v-jn@{64dh_(9@Irr7%KIcDO5nowp{arz=7K{*=IaJ(ru>ujg`4OfY^spr@yLEQPy8bZ#>>YE;`w~U+n(zARs#DRpTG_?6U6i56Yy&jjEi3r*e8m{ z_SD`13F?`XAV0YyK^$mHP|xcU#Jx8Y*m+Zeb{&~O57&}CwX0u(@%u;ue|S8>{Nhev zw<8khpPWGd6A9XNKmxlRlfd6DPar=k0Y4`}Tsj_h^Wf|Jyp*7xDGBT_JV8AdB(TrU z1p04IAb(hb`d1~0GtZ+zJ^8~~3H)tj0{wLf~O5JbAew-Ab)Rye%YE}yiZIJZ|5c8A55Tsqlh#8jD{a%WrwLcUoY~Uen#7F zNnEJ-UFesCjDAK(y(AER#piweOTM{QVz`I+T!3*wzCmqD{2Beh&pr{i`xyn#NIY9z z@VuDsz^9@hd@hxzjf>zX)KkEX6?|S)@@bft$-6(2f^~{tBKi^aZrROwCO)sJa>d3Im?IQFUGRTg z=Z;k6dKbxZy{i7ds&<85kP6`T;&UGSf%UK8rW!tt(j3NK_`l%SO8zc+5c_}RL-QqZ zwBmayI~ZX}*mm}+b~USZ!JWh>OYx10AFTMJlzqI)Pi#BPS9WN%q@d03LjSWq&4#S_ z6s6}`9LbA~RQ#Tx_=wVafT5_zt2P|re&TbS@}Hu~hn|1i~Q3g626{CFPyHe1n{Jc!byH$OT zm8XsC#dyTH;07i>Z_9&mq_T5?vU8r|&ro`@UXjF7RnJwb+}5WgueSY-qm_K~i;^!^ zezgnrrJpz7H$VY(7LJF^_lI{rEbOVLT%b##O3a-u050tnB=u;+t1U{$ACtreb4qa9ishlmU8_AXNO_Z}-1oUa1VSrA~k1e9_)oNj$3f z@0C4Ue~<*+UVMh3T*^17IIOzS*s8{bTlLp$CI7YZlcLp<=x_V8YHzb@??Fobab+KG zlO(=S{CH)DCN;m5DgI97R}D%~Ro57%;!lB!Z+1QJQ0+2yNd+y+&UZq9?dk|i{&1z= z#8KESBnfo4_@t`#8Z8n(rTArPJXSm=iCwDT&4AR?s_fjLW)Rw;wt~4@-GP8@rTz<5J0}uw{%>c5^GmAF2yJfjwByP`xDV ze&jt7@{9RgpZmw zjFW^vq#I3YyhBWUit#V?G>0TElLz76J=`j;e_fAjOI z^4rD-B%Z45(5%X>Q2K4VJ*nhd7fSgSc`%Ms?W$1ivghx{YZ zC+ss{DxRS9Gmerks*%K_ihoG8w^8K-yr0d_bj4?>yiTbzj#lLsDL=VHiW?Uw`!sBk z#15rrnrc^8MDiyq`G-}%d$&pA1jQeSeq=oxUXTRD#AmwduZrCg&r-ZmSa|j9@;QZ7 zf#S+QVWCktZN{7#AWN!?`LlS|jCo~7;kfGxCzM@1qbg8VIc8RIRaIG)QCv|{STVh# zaCY&mS>+{hIIXg*On~ayWwT2vu9v^70+pq~3Im5(Wpe~oR#{nIX;8s+@+b9`DLL^w zuBoiDtgDPNt-QI#m2+myxmxI(Qy!R6B1&gnJl~MYsjVByuX%BL33sw$jOYRsFBx=$;oknni{Dk|YEg#lh6hhNX9D$D2ge1fjc z0rUrD)6q#~l}71|s)|{~*VAgKSfFx-8nso^gMrfWxft^^u7=lE8G))o4mMFy`gm|o z+;yDS2H`k`CB-GvT+3mx}vgdR(Y`;6qVCTvUC1?W?@BT z`PGORRl-I#SpwTkErx$n(+mU^*i#L+xVKH6F$W`|xOCbK+cFi!l~rYh__ug=8Lp8k zg?ph|vuUaT!j5e8w6Z|SbcU+(U?n1_2@7Y51; z5#GC96A#yRtqfL69|YZ$h>A!DF#gaWn<=EO(jWp+HxZqQiwGm=jhI^rvvsQ?gK~LA zU`F|zKkAbHPdLgAc-QC!MdMtLVLRI?GMC|O`!nYe%#+6B%mV@R1+u2l--4^CwZ$i_HC1nd$) zBkVZB5nF;Lm8khfUe3uvP0frp`rJfgvn%cJ71vq{+o-W8m1snK2q`XpQCT(88aWBd zixeRO$W$@Wh^SB*6rNKh2TyQLPp7)vpbDc^MJwhKh5GDG1FGIZhr{CRuP*bU*Sdhz zV@Dh{yLT0%hAJDfD9Y<5N9dIRAG zeJ-k8p)$*^+(?>H440_@5lRjO22($4eFd<|M^c%plZtVn9v}R_Rbi z%r4JVnQa$QS~jgXIEz`avlTc~hWS<0Y4+@5u}pvsjoC8;=zF7Tx}1=VX#pf)C7fMJ zAs3r5XQojys~nfko{6Q2YH4r|bOwKWp2p#)mUw8$>Z0VxrmzOrWf!tCdbnncy5J z`$vXy@#&*$eq&dv*2r|#o`mcQcfW{Q7|e>VE1rRb4(WN_Q{_%otG_Szi zB$h{kva6AA$PvN%^vJ_cX4^25xEODcj`j&_}{C|4aRP6oOmx_6hP zs4gfb9p%$Xi&4M$#10ogMng|hiPp z5uxMJS-F;o&gE0DfkRZm`!F4%8M1EixmcdYntD#@EM}|PHMpcyuDj>Wz_>%-$FE|p z;t-{F_Nr8fS%s*1oK0iBviufAK_u~{=nivXeq%uPAyavLD|9?vnytm z&BpYh3S&R9b9?N3o)kr>K1danDM);h19N)$(2ZKkyuH|S+kJf z71HmK9`$_7z%%N;_9=G6_{4k}#yd~p7$@H0p(-75+Su>_q22QOiKQ)^_OZe;0@LcW> zL3k>xj|;HDgz*!^Nn@8GB4>hSpQ>|ybr!|Y8#dBj$w`Xn1&zikfh{>0B> zlQNFFpXYsrE;*hf>duhu;_TnL964;{icj~t=;)7HZKg|mcdd54yW~_M@v`_sza|;? zC;tEc{-0PtnwA=tW0Riul|1+c51)ZMJ-v*bYU4f$zt^ktyyD)wd50_2*sr)jGiKmU zNs5s=Na~Q+C&#aER_}e<```DSD>u~J9Q>Vsmb~ZYAuPtQ<(Hlz`635z%Qrgs`;?w$ z2fs-1Z4Umn;i}wQx|eI~@jCd2m7b7;f5I-;!P|P;9Q-p%-UxLs*VgZK@V1^J2j67Z z$HBj7*T=!z^)znnUhXm_?{VM+k5SXTT-*L$2XD7Glz?w^@OFKg9lTv`n}fIO>8b5r zAA4MQ9em4q(#}N={yoKq9Q?O{1hc`+}2(H6?Q!x{A|Tfckq>pU+Ca(Qhcj}pRai1 z_U`3|6rbbZ7bt$ZgSX`y9sIpYKCQmH{wHny4&K&N;ox6Z@{JDOw*Mvv-(r^=?q05~ z-|OH%RPq%L{#(T_aqv48zsbROD&D=Id$|YO@xj4o-6j3X>)`ErRy+6%rDutQxBIuv z!4FgNW~6(4@)ck0;O%mo9Q+g|-{#GyZ9&l#%RA_u=f$%h>L1B!2O@Qo@DXms#PlzfweH`M&y?BJIv z`Bn$-Rq|~PevOjvaPT1|Z!GNI-uIQf+rb-ZUiUco%}PGY!51lcuY>uAz`++O`9=qSzLIZp@U3b-TIS$G zcgncb?Bt)4e5->mQqP%ea`5)J^Qh|bJW2F8D2XD(aI(U2C(d6K5`DO=y zuPV3I!Q1j}4*mfp-{IhGdE?>k_IX6fyB)kO?{V(DgI4Y`Kpr-CE%?D{1lasIrTIr;4Q5_A87R{Q0o!u(fMTwc#m2~IOXdT@Er;G zDYp+1wQ%ZbPQYhscF^nDkbvKpfG<*cyR+PN3HU6PS3BhwCg2Ut{(3#9C*WHX@Hr|U zc9z?ifUme~h_EL%y}I?IN9#xKUfQ1`%|1H6E&-pV*+-Xe)atMEX<9vX-mUq=#dc+I ztWLnUCE!iXPjo%?TEFOglO}(bR&IwTuk!_3d+le5dD*fAe5Tf3UA`d!Z{69|U)Y$8 ze>yZCTbJ?AK8?5EB@`#Me)rH>)F(~jx7#F+ZjEo#_;ihbN8>#jf277|YP?6|vot9_=hyUP2<%g_~NXk_5s+n zM{51+R_kR_r)Ye=#>X`Mrpm)9s`E=U`NK3l(=~aWU#H1$)AZMC@;Yy6@&h#emL{+B zX=)ux(L*%(G?m9X`AkjzRjodmn!L`Nn*6t#ys63S{B%v;(&VRW@;YCy$s1bz>os|u zU!uu>t?6H)$?N<&P2PUzg_o_<2p2#ZSCcQ&_#{n!y2j&GtoWxw<3EhE$X7MKzs83&UeD|5H6E|3#Xk)i zk5{hZpM@Ilj7AVO}31G_`@{5L*ozE_pd|6Y?fHU0sOFVOhMG=7T4ON+>pMH>HxCO=){ zkJI=Hjo+y8)f#`i#)mZi1dXrP_&;fUgT`lS{6dW%s_~5)f1<`O(fE@zzDeVs(fDN= zf3n6mYy2r1zfR*%)%aG8@2mO4CXGK$lW)`bERDA`K3n5EH2!^!->31XYrJt!m;axk z@o5^bKepx8_?I;KbdAr@+U3#s;hKD=#{Z=8SsMRmjnC2eGd13;@i`iAYWxU|FVOh2 zHGYc5+wTeDIc(2A^pz)^0uhjSgjlWRir)YeE#usV)MH)X{ zQ?K#QYw`^mzg6QGYWyV{->C7EHGYZ4zoqd_8b3wjmudW^8sDt(f7SSP8vm%qw`%-l z8ox>7FW2}sjlV+UEsg&{<2y9|N{!#A@t#j zU!?Jw8egpOSsH)0#^-4KRE_s)e5uBp8egXI1sZ>}=5JFpewrp9g&IFo;~O=8mc}p9_}LoYr1AG?{4$OIi^ex= z{2Yy6r}5<)->UHy8ox>7uhsZAjjz;rOXCM={n(-LCusaWjek?qWBhf9K#czZjZf3~ zc8zyyd{E=lHGZzfdo=zetv;C=|AZ!=rSa7opQG^)YP?tD|Eck&#$T`T1sXq31ovXdo}qb z8vlsKH);GnjbEnmw`zQ|#?ROIbsArz@vR!aU*k7ve67Z}Y5WR}w=}**<2y9IPUH7! z{FfSUsQqyI|7{wdrt!CHyj$Z3YIaW7_$|AshZVk@f5go_-h$MjLzfVz`w3zy!b^dX0BLUCqBn7 zC1#CdtpYzyoJ`y-@M7X##7zP(B2FQ06!;$ERN@AK7ZCR*4hcM;m~IxU5O^MO8gY@p zmBietixmib4RK#$ufS7@`w?ded>L_nVvoQRhzAh61s+E{kk}A-G_i}g<986ppGE8@ zZWH));sc3W1wN7ZAmV0$k0m~sxJlr{iD7HBU*Lm?2NO34+?V(e;*h|}#D@}B2)zF| z;B?|5fp-%hMqD89cH+Z{y#j9|K7u$);4Q>Q5_<&RNPHBrTi^}EM-v+YuOvQ(xZ^jr z|21L{aht$PiH8uk3j8$jvBb>+FD5>YxJlqe#2Lhm0^dV?JaL1-3y4o34hcM;_)o+Y z0?#APBrX!Tl6WX_fxy=gpGfQ#cq;Kp#90DgMtm}{N8ky>rx3dZ9!GpCu_5qi;?syb zeii*soJHIw@ae?a#H|9KNPIeRv%tp^bBiR_B=F(H!-yLNK8ScYaf85piT_L-5;&Ro zOyUZG_a6(KLtG^AZeng##0mu7PJ9-zSKw{LXA@@$yoGoqu}9#I#G{Da0&gHbhu9E! zCGok$9lwbFC-xGz3A~gzm$+5nr-?@sHw(O&cnooqz>A3Uh#LjIhd7_OLEr_%=Mjel zo=-fMxI*B0#3pf(z?H;4;sSxMAs$EU6?iJ~`NUZQUq)HwfI9_!8of zz{$j1g2pNY-aiC*3UQIZyNS89j1>sHo%k|hufW@gFDK3tcnk3r#2$e+5?@K|7I*_O zmu@jb;FZLM#2r72{wFRXZWDMZaWQeLz)urTC2kgYF>wiTlfa9JONko=zK6JsxIy3r z#M6jF0?#MDnz%yXdBmK6VnqU163-wm5cnG6Ylyu9PbHp7oF(vO#IuM!0#6{GP3#tU z9Pu1tL*UWG<-{HPME?_45Vr|@I`Or{tpcA&%q2&xS>R)dtB9KfKAbo}+$iut#9R`@ z8U*f3d>wH};ACPh4Pq4n@Am*#6Bh}*o0zG3tU%!H#Pf)~0&gQ`Y97lHcndL8@|Z{9 zjl?$*y9M4r%oIFk2)vT`7UGV*qW_6Q#BBmECBBuoRp6(I=My&zyqLI#xJlqe#I?kY z0^dViN8BLr0^-|U#C%&7wRp1ke?;&m$_*mk<5H|^YIPty2 zjRGG;{8!=zf%_8QM;sD3nfQL<3W4_@1H6#9NZ{SX4-gj!yq$Ovu~*=2#19f@3A}~) zA!3if8;So$>=t+f@x#Q1z$=L#A@2A|^gnSUaht$PiT_UAD)7_9i;0^BUQGNbag)G{ zh#w zWyH@Cdjy_9{10Ncz~hLYBQ^vcP5eA@$G=7Y6E_jJ34A*7KZ#ofK9P7SakIe362Cy) zB=F(HFA_Hjd=T+V#0>)XC4QMWByckEE5sE7?>`!N8F7)oyNO>VE)aM-@oU6hfwvLA zPMjt17UDOEJpykeev{ZO@CM@L#D>5tiQgjb_)+vfaWiq7z)Oi&5Vs2aH1SH}W`P$I zuOe;|coFex;zoh*Aznk=An*d>wZtKT=M%ROR|q_hcpY((z?H=7i3QdO#2$es5Wh?87I+--d&Gvoqlw=q?$|B*pSYE{P2kgsKOk-u_(bB3#Ld9D zlXEZ0os>Iqk{SN#Vl(`UdF%ISNrq9|Y=$Rhq?uuVh9@tamvOQmztRI<*U;LG5UzC% zy(5D$#x?Zbj7D*MD1$!d8v0m9vp7DR!8*8xzL?P_j&Edi;AlqjGSbbu&-{@WGrTCr zj2I@=yIr*tl8N#Gd*P54Il~N(Ny9IjWM z-jQLlY0%qDmz#i)w^p;BY{VK@t()3WYW7Ama(KIAWC(5=fn(;(3lVFb9|w^lj}CR>Nr=KE&0j}JN)6YSkfCZNJiWjMg->X06GVZATQ7Eeo7w zM!hB~?TsA;Pj(%X$9A$6>9Hz7cv&&ow3d9@6N5yYFtR^0Z(U8#HN%@OzcTlV+$(dh z$}L!(dS)pblQ)#36hGB%mvSs86+_+vkg z9sY12W1lblu0LFwVPQL>+8;UH4BrShjvQi!e>B5~nvqK|QYNFvE^*`Nj*NHLt#{Q8 zL2cm4HC9s1z9iRe;|RyN{cxW2T;cOCj~Sd>pO-P%_41fOHEph%eW`&PZa$}GU++L_ z&AznYNE0qq*BKZVsY>&QWB%~&nzc!0$sTJN;vOvKgS)wEKZRPMv%Np}KWg@+1WtmT z{oyt_%+=RAE>|vO7$w&F(J|3nwzE=uG%Z1C3kIJ#voB+RJZmk zoUPfH9DLb~jGtmg&WK!$N|a`pCYo0JGQ`bDF1*PHldd;idGEpp$A>L5dSiy!*;?1k zB28Ca5Z28N-Iy_`m+SVs<7LFIqR~bF`lDrTGdeNDOIJ7z0j;h%I3{$%V5rZ_@CN#r z5%=2BgOmIbPi_t3L{jWH+27KP@Hh?zQT4$_>;?Fo-7e@%&wZ6A^Rt^@O~cU)f5Ac) z?Ck7}l?qAWUz4VoP~(9BoJ)Bye+c*TM}me~w=r;|85xHTADw2m7UMXR?G0~XlRa#5 zu!JE8W2)0tdlU3q%y(i{q9iou3^O`l`EbL??#xEl#d4RiZvOCgvn3^ig?QN%}DAE2M#gX>l^4RCOR<_9wCZ@Tjat+(=eQpYTEYFp$1v^A+pC+ zO@qbSUr=@tx?Io|J0F^-HP9mI9i5$`zhYN{Yv}fY+GEU?W~k`&N5`7xik-d8=&+xJ zJ`z26n^pZYoD#c-`VSbe}nNve&F>_MjvL&`U5fvwTB; zFvAC$;gx1DXfSbQ1%@8FDExuhGFmDa`m;aW*9=#>F9`elVu1S3gxCa#_z%T3?)JZN zyr{OV;yxp+{F`eC`nx%h+}SF&85<^rMp>9J;XL2uPP{lc)vOtn;|31&HzT8V9WunQ zwj&ESqep%Co5%1+k6(=g^t_2{{6oJoSNzh;tZ7d&M{KI{$aq%MoMeVm|AzCqor6|G zl=%hDSAMfL^=AA`x;*!aD_3D~sCZN}Dg>1V=I3t$heP}sRl|W-n$c0yaH_M7?YMH4 z?6z1xIGq;HVsB$iX_)a1UW>Sf$({pxA6lLcPlMYc1qd8%hTk*8yE-=t_gNhnXu?Co zUm$u~2mSybU|2aagHa`oF5`xk(pUYL`lWJT_@lsS)@3`W=1ZY>t575~Mk_GLmBQEw zthBIKcJqHx-mTwnf}6;E1(Thh(<>qe^A$62rfVoBVb@T9Mv-gi#Ec5p(1$WY5NDdw zAdb&wG>YSkDt&oFq%UT)R-`Sy@DF}W<7sAecs){RGyL;w9JL~4;r#B&?nI0wczkd~JqzqF4OkZC3TJ&d$+mQ;tK~o8kr)#$?P-XUzAYpEBk68TBd_ zA9>;3{_xK1W~+ZYf=XgYjMOp^&kBuh)JnufBE04?-DF2Szi&^|I7NW!65FjzCQ|g@7|wwJ|+Cc5?*hH zKa4xPFZ{ce4~Ik8$qlcu&caW)J%Y}d=#9rGjW5}eTeBK@Pp8!cU*&`uUgZz(VMMwG zBV=53jo?{5mf9?7R|C05|4gWh{pWN`rzVLh2QVTP1-Phq&)id)( zwB3A(ujJRfp%yi&bJtkL>RgmBfuA4YjSyt>=Tg*^K*S8e!G@`mc4^9i~b3l9GQo)mX`fl zdyb5|Tqn5m!rQ`M4Bg@n?;0N+xidFin-JZZH{%Gol*n-L4xQvMj=fCiZ(^C#LYgQ($h^4t+ZqALQgtFI- z_+;*Pdt2#}@%(<0Smf4xpCi(=Ur_V6Jov}@2%YwpiC@S&#L5Th2Sfc@FH+GKL;f6u zsT)DxdJ@9O=OP3C+-C^WODx2&oP_T#{|+6&)R1eQyCKznww;egvY6(2oGIxpYyVg5 zJgBuVtU<}vi@2B41#iSI)THTCfcb&gmsb>$oW=zznJ)nqrTdLysP4X^aAXiFWR zw9Z$PV*~~x6@we>a|gf5YG(Eqf|-omn*Ag`T)e*)Rm-TqY$FH&v((`|HK(q%WBxxO{dBiapFvz5Ve(sb(=4F;ge|ne>#6A zLh64ye`wr;kc)8jz11Q9N8<-SmkvGzQy@BZ41ac72mJMx{#!0hJG z^@EZEsk8_#NiusSn_-Nk(P^SiPWirOWL_H6zC%Lua9M9&=0%3wH~5noK1*E^x3@4H z;}Kk}86g3g+DaVWVpv0p@i*z^X|cR-Wt@=vUH<4POCXgy5q|b0elRM?z{-WabwBk* zPHF3fj)`EsGyD!)rCBnGt!~A7Wjh+~bLD-6QvM`{{opD{L70c^W>Ev{D3%``&i&%- zX5?y;?IYP@#cr4E59np7pG97TlY$q;VU?7Fg5J|aSPN9aRhtBdWPrF<%mP^^c9Cii zK>INnVDv=2p7G)DFoj_I%C~nb_TFSR65bmKvOlrxbk_%R*FTiww)a ztdGlObq}QmRyB612>)1r!eXallioHlY_*s|(ZR#P;^b%?ggJ4hUY^O3XU@~k%)uG! zu&=3|8$6hUx+b955bzCV^d;5~_RikukG{kTThq}i&MF4l>?*#@b#|A(MqjGYuAk5A zGuf-*Rk4+jgiQk%qQ1qCVeUye2?cGjtNj60qMv_8cf^iW^IrBwVZ-cBYa8k=O$lAu zJ96LT5Qxp8&gkGwsDsviIN0juLtX4MR<2t-(em$4MgEPUh!rAs!@@sWb1;hhk>gto z4vc&wH!?EAEcsM;C|ogcP^j8#1cxD!vp(92+TgTx(ig1tPSpDYP(3c^#Q2V+K1Hoq z)I@TuLq5kvkLVLzr%tu z)7p%2QP*63P0fy^(8!aH4-DJwI5N-7 z`$})|steb3dj4?XRjDg+k47kHe-lA}5bg+Z#D8l&jkbt1rTua6!{YosaXzZ&Cvfuc zCl@oUaVeK+9YN<1tG=5rHb)_d88;sX)8wHJW(oE&lugX=6gPWnb=u`uiZ~k`@B<=< zT7K;0rjxnit3lSu@RIhoy8KbrtOdrbg}WFA0wK!h)?vEUh_HsO%RoAIuDTw;M>l$dxULXExd6p3P&Ke=2>!`&)qNMN^O!nqy!ja%o7hp(A*T6Q*c4Xrz zt)c2%BX}@!v&CX3=77+M5?AdfP)!H*(?MOedjZiGm;aqXa&vCjdbJlAv9#yk)-|jr z;HnT9?-1aKT0WQpby6S~0?V0gW9xKSFR_ov)+4XZp+@Uy)Kq0$U!d83hF&kung{Z1 z>t=fSs1I1t@E6t!I0VPX6&disS=_KK<%aF7j0&#cvF-duMhKT-*+ok+Wr2M|*C)+# z)p>6K<$%Cd+9>Y@J1_x%=hf zc2WU;4Zi7D)G0dZIV`6&3oSna1zTZ`=*Zv=6p0M~N=o)?M5nL!g*RE(Z0YI~Y#h-m zTrU@l_D9StGqTLXPK`{*rbKn7KT_f0K8+honRM$OI677ZI0MGSq9row7U<3o@5{r| z3T~7Z9+Z(6-flew+xsJ@%)?1vI3KQNwZL+=8wf9qG`Ud@X2L+ak7;6fi`DC2-DN`6 zV~oIoXvgAT*pAdQ*p4s?{sPf!4;03>{Kj}6-oJLN*H}AtbduRJmK!|yJqEV9W%}|0 z^tE?SOH%L}{F@xS5&x#R>J~sG{6lv0idZl2p0z1In$HCKw)EkJW=%^9sN}!_(?|d& z*L<54=(V;FmKgI{V}91oPv@_-^AE!Th0dqR_>p-~bl!}$qo*6*moH?Yfr!e#yEN`h z7WJsDM*mN1kR{jbOkv@%QgYqmy}I+~55J9&jGeJ3eI$!Hzgwkqgj~+#sIIt81`VjgEOMd&y?Hfv)NwghR&u%Y8mf&+2{mNzJ>MNR><+r z$ZA(@rKn;cBTKlY*|Jh9X|Y~K=ho~)(H=;+?htbDxvanHH%27?re`f$TjMBZa56QS1Nxd7_i7B8LB1P-I@vv^rc$l|tD~38v z+C-PfzC40XPHcl5|7c$L@3c5u=0fN6*fOoY9Fn+bb^M|&A9uef_B}Mm=MRX^h>Je& zut#i#6y?02^ga|9p6}FqFN7P!eQD{P$WBwo??ap6l#ID-yyldFQu?BszIY^k5u3Jy zH$2Fx2#vK;pdEe~I0NM#i5ohFB7ckwL@$rn5GY2qx5R6*&ruVZ2deojI_g#k%Km=; zqwc1N3wqJMyo|I+8q~Kxs`8uldqqDZ#Gs#Ztc{41-8yz5=Lxiy4F#@QPE? zg8_2{jK|lyBwPni&hbZQ;bsQhIg7oF-5_rAL}p&Y-#3IsFcB_syXu}nKt#gw02AR7Y?B;} zQzKt5%%+u+u>?;3tHy)DEMd0nX+J)v*4XjA@fS>1=NzYji5F8 z*ixu}3g^a`?7<9}{by#0AMOoCCAE=Cc4cS=II#zofC z(9$)yZp4J42G`vWX@hGuRCFI)%HOb@@p3t1aye7L<;)Z=XNoX+Ojq*@CTTgoZh5!6 zlAg!c0|@sVUpbEPm4u3l@z3cXI_fp3m2RHtsGDgztf z+8Dv3qwbE&pXQYBd3?2s^*MJd?B#iGcz?I0xdoT;M{#%l)dBn!Ic3dG*r=s8qn_go zA3QoB=ELu>-0@+LRLjePe%&w3$U_;+ctPaZjAk7Dk;gLDiKBdAkPilK68MdbHXO0q zW#LFNq(AwivQR!{7vlNo;9mDI?STj7g+H~9d|PG=_p=sa?aOV_Wn4>PP5d1G#a7uX zVj!(CBT5(M!+z=>*;+618nx1Py*zf%$dtg~k-Y;4T2-7qx}WGbvUhN9WG)`tyBSOL zQL!B!*x^VlTh{Xy3`-Of112$Ff7@MhX=(`ojbv05kfccp!p_z-G8ndzL-_L`TIqcZ>DH*7ZGK(T5{0uIXN> z2^w)B`XI-8@_jL4>)sN$R`K!<*2B1ig|rkq2QU&IYT_-cz3ZTlfop9*HE1|pTiuxx zx8%Z`eUU?8DQpy|J9Q@7wFCuNKch70LgdA5V(S+Ue$-9W1x`beu3KagL*77RXNlxk z+J?8RQ13$cNvt1j-nD-VRgDymz9Eizu9t&@^CGj|e0C{JmqT;HlW-5RU{L*}L2xJR z53@PAt{&Ir)k8cCaYir~T_JvRu~)1UI}Xb3(kieAS_0e9DqprhT7>gJwkT~gvQ#=t zn%3Z*R$tX%mIm?Q!?7h$tB=9h{axkcPR0oO_njO;>Oo-cJ?3x%JdK;}xJmn-c=i`N zT&Ee5CB-!a+jHpSMglsl7h2cQ+!cf#)mSD>)Y2}MGByHolgO82Go ztB}Scho@n@han|L#J|y%*j!u_^B``?JG>kq%%G7Wr|}_PGyJI;PWB;XLJy7x#fLV+ zquo{x>Yp1q2ha80`6~*@7Z3fU;Fn>KxBFB;QJSeOdk->K?oSDY_#qYIhuiuNaT)D) zkBgj)`_MaFwezV<s^1v9eq-F$LWcRsc=+V-K2ca971|g# zLevr&&oun^JHT@jiu`n;9dTxtbqalo-dndLSj!Gd!`)P0O-q_}Fnc;LBP9uDNXZ@1 z+jZB0oNq-RBJH8lCMxX@rPjBrd1~VYM+MMbNj&Lqn8*O z2oW5YVPR%BB0x7$a~Z^VVZnd5kW(?{RE|)%N2a8lzv=ra}AK zZu^gu&<)ktGUWs=Ha=i?v@e~_>2HR=L@rMcGpsQvnjMjjZtaf=`%FIRd>9mCh#v&TN2NQU|r3u81rgz~e?XPL~UvN71jfb~EK9qZFa3gV_ zHE@C%X%bNnyUGVaY(K8rVao_;B@{;FQpdRQi+&$ci+0}ZB^%6K@@F^GmMj%ltRzQVfCeoETuu;ux z5QtEbx;8P|sZ2fla<7~tMQc)LeeE&Sti5?nXXnVI;BD=Vuw(cAZOj1uAc*W#-Y$?` z8$4W}9bq_odp8daK|gR;+q&KEhu4~AKYXg+aZnj%umHj5QrHzc(Cnskl|mkb6mp=I zCGr59?HxD+omY-tYigjun8@7>-LZ05JZ^7VI|pWC{@2$2OoQl8SMB+bL7!gtmEEVv z5%^Fo?6rVKXV3kKGTP3uxMwWqmv7O3iStA3QdxOoVxYCr4+v2?)=FH{ejKWs6tC|4 zZ^<6(68cR!1{l6xf@|URIo1>C687Q4Vq|h3OZ8!s>_gNZty!|ogK+$%>c*9@1=!kPf(81Jsy$9Nu_ac;v^iwO%$rsL21(u0=t(w#UEzyv%{ z@CWgYg@K(Lbq93>uTc~Xi+#Th2&`BC7F54Q7H1X}V0Y>kEJ4~C6uqGyQ^%E zi5%soB-+{!FC_W#JkUa6fYiT#(ajSgXXi#QNg5yi8L!2N=lgeBTXEwKPv%EbFOj0b zY&?)89Y;POE)t+L=03RXI}0NNt;cO)>=TPri1w&!ew}?Ec(1dyeZw@|4~h<+gml@y zO@e7$ELRYzIJU&?pOe{T9y2U9!~4O2oHkE}>XR9h5tTUdko}2UiI%k<0}YvhV;)B4 zD(2yC2?ojT`;)BC`dc0KF->GI!h1x0#H~@hLx(ZOrtn9)>|fT9PPpP^ZVvs=w723_+1?3`_E!C&y)R?u z811!=MuqtZK?B%-lsec6^Y%5fAsx&Jf`2n{V{9=A3tylpWXOGY%t)#c3%lr-_evc`=G$r3pRJ> zMeb?ER%1S%yT$JX9pX3MspNZ#%RMOnEjqY$DqLUAa{ZVa;E6px1l0e7yvVKca->0g z?{Zvp;&6k5M`S8^IvL5*E~^dx$E)d4*z`l-bJcNzjV$BEb)CV1kp`B`mtymvg^Rm% zYXcmG4x4UOuyj0L!a;gns`bG#ZZU{e*nl@hx;Q@kgLuS2NG5IMN9m$+v4Je7L94T??k!v+jT($!aD=EXAIOtkVzz|hRlN)H zBK&sMm(Pp;lsnNE{&mvDe(actx$TtaKJ|zur2U5k%Ph%9ZFQ%MycX~GrETnYSiL{` zUAz}yEnSYBd42XqWC6>$&moca$UVB%9>Cgfp1cO9UR<=kPD2eU1J21YVD&EHs!3bTKW{pShBh+;Tq|}2y z@fae((>GZ8rYSaAO>U+wg6rf(#=x=rVNdGDc?hN=NmAQjyK>Pd)?cYNfcv}z2MJM z{6O+4l1C;mJWJ=l$K(P%1>jFmJoh)Oj|49&c(~$)la-kLtz}*3($u*Fa1Gvjbz2Yd zT+M2j!eVaH(DP2&sT_N-!n=hIQP_s0b$8^Dv+I!o6W^ya&dY&TV|;j;7w7Pt`m_RYCON$OBc?Gip(RWZkq=_0<`=q-895aX!)D&+6pQI8*4D%$3I ztyiD~3f$wOgMOlB+~fH7KiM~YXAR>p{^uvAHQE&`w7ia@f^9NJPCePL$|*b!+qMw)oDK5 zCn&&cSGQyufy?pwBA;WsAbRpQG!5)w_8NoXk6R3QOAZ|wI8HndhxIj@gO@qCTFa0) zaf2hx2x8?ZGi|@vR>2pT-}1xA)!3F(AIAV<7pNF2-$( zSHuy_B+4JW%1^vrY6M>`kD(Bc_+YlHm%sS4q3h>D`zI`*CRQ%n|Nb1dn>*W%7NM8+HkQit*4sMTxRsWT(yrwH74;)j$<==1H#nIDfqiL z_$v?nYqsI}dW~(VAJgAe_Xd9O!CzM$7Qy_5CyrUw@CV|F<2@_+z;STI zo|VbLBcUPay8KF!eu>9b^RU+IW3+#dJihCB;cZEQ&)O|}y(6C&=EK5J5qPuxQ+xje z&kZ4fa5=E~naVNvqxx#R8fx#O$i^UvBbZsbEZ9Jq9B zY&b|h-`T-Cj+r(4kvZX6k-*@Z{mB8|8p_00I-^tw%|i^BjJad3hgnzT8mu8M^ZFwb za`*=w^7sgO9<$zXe`G?j@yzZ&pyQ`oM?Fi|5}WFF{sJA5!N0u=KMJ?t0=y}(5THv5 zHW88H$;-b$Q`O~mJ;YRVE)%fMKwoPVrV3$SNTqSV0P!^Zsl1b#`v$@w|8T=^@dr9N z{IIX@s^#;2xiy`A^1}OJfZR7z@m7KBwl$E?kB;3Jr*c?j@8Rzs;1Vgao_`qv7YQ}HS%$0wR-~@F zGV~?dGA^38Dy?Q)pPK!BT(^CR*5D;6Zc_v^F?O$g2R4r&+J3@3fq!8GG1z(?qt=hV zm~&xt*bn0)C*W@f1rLwij7w#Ijpsfb|H4of&ID$RAA$94aJvt07WrwqFRZiwA$D)@ z5)xvIcrSdHG`v+TLOqJ8e+s=AN&Vn$Q9cVlptCjJpR3_OZF z1N`#iv<8%joa+OVJ_F~7_CK9IM0c!6_J!Z}t@ujt@U&k3q%A(&Rg+$p!ZZG)FMN3K zQaU9(k<4aXrF;{fNJhJ=kI*N4Q(dVp>gJomBTL|sxqCN{i_ZEuZEQ4U9h-CAMR0?@ z8{h`vi!rZbzZ3foV#8BDAQU_d)5s!*4E&ujF|6qq7}j1`^*gI+i5>8_YW<(;!;)iM zN&k?Uw63!auTNRkkTatNN#YMGo8eXyZ%>;^D`68Pb9jHAzyCjFlUBxlgkOkxSkDjm zw?~j1@ZM)6Ie`1zTf}Wr1X5-I9!A30XBbt8b2anQk^}fVSR2GL7%@$3W7&UU3o#m0 za5;PpF}>^RRSn)(JR$^ICCFgn%RB~kT+kkE=-NM0>l0)I;*ZsI{ehIw&4UoYK86bi zhM1AWe`6um@B~6jw9<`m&V@Ul$k=NgfzI)V#W>|lD!Z&}D8)BRS6Ssx!)IFZ-Eeof z$R;)#Nym{HoWb4Po3O?)AU?Nl|~1I*CU(o`$fh|MLh||Ly9l`2_vnD()8`L+QFrG-k}zU z%zIt6KO(xxZO`v8#IXyxYz-qpe6vAB11vh!6E^CB9g|CdNa~A}?Yiw1xgpKE7RiCw znXnFp-(XSC-Gz!Eb{FLK&@QW!p(JuX?j8+)dS=(IgT?Yik{a>mz%;J<8oX0sZ##S) zfTUR8!#Q_67MJu2$#XTyfh?;Z%ttp~)g@^P$zPJYbnc=~TAGp%*pkuVUuitH6Q)?> zyJ}V-v^=P3S)teL;w~*q$*X!m%XF(xmzE--+}S~ooF?j&YOV69|e?7unnE& zL*VK5j1{)E`A1FY$-eMd`;o1&Zfh9AhP>`+aUK4qioEqquMv?2n;z=A^X%)+b6dY+ zxSBOLU}aPWSH=De_Cv-+`dhpu>JtlTG{@*#KbYb7O=P9k*WYtgV<@e)!hfd^AXCLM zD-B7q54VQa2l19OS4^(D4Y=NiS;aqMX6D>RS;yX@j@>-_KRu5&nU%z5hdG(C#nP(H8 znT|8@`sGgcA?ArcnJV_2xvECC%Z07$wnH#Dkv}ngU(U=a7dexj!snsYB7fg4bLUTd z(Ln=|oL4x~caeu9eaD2g$Di~ACH}|LcdQh+g!n`xOVeOv8vgufUW=W--~I?YDLP=m zwK#O8w|3Rh5S8A3fK|EgtYx3TZAAj6xAqEX@Fn=T`i|vo1g1EVXhcf3NlBLR_fVi1sy^mBx zp6XlP736TLzRnzX-!(!OVWwLT#7`~bDQ-VwY~hsGx;cL8neL}dt0aEPRfek= zRmD}_Om2$j$IoW5Chs7EQ{>jd(1l6{p7H8vXAH{a-sC(HJbJ*Vx1Hwq!I?_`A8cip%L^-xMZC zR;GMcOo6lDUH-`BSWb`pWqjl`xMlFD*!{4CviJDtO(qXf4+5e>h zDRvnqGqeX&J|2v(-3TPQB~>=>g4by$HV-dnBIb6s#?tXGCVe}fwa#Cp7MU?#+>kpN zkDGJ8!Oa+ZNQnI;KVF379kb6c067Lq)EIExHVVm>^7~EGa^FfFtlWN88t>l=f8Q_T z^{1GBT$o*|9rMqpkmB~yUPLc{($AFmAD@3d{Xguz3v^V~74ScK!AL?9AJnMWUKKT1 z9zh-^AexW_CNRM$52Kzj{EKIEy&OWEe9Kcu@F`Q1ETrm0f86JahoUB7O&BKZdP;|U zd4YbJ=~B$}7vTTcI2e9IDz40w}Q3G-UUmA`RfH_?^M_$a@%- zRC~^fTHh5_S#_=K(<9)M&gblL-rx7W+5Zpo{b<D|Igkc@NvZzn}r+}w=yOm>5CIl0V`WWOnf z9(`_*H5`3{f>~Gp0p`9tq%YNtW-ycklnoMr1C+8{B?l;F0mzM87OLa`*_Q`B)`(3y!gm=xeXxq-^j6WIEbGsP1EqC&?WDQ72XzC-ZCw3ND6Gmw>Ge}%UrccNT9iiq40}veAWxoC#W1TYD?>3{6 z7P7!Ple;uUzwr-xLyGN+nvqmn9eW#DJ8OHi5fOcd3Lkl?kQkejFm=2R@&Z zc6nZcpF=3PIr=ZFo8~l<*Gbgz#^F zm_zA1>1sJRV!}*UjI8mjj^(@%$hE`SFEq}6$%ZE2WfuScUOj*BD%7)rgwIgJAMa*jw=SZ7* zLen-y=?~1?Acd@lV%y+ez+Wm~_=@uju#zx>uq(7G4`yyH0>KFDgb$SgqBgy>(N!IR zY)oiMfst|({=f`CCG(ydQZthRc{ITFsgAm$oU|w_K*0QYB=0cwJaa> z5^*`0Lte?g%be_0JkI5t0z1Cu>FbKhOLwKck+59R!C#$XA=kam;W|t;*C#J{k_sbQ zx!taeUGD|Nl+6tFk~o_d8U*t2DrA#~SLx{T@G5;>9`zN-5c`oN>XQ>Cj+|FNRu}GA z{l}u=qX?3YmzC_Y^clL0?t$k+-j0EALAUc+VvjdPz8%o^;jVoIpC>duMmFXZ&`X;4 zJ{&afEo+VNe*UH!CuB2?bVk*{=2?^k>_uR!wf!R(^*wV$D2<^()hfHRaSn68GS$#r z#+65Y4)bBZO6TAog1PqZZd;e0D2=Z5w{8mt5{86?A87ac5X~Gf=)Rvv>QB|#KfziC zww{J1F6POv%gtfz=aCP7q}?eEB%ATN?Ubpd>2exnpw zs%>p_w6`*xPEmd$ql8zXy1c)VrXIFY?(j_~@7-qdt@b+9+pR9|HHK@9Lh(E-UnKF5 zU3iV4i>^Bqx?7ts3smnGz7N{_l8$oAzMKTMWB#g!O&qIRTO1C;Nc0n#DW z?bzzTg||Jc#XaB>#wsfF#A7{G##1llt*42sCZze&8RXPWd)5aql+_o$V0}%EbYCsQ zsQwyb-E{0|WO;N?{y>YV?T-gyI;;SO-B*)HW zA^pQ|-SE#va_qx|_05Se82SAKf(gI<9OtmtI&akN=kh->V55{lPLqFN{>`h9Ss$A! zheE6{wi9P56R7!^j0T8=$6dY^DjJ)ovIbnjy&R&mrDH)=^^}oig}U}Y?T8V_c9jPc zYH{q+4i?T<4A`R`21`EXKOkk(Mn%7b=D*XAJF-=e4X*DhRh-o1NI1in@^uXM#2&YWW`OoIqL{m!@tG`!}$^Ro-sJ714s=Q zGe$N_vHUR}n9b$==4ormqy4jo5}LkHAtQ?kkzLk(M2F&oFkWu!xrpX57g`<(O)a?A zH}JP%oXUE?dC@KEM1c1Noj&)5a-WV*jeae|z?1}Y=p2e|q(8t-%#Xdh)?<35ARn7! zz~**Y3eJ^6BA*(+40w9Og}1|5Qc(;@EaZtA0jLMRO@R4BWqh0zoN3MclRfKh1C=DEJU&Ws)K~FK&g*<pdKZN~PI$6Gk&s^JA6rdScKdWJCZ%qlpHcj2 zjX%Sv1jnD*sVZ`K{p~ckd=}yvdU^9kZt3qAzF@0_yZOvgOxvmEL96mll2Hy@7}_K{ z%fNDTyKK3Pp~RgVpo1(EcPyZk!SdPtLzyD&-hnBX{z11G_L}!g^JI}U7SDNxK$zJ| z@~{%j9cxErE@;NfR^ObjY89RE)$fw>NVP>U0E(FqB6xotm#+qQF#F4c@ z$;su-b0*v@`#pshyL8ad{h9Q;D_Hy#`N^b7}{2v zuX!~d8Qyz_LSjqg8 zWbRlkDRKwB%iq7Z>28D_EImIOvq&rTO;w*vu;&x@Ih`I065kbDSwMHXFA>f0lm$`H-;9%t8#RE z|E0b+uQRgJc%>U6K@F5a586GEuUq6@i$m@=qAr|yjx|voERpgWBlrVI>s7G z8q^%-rwx~NWt$*sm~W$-?~8pB(wWZX>>b1 z!)3GDU#V@DewE^(6Y_51n^i*^$lucAd@-NgXTUs4a>UE#Q_Z;| z?nW{EhkCH^cJ3Z^cMAu(BQ8moDgDn}1BoObtgB9PMKPE4oQxL9yO;N=7bLp;C!ylh zu+ROFr@x!yGp?vUtW{pXbewky^g1VcJ-rk8P92Ds=gTPJq7IA__&x6$$ks4?)!C-0 zy_3vWAs#cUQ@e`YN9rhZ*>s1zS$e(Zq$!|vyeswX#&4qEo89N{mIB3gG1Y^nZ#Y3?_IE;;KF)>!nt1|OHAGuud*k!);a}?Z zr8j+)&@76Q$j`&XqwlNDY?|Nt-FUA7zeVbiDhvIK&mJkJ6N1Cq6d+M}hcnz~OV_eF zQq2O%mMw3Dlee20SX5 z)ierjDJ8kS0eI!19UX8*WhZjNBGJ`YPeRXzW~}m*%{Tv2+}H1(Ux!wL9Y}EzP&Y2^ z4k2j5%`!(2Z7m6ANe)$Tg7VD!X?nuC{~+rAL`oflHG>BELx0^$E%>8OPn+VqNKF^U zBWxC5P}(}tcOl!I0(E`lCI@A|U}r-W6xnB9x`e!0-~JFoT4DxrI;U}qGb`=qOSqR8 zgJHXwA_(5yxa^X3oQ_vm|Iq#CL@D0n>%;qx?2ER}1K2~2q3)YW{Km99G-VEDGzx6HG>B;slD=}rEK_EC?V`|cC zhI3^!)|*Z5MR%Sc^D-Zz0HqTdSChWrcvsrix(|nK?6Oy?$Yj|?Z|QI2CCl5$vh|4X zvH8Xhl)$|b$kQbsRytBZsQim(8@Aib#hqs1xSInMS>#pcokKUGXp zA$vRcq`)qJM~+Q}R#_42aH#qm~k0IRFLi!KMD zH+^FJVI`j>Po_fA2dwmoA7Xuh{!l^o1$|(SgZHqKoyV9jAbP;33D@+E$wO>G0d7Lu zr1D3Re`DB2rOI1hNQ#z^Ug@uXNdNG0H`SE}_Mf`Go%>|J;Z@?`x~%J+&bm)9qB5Hq zxoa|(7m;=5SLDjxq+Qa7tRumQ$wb9foxc_SBkgYIaG>vYX}?`A>yyZPl;JRkjcRx@ z<&|%FG=5md_S*Y?VK_oh_Pe4Y8UrydUy_LOWHLv7{7po;zhAFQIKc#`#jR)Sf8d|w z+i-mulKwFXM*HRi%g-4^aQo+!G2Lv%o9pu!mA#E(Kf(^xF&2=J&Bpr1dVI`i_?y|$ z8+RM(^l)&Sr;HKFE?*xRwS6)UUzX_dy5NtzL^Druvu`aKsq@wl>Zx1^)e8@ExJu9` zS=)iPOQ53SPgJ0~pu^WD(fuR+1X?Ce>j3c!3#C97N~$XJ$Uf;o6xm>9u2v{MDZib? zxUN*<1?2`oDe|dGTZxSvETvF^tq4~+45~6287clM-Fh`BSiZe-@Rhx~pC%+WiUH2N zg&D7PheEbk(?nNJmXoK#!M;1zfa><8&lGLUJBTh66A4M>R-4HA6ou8oWV5BNOMkU? z{4@5-wdD+XY#hgoEStCUW%Kq-_^C|#g|x5dH7qY*eg)enB>JQ*OteZ9hHTO;oN)6i z6rbTUp{1E7kWR7j(}XKxzHm$Fk8fN%` z8>NZmp)o4I?BWF>u`~)Xh3Vztbye4^R9pE9r$C4F{PxDR+V4}2MPij}T#Y-y`vJ2c zlnm9eq&aVjwwiM*&AACN7qb+}B~h#h9Z!L;^?t5*%7v0H_ClKme3t90q_d)Ms1295 zf+&Z>^29bDL9iP6pBDpA5j{oLOy_`-)3{hJ&zdQ(vIrwaswp_K;LW6`)2T#G(HpLT zuQIwOG`@sokg`&6L0g22Sw+8zVg-@?aZqVj4d%@giLd$CsS|BN(-m{Tm##=5G!;h8GX5*nNEwK$UtX)SSf&=X|wk>kd61%F}n zfHy*d7ZRH_AH~xjQ5#!8T~DKT7e#^EF7~rUkyvgZR8PXFL)caGi8Wp<#slhOaDd=8 zwSTUd)|IwBVd*u5V_>>?g`tKLE#CZ!!P%L0?_u)yb|C%OcVnJWZLMn#B`vE3{&1Zn z->>10@6iHZs*c`G#>|>vx4^q9!EQ2UtOPpY19OuK>?TE02@I7Yl@QDpG+K&0nHEG* z6ra?EGIIa;u9hgWQ?~~KBv=RQFxEEyI#~SJgMy3*3=Bf7gH@D7jRx1r8&fZ<1w>wS zeDFoEos>Aghw6zq-%kJUcOG^6G+~Cw0o=#mA!4Otz<(u%=rfG?>n~Q_;u|pqS3pks zaH8_VL%L5se%Vo#eCnx}Zu2bf!)u7sQAx)1-k(wLVDCMZ@^ws;0PpkNuABV}O4hcH za$%vckk<%#t>cowks;Oi1vUeV9|vXWd;;`pvryFCrcDCk%LTZlgE-OaYdTSCSU z^gC2L_A^=Q!)SD@{maoX`MSxCA8v)LPoKyiDl2-s%ll8(e}^3+^JgJX!2D{iC3ZJU zllf2>P|X;iI7*w7#)90Vc<1=D#WMJH!p(1rtMTiIgr>jo2F@YtlI&>T$S3YI&@#q8 zDZu;hjD$Nki{7r4@{`rb*soOuer(NHyfI#!ZvTkwW($P&^{c_jnH)%0EqFzLQP&Jc zZ}s-e$L;0R@62d6z+vW%nLr6;C1{^_H-&I9cQ!|;derA}HdvOSS39UN%g(}8vJ5Rl znJhz#ke6lXjX-u>33V(zU;2X6Tue#2Zpm-xyhGO0GGJ@3oUCLh@mH|o^UDx>iLc-K z)$01$FWF9V8il`VxfGsZ!kSYww5hLy)pe}{ee+x_xtf{OfhFwclw=>J9Zm@us2$Pb z=&v2U#i8yWx8p?WkSGcHoY75E`ske9XN6dYG|Y{Zo@)z|m%y+Zk~ zrZsPyvUuFEk7S_%;>C2}&G;-|==EKgjNQ&@-bzEkGMQv*}m zH>Y6e|6C{~y%gg~nOOTqxqWBS1JeGr;HMp%2n)_H2|JZALK2n=H^>ohxrG3Z?mhoK zHUrOO<5eE6==sB4H-5qQ8adeikU~7{oNtI1L|t$)H?!tB3_6qih4epvreei!T>tsR zEoRw-KfIU2A%=j9s{q{oK`$ng6skl(Za?BC5Gm_umw%k$_Rey;y=P+Mf>&6l@{UV# zN6bxkHD=F5bEi_iafvwt-w_ml97fBDiLQvNUDni5)-v=uhALK~6pOzOWJj1iG2EUY zS8pA}n2Sw@l}d6cK)rgqF23_rJztkg%ee?{U*HXuW*L%2ePw&RVkqJ%faWx)}@|@myIAVP( zWypBW!M`Votvi1R^5^8fpp#W^qG_Z-l(tVS4|KbR9ai{3cc9G+u9&L{yae_ z>)Ic8mD*L!hyJ1+@&-=IrVx)Jaz3eeUzLO4PO6b5%a4c6hMY_^9)t#bx7!q-hN-Jdj zbQqr?hbM#cpK$){vTud%UlS>>kGgNiu&++BTW8m0V{W{4xLU^Q(~-|TRt_mIgrpIB zqK}n0EbAF+URx$fyr> z2P%dX8Vn~dB9I;Cg|ivTHi~_ctK>!QhLn$ul(K<>s7PmJ$29+eq*d43xxEJc)chN6 zZbZ4J#b7ul88J<chg?9)XOOqK+PTw3yxTKX8A{tT4dqgu ztex4Y6<96M^ie=`=o#c7z044baKgKo`){PkvKnkIfa)ZDRYyFdaQF2LwOWjMj;+8k z=6=Czygt$4InKr9IcA_($Jan84~Rl@m_7#`l4Wz#E4kj6%ovI3?|1*t zrCONN*_nf~`3k$aCG82f;I`y`U z#_T+-)7hM}>AFs~^Ut`O#Ut-G$OiSr}z5Du(OC=jX(ZCt^ODO*iWj$ABy__#F62v%S$`p8Ld1%f%!4C39BRj zs-5U9-dd;m?>XwDgiLj!I)Bm18hyRK-nVlZ{Tjm9FrP=vds677;_5FON zs_z|W8zKGSIg0qd)Hgt?x{4E26{Ua6f~WfOgnV{_Yr?!IvdYR0Sm`HmON;ZusFuu= z796L)m(h7QV!>>I0-YaO{rf2Oz0a<1##Fj`hW9HtQZ5LUuiWODCrjcqXuo$fBJe_X`|>sFt3+}nbCN!xONr zqg0hF68DdN`T~He?*#=E#U_Xz2iO-K3|H-cMEJm9_%N#ArH`7^WOVe3?oUl*(0mIXR$6!Ju-D9+B{B-0H#F4Gh<&tB&*@?}HRUMc4o2#YOlJHa#f2f-drlfiSPPq^HIF@cx^2rh3X6HJ{Zv#n5-?C2|o_W@|8N3 z1}1w)_M6KoG0Eg(t3z9m8(;oC>=1pxDeY)~w$@C8`~I_ille2rQSv`a9WZ2tf!oVv z_-F7ert>s%yU?$|wd_7e2BOZS%l8$gs=Xr=3q>lXe}h56WR*a+gRzIK2oFw=tGn0K z1}51MwuNdcM`JxVam-WmQ8r!p2eCnuo3>fp9Us)^cAJN3d7)&s!*eFG2=-)=DB=!b zUYvAEwbzSloYW#XqM4>FW0m?)-6qK-$3JyIgnP`)Ib9gSu@u`k! zUk!h>u{K2G>HNDIU-p~z(1&fRRy%#S@$Oai+nsv`Gnv#0Ya$6zH@t*v5pYa=JOA6?TrisAiS-L|DJ zWZIAY&AJTz4EoM<2AHDAN23~|X1}rM2vardyge^}UP^pl9i8e1huJIw!W+fWW>Nm- zj+-?R3C;ILIvi^lhzbO9@nm}h(3#MDmq6>BUPALMaTp2B=L<|vXpR)vkkBmWxesk7 zmAlnTWL@#0&C#vgGUk&3fU7V4Bb#9lH3Z2O6wj+7>=LV+)${HNo_CZHMXYbD6?{Tl z<(^emIjnF(t>FBuEA^L9o~?_O|9V+UJSSKGeklM=bOsd zW0iio;LC0Vv$u^KC%9Uk<+^k&4Z%~3c)QUbRTy<3p(9oh-%Mdr~ z3VR`H4UEDK(6WDxUHNu9RIhA`*5~W40GT)mb z>3(7rd(CuB2D#D4o^QzA%NFjEGeqvj>RY`_OxL#?$(nqlR|g$7A=4J#w#t}|qLS_$%c1i5;j z25IXl=2NRBXGW28v2xUNHEQSa!SaZCFm3x1KC^gDii-o@GEDq?*haBHL~OUUDLy4@ z)<9o!{&}~YWmM>XJ*CTkujFQ@PdeBE8gEEPKHQMJoX#7B$(a*IfEOyA1tD;x>=D|<>u}%+{mK)YNOZ#(W1c!H* zFkjhkwjYvW)YZzh)Gi-Z`&vcx;jYFPXmfo^zwRXrTFmA)dR}s=`zH3(zR-nS_qv^5 zHVOu}h8Df-8k3vuf-{y(ag7-#WvrdZ?#m9;>~8XBxb!bHjsIq_LSjB53tKYvWARea zNL`b<#f*k$J5UE;+-aD0*pYOd(+tq@AYI%1P}12viCrqI+rfX1)GdZbnZI`|3CfLW zvTpJTb`%a44RXkL^FG@kGLS*bh43KtiKHHYhW-9XxHVT|DYofx))|<`xSI?-IeGU1 zdSha&#ALw))m}aFo*eR?67s$)(VdL`|Tv;MA zZ=WWO+pp8335*#pl2+*K#M|52MpQxv*P$P5L?KW|3eCw%4H|nQBNS&AGrZ>0vKpkq zHYD<9vP~8}b8KW7g5iGG2pw*hd|3n+kC@&zFOts zA0Oe(%rpNW>@44%D0eGc@c9Ip_Em zHy}%Lx7aa!7n0Cjmg{B}C++zKLtV@s!FtzqW4lN%OY32F{j~76`L|qZ)RlBPtu=NV zB;?u)%=K@ZW1)FxW5{~4bsl3_pyMnWUgo{lu3NQr%wt$5O!ATH!?rPiQE?jdkF00P zx49!ceK@SbFd*v)nE4x19SL`C5)bcIyqYKv8HcPdblD5o{ds@20jaT4eokeqtt5-K zAGT^|Rn1Jl)S6yhrFMxjE?KIY>_C*yJqW#h$0>Rbw1_6hsAYt`@OefZoyar7Aw3!k z8dUlGW8XVhim4ae`-9&3N|(N(tQ+2~9I2Cy_^2lA4CQ|EZCo3X_J!}{#mBk*aVNXe zw$+SsrR`jB9JNnYb_lmW`eeDx*FW}omHZa8=3yK8$fRUyuCtmc`Gr=wX@oNg?dBcu zeCI}WpNiNpWPMyrJJvUgl6;Hd7OKro_gu9On6Pdf*RCZr9`5Do-+0*RIaxk;TO-7m z9+Gjjy2w=86Pj3G0Y9GxJ~G_xuOwwY;l1B{Z=%vtp0Uot37d6iF2R{Q2{qw*#VS_& z^7*n{uHDb|R~d3ANAp^$*C7WyZF`AqeGiu5G4ukZ1T|ilBv&FdKFw=N0x$0h#B6#3Fe7mBPVgzR_VDkUkehkeL{2ITn18$o=R zH=2uZWZzw)DTgYRifAW>ut+pJm>WnXSf^~}3r?phx;~#COu^i$5jq0I*L2A6oy-E~ zFt+hndK~w&q{tlu{*$)58b>>1P0=o}m*}!pCwt7$nq4{m%R{J8DJlCe52^YLd=x`; z#m^D1?u^jI&l#5w+lF0W*lS&68fUy%h#pa&K7#Jme}mUiJmkeY?8d4!2N!w|UQHlvv#Xcsnt=6mXFw$!fMV5e%sT zM#Srq*&$iTR8_W#vu;LF_=F7k%U8Abs`H1?ji%|pUG}Uq$#%%CC^AdgZ;oSng3u>4 zGa2g1vO%uDIGRkZLfd}3?v32E1<5pZ#C(+DPnn-~KAyuSoHWUXYYM+HKcOIM&p?(J z0>5Csha2s9L!Kf4>JjF4^DEJxWeYT_l0lA4BA@=R>|feAEWo!dUA_zhT0pK<#4_I% zDu`j1TP6~k?q-h3G{Jv;D#@bGxN_j+1;275fi|JbUGO-*-pN zG0ch7Q7%5h7L>u=jh4A4g%z9mCuKBz!{=h(qL#eHBs9G(^+m9xlNPgu#2gjVu%hru zOE#^MM6&rNqZIwlV36TFK&D}xKb`1v8;nRREHe&rGd-8f|KPE$G=S`Ji<(046V~1< zxo=}Pg1|6GOK4P4SdbWD6V0CzCKTY@QWAqV2e!TtX#sCiC}BycW~d!JX%mwZmOd-x z$DdXJqhpQ6g>#updPZy+e-hKxY*@ptqU=6A&t=t;Z~9RgLVb>`mgqn<_*KrGlACOLn{snk+&y z(^@nj&wN05NOyCR6dre&`)Lq&(E;y^IUH?~LtgKSil(uK@llA)<~yJWyUd_K{%Me1 z4PWp}E=Rnv=>*TnF?ex=uycw%4)`w|&WMTnH4r)1M;7wZ{=WDWSH!y#UeIGNB9scOy zjI+m4Kt|szeAL}6-&pz&>DWn|d&J?3Ii<`HTLaR}B{Ixb9ByJb)J(us_@Fe3FXqwN z;0CP->@oLXx!P||2Up-?>F@NORMVs0mMN)O>>3`Y#ptetW~Iqtgrs~SLv@z->s;?a zs~*;l=1B&XO#5rXvTvlyX`dwATtyUUf2&l@9dSrz*1OGE*b8aJrXL?-bD#Hlf3!&x zpZE}KxN{D5t> zPL9iL+7cb*FxO~aIRIvRW;D4YZZbvR1ZFF!w|1CUqu9vT%AOq{$Y^%v&qdXNAC(*b zR-&8Zvq58tXtx=s8srJ(o#EZQK2b&6Z>}KEEN@X#$7JbbG%4GB5*j}j$H2FmIny|a zRnY(7Y|oSdN(z)B(05|TC>fB!UOJc;I6*F#g*R@;U_5;KF4QldFGU+j6CH3e5CtA^`SkZ@f{FdnE8(DfC%C--FEh^hf)6`&fj4LhR(qWT{?_ij7dj||K?;6K8$vOQKmOhFngY$BdVb+!m;Db`!{V-X*9k$j1 za)jj8poC<7m?#-+jbO;(&N2hlM~U5p=Iexvh#^BpBDN=Okz7SUk*hfk0)3| z2$FhMmEF7!hZOc^=2)Rs*Q6@T;DK?SpXi<8>NtKpTNEW~)9^^PUHxO`S@HIoqAw3R@PeB&@` zN(x`xb46TbWO=Vok|V`aQal%7299C;osxnQ$V{(~%qq@sMOfkyUtb))$IHIBSXbKX z^Lx1>kdepBXq2{PK|39hTpju>jOZ2grVTJc2H#t1s#J|PwpV|f99fV<5uw8eTX&5Y z(k!i{W8RODuzIdIrRUBQu|dyOZqU+c4|AE26;E+U+!UbdwB~EDgfR2JrH}f@7M*5i zy)E6&I*}mr7o-rJPRsYb-q)uUV^-zSRRhDLTq3F?J}(F*tf#QLsGy|QQ&U`5J=&OCSW!_`WRw<`SCkYRo+?AR3q2*q*>%RO zg`Sey(cR*e)|8ZtHVQqSlDX9$!50ssvaX`SsIDrn^pw;%RQfd~DxuMae)$eLsCZU+ zzE`>I@`b`ls~X3wOzFMgA8VT2g9_t6p8bXYjrErvclRgTZu!kAo9FhvY{thIENh2RD>R3E>&ujPJS^R6W?*|W_-|}|+!o>WM5463raKBM7?x!7Z?3p%i`r9pU zy>{0N8J9ltbN9OAX6_hy{+}29aq*$O=V$jG-1dR{$uaNTcKUOze{a6<;o`)nP9NGb z;+#*7wbwq~HvCf8yr(~l>KMKC-HZ)QcfK|Dxf^!;{nSzJE6(WL`;WP+7hN;`(kFKu zJazEpue3M6-ni`ayte79Do#Azu|Ij^nmNTNM+Mh?G3)DLh3{?oa9hbOzrA$dA1|2^ zaZ>c=n})ts(W~h8gU$IxV-x=FR?1TF2hIU)>vZ;V%|7^q#OIdQzPG=IXDT`@XjB^jkaTJlW?#W3czv z|6TLs$UmlximzDpr*Y>xhArH=?U}cKb!qCJ^e>)$B)0J4yZ%+N>-;qbuR5-7#_sHk zHqTi6O5dIDG~GGvgDb|q`tS)4Ue@|Z%3J4ru;$9sM?2oVbl$3~uWh*W&8>gkHubZg z6mEa+>My1=J$dknGrp;~;MB7=KR&%|M#|i~?_B@NYkkuWCOr7(wyd!iHU7FSGWiYT z#uJ?_F$I~4ON;;XR@{~^GM0Zmpd-90^Mb8%Nx_ z=D3wFrrh#q(yI9Pra1h6f2QxXOAg%Gku`YiTxaQn9}mB)qWHFV4u0_ah#5uuCT`yI zkAs)ane*H>GjCR5MgM_c>_2x`N=fq_YxZrJ?3ndw%&PaceROB9iii3(Uw-|A5vQe$ zy~)^g(0S7>6~3o0-<W{=dQ8$^u4Or+|92(aL$a& z?)~!M&ezvgOf#?TD9=1&-oNX=daH88QyCv6jBlC!Xy3%WE4R1J{QEDBf4x^bI65o! z$(xtFe|psL$C?|Q1@1n-KWT^ewrz|0{^O>-x9+=eUQFJCj-_*cpAmWPdvD#?KQS?O z!&NOU%}b5AZGUT9{^{9ImR(*sxU=H%<|^ax=^vhU+>X+x$GP9X>E6ACFYj3N&(Gt( zc=wv@!mCSGZF_6r>z7`6!kMeKuPt{>@jbET&Gtab)UQq+`}h?z9!!~aW8Wu+{N>II z`i+_K=clSSU$pGeiuD`!9ZdT&dv09?0<74Pk(X)I)>T(m)p#7)m4&mAaE`3k2(Y3?hxIshU$#=@@WX$^3FjakMe~{M7T)Qj7r(+siSru+lg`)i^(8*eGMz z@X;eijXr;rV_aQ%MX`}Z@g3D=)l!gxaXFI-h9@W!>C@+LGB0eg(DV*OfR5=QB1G%q^Mgs46Y3E%7*JF<{Ios3@=X)RNvT$D&$C zT`h8-*cFc23Vw?!u9H!KV&zc1l$4R@j~q00iW(S-3X9503d$=>t1M?lNnuTa@>K5H zxrJSUwVtXP@@KdzE%6kUskh>iiV}z;o`uE53YJ$E=^zUh71kLJ{F<3O*-3wk<5L)uOf;lA%Ly$`}^Qn}AwW{l? zR$W0?z3S|W)scd#k%a0OUAMY|uFBPQtDWVtYtt1Ltd7DCt>6&5bi<^kLu$)Z`_C<@ zqB{j=FTmw zE|78*%$1%aURKr+v)sb1D4a`OOG_$iRm%*iDJ>e7I_d%ysNV~!Yf4JX>m7B~Qi+0Y zL6wDbOH>D$mgl}CBdZ|qvP&jhmOVvro}E?!3d_81vFwtPuu8JJ(4%;5ND=HOTvJe2 zNzqjt7`=e_b?8q8o^o^n zF=o@#QN9dM8DgcX#-NOa6{4SAS5i|oL`Cctuc)xHvP#9Qnol%3>Dgy%1xi&(xPV4C zDytS4RJzv49XbJFS_NtRj86kLSd_3N8m^)gcXd!n%Q-d zb&yJh#(aczu_~uvs05~|-YK+#hJ@8r%~c3ezVNgq!g!K5staptwZa@;#07=5hG@qN zM;m9?5+S>$h6a(erSej^Z;7qr2nmmYX%tt{EJCfq>T0B@Q~*Y>Dp*`sB5u`$$!8ZE z^C?n&bx9GNXq;UTg?lcHO>fBx!CF|e(2x$r-?5ALt^A-; zQFWKm$>N1+m2=?MN~1*4yQH$Hs!rx3)VR3LDs0F|00q9S{*u0;e^?&UE;b#4ID+`S z`0aa?Q_@zg#b=O%d@2$~5l|H|giY-tsTf`OvrAwSLxybwCFNTqQNK$nD;B~eMpdc& zt2v+{IeJPe>G)N(wdJ!wsH*b_hMN8rm)FiQYO4#2q#G;xr~x~9pk}&Sa@Pz)PbjRZ zDI|WW_L455p2<~)G|80Q40>KEGtpqv8b)48O<7@etwKOXkfGAzjQQn-(nswv>|#ks zr*o_{LgdWFMrK(F#0g1BI)68@X4T6YrhBLuqAILfgG3Bdp&WRy5KV>@YdwXPMRs#4 zI$6Vb59vwd@Dw$ng#VNh5u`3-2_mHifu@E4B?r1u96@H64&0&02s&VOVR=oE_*N5G z|B^vX$!xTzk{WbDOTjIv8LX^_p@`r1enq+^_X>qgL~k_CMza;30w|`kV^7F5lmdv< z_aGq7J-hZ?Lu4qdI=g7F=$S?-)PNUjOBfA{Yjwmf{tntKYRd|xzoV>Hk&?u+Qil2J ziad2SCAC8gBiUvq)xO=MD1ljCTY0X>s6e)rP^PN7*<}|G9AY>!tszr%1Vct+YorjK z3Dq3No?R=v<0%t?Ke)EAw8YTkTMy+8DQI<7RfW~Sc~$f@53>rp@MuMJhLRc)-cl!N zZlRLW)yvA!)8;B&q)2oCW*e4fqF6QPDpXpB6@mg)poF7?{4FJ4Y5f&dBqeWi%Ng9P zSD2QJt#_^XIT%UmDrbWLKi6nINQ6p(*2$`Cs!&JJaYW;jJZOvp&%){whiVNqb#Pcc zsn~MVl63){nn7h&om9a=8SQqq`NW3%ZwdsiX=25Q-w1v`-;wG`yIa({Fd;0_qITw@{T~@3VvDqhVna= zUktwkw+8}`tssq+fxs7c1_FEey~1xjzX$oX@*8zmAh2&0dGULe-*5Td&hG|(W&Gsh zF2`Xd?u~Ua29s%THv2^-Ivl5+#5;D=G)^y z?&9YLFORtOyszeWfG~`Yz4t^oA{rtcu^Xct$2npgy&HNv`a1eJPIky1Fo$DkamoCl zq8>U#=~v=N8f>&tN^hww62;Ck3yuy66O3-&N;k${r(iHd(ibseC0JEmf>K&(1amwJ z-rPB2RuoJ+k)3x)Ix;u0(+b6q+(Pr4I|tEqL_$h#l7i|c7%@qWff4O!5VA#n1MNIT zZ!>Z{qK)eDj?uOZN#-dlF^bBnYN^f?S1$5MX||S{1#(&W>@r&i7gWlM=!cz++J#ag zWn5iA+V1kox_YAwAy-$6p3ZPuXfW?7tgAro$b>-zCG)Lu1}WIt3{jL28eo298m($T zp=?6g^(!t3h8A~)TJ(`wstnnq4Fmv!SO!7#0%>ce)6!{;v#T9Nbu~3~c890RQ&{0B zuBt5I+q=wzq?4;T7PE(PgND^iJFU?5*alFGf92<+`73zBAt77 z@wpI@;RN7N(x&8hLW}fRMpH?0yO=J zKp^qSKwvGu)TaUg=O5|oe+mSIM;e-jWC(OP3_U)nQMsZ*DYMl@lyY_@l9yRjaRsE8 zYMfnc#RwhCt7ct`GL3+?XHiAex};d;FG`F(Ez=Vj(Z!iXg{lKX9HHqImDN?ELdlHF zj+!DWfS%Ub=>~NPsVq$o&y`myjUSBA^t3O`#ONuam+;JBix*YRtrjve$YUyFvRKau zhF&Lzs^edMP+_qzi7+7ov4MpdxWlo3D%W7$dVi0Srwz%==|Jn6L zB_+k^3bRP3xR$;qx~)A+C@xUi5(Xr+Fj1MhSH(929F#vV#tV*Lm}``YEUPCVlo&~ zWK#&MsYQcYq!5f6IuW&Gr|Mn~WlH@HnBT6)k-eRR$f%)erdrB)D&qjg2hTz)H^%AH zqQ-m`!&YPp+Hz(fki@R8twRHWtqjk*&c~g|V&X_-7&^hK3j%?3!q))vfo;HQU{WeN z2e1X$224#01a<*;0Uf;^j>EttVE$<01KWUQz_KxcKnpPG!a!gRFd4W}!hxoQk41)} zo77)Ke!w!bDv6cLWS|R}4lD!a16zRAz%{@Y;8x&j zpb2aP9tLg)CbK$g0`q~WDvlOl60i-J0^F6&=i9`8Jmm)Fv*%>H_yZm6t4?OChXFL% zZITDv${vX_VA*8q2h3-;&S79OJEc?5yAA`(fMx9DZUt_=obm{~3SAP|b`8s?Y!xpn z01xPz2_A44a3e6e5Io?TS(G!L`j?O|uzogpz!u;d;40uo@#m290id%SI?>LDfitDO zuMGrN0Zrgq@du{U{z(<&3v8(*J)oS^1fo|T2DaTr z_{pSyH|Ybn0?UBO_do|=8E`Fd7jPHQ1RfCoUlC9G3os8@e=qq0*8tmq$*aKwrUIR( z0PlxCf%(8{U_Gz}xC*!$nDhYs5ts_x18jK^db7oU4KN*e7&sG{^iUvB4@?HO0#ku& zfG*%hU_Nk{gs&k!%lh}!g1SUUAJm4x|3vdl^ zHPHEI=me|>n&J<1805bJJ_FVR(}8P%Gl8z>h!1pbq@KX7z+J$$O~g9`yywXmSoR|H z1?~c_0oHGU&&0o-dYwu9t;hl38el%qwGDjW)|W^ZnEW#7odw=@$`4H1K{#+1unoB8 z4ay5_13CtPx0CV$w*pT#*tfxO}S#)HDrikEu2;NrkejCKg6`UL)N@Ov$%86BNc@i1qjl`Y-zGGZ` z;_}FG@kzHvWyc%-=14JT>i+4(S zXOK?Ps6b$(FjPs|9mqonfy_$e0z z0&{g50>uAW{9Eu>457nD0C(Wufd3WZM{)d-+3`usqFnLD(rAj=7&AUTWnp}BR=h#J zR&f=t$3@YXh&Ns0QHskT$;8+?RTjx_5aA}_XGyqz#6JsvM=JIi@tYK%IGGsYKLdX! z{tQ(rJ=@=de_{{*%kURnDjfd-{EZ&`pT$2Jf2tdNB;Oqx|E!>|MXwL>PX#~RKRTNB z#{Zl!{Qmft;V*3_^eCcUZeB;=-+=##q!+>;G`}BafvPtqE6A1jCcSpX60+9)kS3&;l(0=v^c)P%RQSe-i(RmhXq#H!(%koHBcS!jz z3Qh~qds`pAd@ZB=u`=wH@ z#%LkbKoSy`q=fXK?AXFk=cL%k`?|YT(j)laOuDPlO+OSLRo^VDKcz=53RZZ$B&RD6 zRVAM2{rQ>L4#VT6L@o*9X2#A8zSCq9Zv?vi0phhE8P5}f+by27a|Q8QvI2ow!Z%t@ zHALJRDMBdfFI3){87p`zz}r3nc^ZOOt## z*kXeAg0~0Vd%vdp^2kXd&lJCo>n_i-;;W-_dgj-R*y%m;u{9J3AGu}Da%mv&8{QpP z#;0pOzFBxl)t~UggugD~YCM@FNRcbT_;&?(YcFG$rr@de%$4?>7@smfNM4ubH_3N1 z_=!{4=qvcH_{7G@MK=4Y_S#E$I^l9TRd@a<4W^eB8<`z+P$g;S{*=3ADss1Hek}`S zN2@nG92XLAD)BZ>NB-(~{>TX;e=|h>W{Ld0G`=Asa!;T5WW0hRc#^8oNyJ}4{5|=B zfT$hzBmR%zzYBk+TER#9=Vtu3^-*81BaFgIG z`oa%F-@M4FK^kPmUSf9_K@)lmVotb=c>flU(0DT=e-Vr~KDHqEj!=+z#l&m9G7#8( zWV}0rxS8GKNqz4jUg}kW0L>SC#QzEWQ}Cx6!N<+pOZXrD8G2X`a=DAovt-rY?n2=U-T(7+zdlF$HS`kxSpnXGs{?^QY5hZw-^fWlev7b-CPFv<$yEF! z{IG-g)dhjTwLyM|$r_{F@kZ2WVrd1#!k2nR6Lam%K;UKGk1YR#sxPVb2QQzwb{F5M zbSK0ck+VYD(Pif#$*K02^ojSdcy#F_UG;jkM=I#7kyQzgp8l>tS7Ti+D?W zsrD3#q*1pXn$xPM@Xd#$S6>teoGIm)9G^Ianu&jO9Q=ttVlw!s_Q$^k|4YPoGSrW3 zjH1({6Nx5cJ+B)}SSRljL8_8kdf*$3=#Bv;aL4 zO^A1nkBxL$WX_V2qAUN+#BV5J{Y<>N#=ori^eNzHs`1_x+cl(3h@Bto+N%BtnghgV zs;Tsxd}`S`Ge-7C!Rzs`xxx{fS&ol$=k!I&}5h ziSg-Co}SaQ$}QCbo$Y3VA#ij9UBb8q9GzkUgCz75UeAi8S9z|Dfj70u0)5xVaNx3Qr z&nNsEssGjSdH%>{QA?v6VQ~*=q`Qm0n*#P zl<`6GprY53{|H{6#oxrgM*P*ds>U5Kw4O=7h@5JVtJy)j#6jYv+)RI$c>0lY$1(9v z#{aV*e;#$7Fd@D=^7j;*9KxoxRyVWe|D?Cei!IpB1SoX#;J*idOsT3q(=|Pp;cwzk z@q&-E*8}(;z`sF!l^sj#VJWC#R}+~>(tQa$*Dc-SXW_pW|2+KVGFqY2GSSDEiayTx zN|Wkg=gQc~_Ml^YY;YnBp3fiOGpsWDD;jTbn9q$(3c8!y29;e3p@uH8o7$;F| zQ?rkdLt2hYyX_^t1Eg0c{UBegEn*{f$8TrMkNPRnSC5Br2rAdztbu@rJ)Lo2~QKCAK{D5Wbf1QVG|O@Z&Q4 zoxkc{PU#O1;P1fy3c>A4FH(evwC`rZ(+Mx>9^P%eMAH8dycX~V3m&GYLgGvM(HO2* z>9r}elQk!o*}P?M*a6;Z@b*f2s=p|`NZH@i`UmsY$nCaX z1OkjC>BrHS`40vHzmRy{+Nlt_TB@bsjR0@^cfgwgo~%88TYAzDo50%$-t{uS3DL7R zM%)^;JThvCNGRLhDf00N@DqQ}8fiv@*Fimsn1l>Tl)xhyJ#G$JuxLVMqLuWCpzk{Oj5N;LQNP)fYJnh z>)JrzDc&tP1k>ns0aS7=SFzBRt9Ps)*0}O4@sj?i=jEzjGFU}U=o6m^iYqqq66;OP zQB?m2zYYA*@exuwqFMM{i@!_qCy8q4ZOvLN{v1H~9>Udlq~(L~-Ay6wBI&rntN&9V zP-Uf)A_6Eo78^6RibeQU5Wbc0>AL@Xt34}k4pcsJR*)t0w;uhY-u^cIP1^ZG@?Y}= z>pWsmh3%J%{pE;xiJE_25`Q2nYHm!ASt-tu^e&V=0?)A5y8FDm%if{JX{BFIj9(oU zl_`vKWR>(tMS?#*KT_aF3j9ccA1Ux71%9N!j}-Wk0zXpVM+*E%fgdUGBL#k>z;~iR zKtDEexZCzW7F>C;7=y5l90l>#QC4{9-mIgb_UQem;a2>Z9z&1q`9t%!ZR;y_`Nee6 z)ANgCgfAUwmH%k*drDLMtXK}mvGV`*x6;>6<;Yy5y~Xz-9sg+UcVtY<$4>cJ<$L%T z>Hn|gKUV&0kI_C)93%W__5WY#XQ$hy^FJDYZ_wePdlT%q-v(`d`(M+)GSg~lv6=Vu zY&}N#U)JH@xBuJxwNuAG8h;)u|DMV$es=oDs^787|G(ycEdD)O`ECCn${)6TJ68N- z(dSt8KNkJ$dW1u}eU2sn+s9j&p?fkz(|Y+B;cw`0w!-%KJa&xnO(F?z4$^WyuE$i4 z^i0+Gc0G^8IMPS*8Li{7HAy`Utrk35|NNo(Ow#!T*FbbWi8|kKkgxG=xf34VlUwRD zBdk6)9!t(WK65qx(d0o-kp#C!#|z!tAh?G7_3+&A1M|5_#|v+d;%K$52f2WosZpr>}1E; zFJbV9_Mdj0Pq;kD3`^g3{?Pioew6lc*$IcieH#D!w$H~$p<7;9`nEH0lyceOJwf4v z6ZLp+w@=D)3p2l`2>AU_x((3zgv+y;N6F`h%7YPM^|6z;KX+)m!{PUBpKP5^ct4d- zBlh(C5V}d3@^wCJiR<>9a+G|2X#bh3^9h#+#!>QVSDT)=TdP~CzTF}Gwn*m_9OrZt z^<{`2qI)&I#QlHivB!@MngMM0Htl9hzk2p*w=I_v=UD!B{hfoYn0%Q;ZBMYj;%U!M z)|_SG+xk|bW>k9|N{|f{JoY$ikK=Z`n9I*l;dV1_)#cIA95&zEd*NJ{EA*g$!)VKG zkFRoHf;{&8$jXm{HMR{Srt`!UP;hU+xkqTx;r_i1=Y!(J6ujz7`x91TZlI9|i)8kTA}U&EyuuGH`$ z4cBS7MZ=vM?$hv)hP~$M{53pB!%-TJ*KoRqr5euHaH)nXHGD|JbsBEbaHod*G(4nX zuS%W2hUaKFO2hFQPS>zh!}%I6)o`VT4{5kg!z~)_)Nr4MhcxV^dY!}Z6AjPNaFmAQ zHJq+tsfP14T&m$p4Ik2QorYU9+^OL{4G(G9t6JAz!*etorQvuDr)yZM;d~93YPeFv zhcsNL;T8>dYPe6sLmKv)r}NkF91TZlI9|i)8kTA}U&EyuuGH`$4cBS7MZ=vM?$hv) zhP~9G59qJqIU0`AaJ+`oH7wO|zJ^ORT&dwh8m`lDi-tQj+^69o4SUt<`fGTOhNCnb zuionY=;Z6zh!}%I6)o`VT z4{5kg!z~)_)Nr4MhcxU}r}NkF91TZlI9|i)8kTA}U&EyuuGH`$4cBS7MZ=vM?$hv) zhP~$N{53pB!%-TJ*KoRqr5euHaH)nXHGD|JbsBEbaHod*G(4nXuLU}P4bRbVl!oIq zoUUQ1hVwOCs^LlvAJTB0hFdh;so_2i4{6w|UgxjjIU0`AaJ+`oH7wQ8ipBoml|7GJ zcK8}z9(@w!(^8vk?;u>N*Drz|-|=O*)@Q#X`ccttIY$0>hlQ`cp}T&zCM?{s*b29+ z+ZGm{9v1#$Sa^dDxAnnS!onR(y668^SolmGZs)%@EId5@55vM&>-cv52agf{Ram$o z7gX`s@uNrBC9#@dwGOv^6T-sx=x|#P?H?ANexsGXt#1zw3wPXPh1>B*hlS77;dc3~ zQXT#OSo}LFEdA}RR$2|WYWTbEY0vao?*Ci-n-^BTaQ>Yc79P&O^TNWz`FBZJ_#RDP zyM6s(;WNkDAr>8e5f&cKpAUwGhx7m1u<&sHe>N;UJpC8L!o&Ih0^Z3RPsk^ad+Uby~592|EFO#{_0yS{KLr>4F7lI5$X8qS3&&8EsQNgELg2I8FkAIUJch< z{_2(*>$TgzmH*05?ewbuX$9YI4>jGet&=s8js^d*@e|*-5YFq)P{CLi@)qeR{L02p z-)994inCzrVhfrUIO5+b-&W*>MbBzVDv#$V`C0hsqiy^HpIP|ZY*Uft4E|gA?^oFP z8^5-K@{cl~xH*X5%fi3>hv+)~FxA4a& z^o(B*r)mC-I1ii%SG>dVbYF+VX4ES*14{JB=otARSkzeTcrnQr;}bodzf1VV$e+W} z%h~%?YSMcqDI`TZeu~E4TI5+y=X2z3yAmC}qI>To z_&8@&mgHADLZu&5LrX+u8sxP@^6VAioK3lVMfOfSUR=G(l`G~lY8CaDM9Sj-187n6 zPvG@ddF|u;LdEM<4QA}4{4IPZ7So>k@pTE`ckLyIVQ}ed8tk`#9@~ zl+Y_W`e(R$IipL!NQ}4x&*OzsAl%G*w7mb07gd5NN7M^cs?WueOCRTY{1YSoYDYO< z-tc}E(8nneqxyjzd&(wxeU&zkJypF`T62{#yOSw{G&V%B*`6OJz_s)5O+VYZe{(p>p2Y6LQ8uppnGP&pGwiJ>O!c9WR1u3D2 zmV_P%V5mkxh%_mdC{;Jyr%)JDk z|9SpD&y#cJo%!aQZ@&4aoH_H|vsUR#VTPeghCxDa*#@OIp>m;1mk8Xf^g0q>_JF`G zN`DP8LzjOjaJ$m{m6y;JhXn3c`hHkPXx$gc;w^hn>1EW<`eM|9_N*VE7#pSw+>5Wf zd=7<0-KuBV1dYh!_VuD=IEuDsJN07%ngDJ75<=lH61`s_j|<4M7`b)iRiIV>Klk+=~O# zzfk|IAx0XGmOw)d^667RoHZ;ZDSs?5Yq+UJR8J(k5oAH5`-N$B1|@VU(MW@mdM9NY z)n6zpbro7TtNvf$>?y0#)5?HGzilMdHxA^7`0doJp?tmdM_Z8 z@v9|b5F=<*S<{%7kpSyx*25Pd9LHy7oUBL2BM8N_5gs+-YsXu{x;=I>lCqzII=pSw z^$5GC;*#&JR-fP=TFJ((RRM^F{GtD#_jm`XLn3W7bT2gL9jrbSm=ii1cR0ivnCoQo zeVaF-zQHXgf=iXh`?^|2JC=-ZC=Ov{yBkio-ZzzLE*)Be(!Bpjm+}}Y>3vJB%q4Mt z=roG-ww+88p{rTQeoNdg#NT1{zN4rTPgycl!qk6R*c|Fc_^w(B8Q2p-Tgdu7i!ros zC;qp^dsS#O@%xh9TeeSy#<1)Ib}HEH34IJ>@qS?O4W7_qcCnAtbJB;mctRbhk&i9j z?g`DLoHQSW~&{@fD^vEqj;KI{nviNCV=h$pm*{12-)n5}HJH}n$Izg5>l_0X3$ z)RXo3PCYJok2mxh2aWI5zXk90h7M7lACxzb^!vP_-n5sWRE^;M$WQ#US|#{^H{@e` z{Hi9%*z>tJ#8;=@-=s0UWrw|?3t^((-);I4Z)gp%@^-LA{Hv7si{hXu?n_N7taf9@kvO#3%3{W58TG zLoYFjvvc0FND9A*OU5mbB^e&X`9+_Da>Ikk7FMk@F$RQ(96>tAhcKEZJk<1@gyVe< znhKSNkSnO;xSC=)XZ^8QDbafqn1;Fx5PJT|KvZ!KYe6f*9u?8M6vBnNaUBxW3nc@m zxj$ww%)f*hL%psQ+M96Al^(#UuLXSz-VY)r)b|CUe-+oL;-nd4w=x+F+iK22)rW=b z2V6}ZIfu5Sx(5mh4bMYtP#58vE8U5+X^X=D&;Z`ntlh|Z$uusVi4*c4GZ|6-0}Bg{ zK3C}1;2Kq&pj*(ksND*P8an=Vp}k9JIeoXFH7u_u+4#Q;*_*iLD*je5Cs{*ks7A5f zCVnSWKc}fU=Nqaz)ZQ$lykjWiNo|o7)aAGuMQ~h?ab%2jrV&arA62x(nUc=JSQVNw z4dhsi;hJ|BbaXg8ToJV5h#MXuV{@wl^rG;16)Q&kJa7(ARP*s**NVTr7M`U1h)3ZZ z#Q1NTl}{r^f}@%W>RCwlW>67R-$pQ~-^0~p<+81%A}*tiP5(`(ya-1XmvSvB9!C+O zCQ^96!MGTj(GdheU5RV1+6S4r7HXN{?-)%&v-nk%pq?yLoLm-=N-m<8pgs!`Vd?9w z!8new%13jXToSUv?n1%dS!k?Ka|aLvbyr-iC8RTOyo{a5i&%oq%E||| z%sp4L#IxsvBB=SiX>4;nbBKr%I%;}BO+NWiZ*TP&XbSK>DaYH#KI=IJr{Rn9-YPre zYA0_8^d9y0kX1pew>aVURJ$N|D?hk+d#Tk>SRp=B<>2RQH-TT_Ts*z^lkQQ74}Lk` z{?eRI_D)U*1EfsvcjzdQHf=CUW_*iaex&zAgte+bbf2x@&LQoQ$UsT@`8jG_yTiilmZv5M;KW(KRCNz89qRFKC%aMNF7Pbr0Ml9mfdD*S*kQ zkJW^>;QXhxz5$R!2CG3|!Ng0!iZGbo;f|})<24jK85F9{6RIy|eEzMvv z|B}wuKbT-$fUYVsfRnjV+(4r^ zM-N7uMXG5jRO64-PK`lPy@u^jYf!%C22NyQ}~WL|D16pd%!`cdC;(NjyM9xXUg8F^ZA;ZpCLq z2v{(4w&UF`3r9R4gs!EL-D_#{L!8^Z_gUJ6ll^z__5MwHz}6i>tM)#iZe|%vQC;tY z5{6BL=7G8QVOd(BjL?1L`lt+gC$tY?{nz| z>__bN&s&+Ip|KS4WxGVpF-85|7M1U0=QHy@bsq}JPk8;F(^)_sm&5^&SrTK+A;%0^ z_)yyG(JWqVVJMVI^<`S(XeblC-y5{4`Rvyr&n7bM&*f&8=W~Rqg0*FUVb5f&j=nm>;o z8@nuFO1=n99XtMfLHU{w5wQj%aY8@Q4rrXIX;LGb6C1x>C@b}FmN&tmDt!fPGB(km zT753lCK*(x8_{yH$dz%&0y`lWtS^?{*KMuDiE6h zIh1aeA;u)X$H#9 zBr=6CBr|YdhU|#f1L2^iQiA&5Xs)2nM2yLM2I*uP6afU%{9;^;rx+|kIBfDCmi)sI z5!9E-l^r^$uaWTW=!9)_e9|L8#m&o|_+*OJmKz`O6S(o=WOPM15g+$IQ0Sf5jgF6J zkkiZ=u$K4)gS0eVe4^PpiN+T@@y5RhT|NpUdv7I~p2#3dJh=;2U~Rc*icgcEFq?iN zH{65Thg-%{cF*_}M#Ul7RQsGk)=d#a;wb~9JrVLQ18Mve3C@Bp!o3!g!At}Xhku`j z4H|g@4=eku?t}s`m9Qs0fL#eo4|~%??m(Db*k=c@{Lu5Tp0M8zV2Kb1fN;R3CPOC> zW?0xM#K9u0Rk4g6mGbl+b`_f#yB{HbPAHtE{!~a<8{&tm!(p4ZF0>IA5{{~NlD8o= z4UH3y*>y-`!9*vVZAqtw`1|YOT-8xXn?g^++{1a6bZ)2*g~(TxlG+@qf#$;nHg#F3 zKXe=}lJyC@WawY4L9wuApOmpUoDk)3D^3REaEVRF1~W(FaH+*NC>f5!WtM&mFzhDW z&Zc9-`8*b1Zt%?FgQ**%wCibJIDsJPE68_=1=$BFpfGVD@x zgh_`xt7|2FpK`pnvbw$Jp`A0fAy^rn?*VTa#ls8UXf4(nKeM@;6FzONXkG(#cfzL| z6xAmr!Qc#o^7UdG$C+lGmC*gE>1HlJu)(4)gr$YgGFv{K^xaH5+n~;R1sXZL(4a2* z8rE`=L0z$2ARNX==)~pFj}DKb5ww*@=z4bG;w7h??Sw~55Z?}W!ebb0I}GpU!ejYV z-F7A0?Rbej105&aKn}&rmpI{Z(rdYo9Uf1$7B^!x8lJEf8PG-80fL$;@m|5EnV5xO zQ1cnc%O~t`(>6r7{Sn*{K11CLg@rz)&d*fu;n|2&=M1cw!p-V6fx2MMjG54adP!h@ z=yDp+*|z&6Lh_8YP+bY8+>_eV52=e}o5537Suk;-6JD&^WA;LaC>SysaEao|kR9Uh zw(^FO#I)OD_raB36Lx^Kx_#DfrF;qslT$%mCrqB;0K35*)J!Nx4T`BUPtWeFN z3&?qeh08*%X>%)umGps?Y{^v?ujSnGAk<)n+*c5a$;FP(*P>?Le^c9YsH}|E$Q%tX zE8~lk;@*fphvPnn$d}nwmK)@Y&p3MlBs55C?(>EJWKhV#Pv`=!_&52zh?dad3r--v z_t_8^veC(}FWc`TgM5znCAQwhT|k%dC@m}e!E4OJr9t>ZGjZe9P!`ZfCXd!!PKG}= zX;D3tX`h(1d_9;-`P7(fk^YGK{LE;#jlP27eQr>(<9&>5zL>$1ccHuFhkwe(U}H-D z`IwS_N!3^Ohki{xzIb=Dm;LrGIN+6vT*)D*sl}jX-QD{!0M>2(7!nuPLa`=tLC7l*x4u3c&DnH5DsdN zyonGPC9;dzvD~`-Z5c>=IYj%fd31a8WcCxz+L8xsmh_btKEdxd$ z2|JEPL47}Bygy^O%^5ZXJTrbmaBgl*DLBcxFt^qimZP6Q7s(xDkWWl7cQ999T)T5` zJ$Hy1y`#Dtw%Ew{Sxx=;!WkNqkG~6J(Y#|z9P^IQ;f!vXXXO-r+xN=7s zp0#>0x_ItrlUAqSXWAH()}S9|>0^y#6LkfKpxomPYSdGiHtr>nr3v+|LM6Rxz$`YL z%i4@)boE4R$uibg*7{3rw~C)V`!uXE4bo3sCzvJ|JK`k~5!83fmD5a#%*N=rh$+p_POwpRYpiCnOSm@0 zP!;DClwHb+iR~~5nX=2w@DXK}a<;z7(ms`idnnlzsVui)I?c9^R>+db?#M?lljXQn z7Ad7uD$8^XVA-AdBFH?{{s|ev9HdN@+3+zMP>jtLV=Kwa;1-vbx>y5py#GySVj8sY zFe1G3QLns_?;;q~AL5!T@6&%IEe+SG(;HHty2MjTDt;x^h|vk5m^=e?N5h!CJ6f8} zbbA&uUD`)74NPY`g_&gC&!+=Z$k9w0l_)8Zcg1a>5^Kp@ml}lmP9kqTC1X4AjYQrC zO2B8?Ea)q5BiV2On&j^U1zMXQ_5i`4|n>k6K zf@O}={sS1TryCizKg3q(#&=}xF991*H|oWyP^SW@36iOHHKVZ6xms?HXQ{3Sm*g2B z5Nbqscm!QSO`shj$#FWW4>9SfM%A#I&~YZMmK!tk0YBM{L<%q*|0Z)IGtVQ{aq49R z)nX~VX*Hu9=Wna!=3I{3HElw~nx2}3u@IRLLglS2Uv@5SVODqyFx9JQ3? zUJ8JMLa<)dECuaZjTrouuQB&Mpr~yJb_+qlX6orNyqn4U768M$nY`u82A$P!(73u% zRazEr*!vVvYyhhc8^Bs!NoL#eZ}MNs{9LAV={gHVsw=mF$h&d-N{H7P4W&+&Ezbj% z*OK#$cs#Rk*ot>DIW9tk;dnARJ_gu{d!%YQdqe=7I?Y-PwKuU>9S?{`8o*W?rB+*> zH`+TIsdG6h3+ho@LCCFQ!+I21(|jNtzY3vhM0Z!(L?A z4ZWumWbZ?E%K@CuY9rL4u2M~wh4%u);_oe_c5IK_(lS?3k7bxi4YRAr>_kA@GFNRw z4r!Ur>QNM_HmRuPbDzDh2letazfI)#ZkpdF@{6Nhmfxl{zmDp1^1Dv$v3#DgceKR) zU{@*PT}O6mQl<2@>&Q$^>;hG-XJ6eSR};uVcx(rQ}qJ85bc1e}_PEw=H=$ zOCAGr>A-cj+D&#>6P_DQ!T%CU#dcGau~{hlMsmNG#a%-<8;Vr-G4I2GDE;*SBK9io zM@Ma`g%=|Qe}_RaAEL9~ds%vCSbB&32&lb3A$=yH6t|bf-3{378Vd6wxqV8=-9Ysc zi{su(C)nD{1S<$;Eocdo6I(K$+{g zZP;t(*)ttgEvYf6(CMv;P$4x|$rD?XEoJFiO5!*pL2_va$LYgoSP=1j;irhJn$#6| zym?aFScju+0%a24*wFWWj+_&Px(J%;$0HqTl9DqW8yovyWlOoq-p@h_y{1oJ?x>Sw zUK)=)&VUC>(V?sy8TPKuQqa%eI22x}q*AMG${~A)e)2lvJGoqMfyVTTft*3rMT%3X zn%IXksJd8j3SEq%267HnYh^f5Pk zsp=*r5r?e|M~=!ein0jDn}nm91dVTE`P)n$=Mqu0lR7@ih2uYwsgG1(n^fR^93M1d zZ4)NB*kqzuw~AO*Kz&85+e8W;9jNYM+4i^hn*UPHopMC5=5m&A!j4lj2WIK@s!vd8 zxHBxzcReKf8AuM){qA&qHWTDOMvlYzJg?IQy2oGmOAz?D!f1zhp^P|H`RsISRF05t zxe?mNU+p)DZM7ptqgJ&WP+F^<7EriWcUaH>yNX}Po&hp`6fLK#0Zw-?@Hnf$K%29- zW1u9Sy-gkZ{_~|Q2$Q-Gg!b&MIbiEGXJdWZtK~qfe@Fr(Z}tSS*BprTngg+3b0F61 zkpr=Q9*Ff`F%)Ee9*Fg>V=$oCNj^Ccn;{2ceK9q|1G)Ipz?VJ{t9c;S*Gkb2xMbsj zSYN({QOyIfz5)yLH4nu43N1|NyU-tftu0LAm?&V8g_W8IVts8atkTz@1Yfa*wVDTF zeF+QeG!Ml3N-S*9JP_+EwXjilhv2?;Y71_jvL?*~vA*`|Hi2_B55)S?2Vyl3#QM?) zVl@xM`nssIrGm>e55)SqtCOi79t?bwV)an71fQpLH{zb^PVnY|Sj_{mzAE*oV7x=% zfmq)lwL<9e)_@0MeWTQ1!FX-J1F^n(i?=Av1F^o*7UT3F55)S$Sd8O?JP_*}tLh~` z&JXfHtncW7Sj_{mzVYfQDgPPeWR9kQ_Xp)btnX2(#N|O0-(yBQIP1w)dE6*RyF3u< zOCN|0=y}Kx(3`*{L=$1XvS?$BrF}^3>#5jmya|)@uM+)5D!LD%<>YI)%6~0SBf6JC zP5$fI3-CDR#$jXr8~O|Kxj&!^{=XQc-S?Pwqd_6}dZL>Qin@ z03w;uJ!M5M)$YHAf_usmE|u=zW>C^SL@u`*E|qwt?Z5glh#37h#n^rrb%=gXibwv* zU|R+sWuI~Ur7Yqx)+eI$lHHUy> zK6g7UF5U|SPJAK?P>!D`c>@KLS!4#{Gb$)wwyHo_HIqCwTHJ5P#8nt5bJQoOfZDjn z#(iY(m_~Bd-@2`QPHF~5e?<;0%gUw&3mDmr%t1qGre1EWHDzAy@MT2sbbC= zdZz_!3VnwKoHa7-tWnHaBbb^shMxvAcDkTwJ8Mi!nptCum3{MlV1&Q+DCz%W*6{zo zXN_THuz-ttf~Wt1d;~9MckvgrM{unK6A&SAi4dHMEwaF661xSXQ{Zxm<-8L(I`0Hz z-tjNOVkWR@D4A6!5Zorgr?6)i*uh{xe}gNgFrJ+8^G#JoH!tQ5Ge^N`V;|HgbJS6% z4Vk0hv~dtQQ*#uYHVia#6r45;G;Sko-#8>!D%B$pqZnNI&H`tb<}A?<|sIA{8cJw z<|sIA95YA3X=4^OS7zrZIBgVycY2P3(?&1&m6}Y+`k0o8XWy1gT}!_Oxrkk(BOkS#}^GQdzw|43l_oUB<39VIUXc}=hCKO zlSZUBQX-}2$4HfX?FyfrAKhc-NB5Zd(LH8can^HN%#ZG4tWQ8ch-*AJx+AuPLR~gv z=yEH|(K3~1TlMV3ND=CFrBRO?#+V!$xI<9Xy@zSlHwwykUx$iAwQLSf?3*xsga)yl zJeb()+5rtQOMrH65la|mN=Ujq2N4=&`b?!enEA&3L-JL*H=?OR4F?3(x;1F<(3I;? z9lYmr-^9oknrcv^TSCcBHfXM!q-6719Z%UZCo~icwgZUudHyiBXRD`>l*3*EecX?5 zh~fPydavVuS|hJG_~fd9{s<}l^H5&aDL)_>(7)oEt+E1^OAbSb&c-dE$;*FtDX3?V zS{4mqvuJn?E8KA5WD?N*C)`@bW^Sd0i^PB*#dH;J!|}sUPoQuwAt*%G2>0eu)}VL- zMHcZ~p!*fbB1_tU5zU&OK#`?2g0xFdpvW>7f*o9!oCzJ@vhpE8ReAITimZyF9K3|hqbE>g^%s&>=h71>vW6>I zcmlaul;ynZ1vR>#dVtPfAgIZuCs5>q=LOAm=?N6M@GC*hEN|aJO>r&jehTBb*;pE$xw%g9R=tJ*+QEgPoQY;=RuY?*NOJIKn&e+ z=?N6AS}#bu+^&rF9gN0iQ_>SC+RvbTm!3e;{$#+Wq$f~xfI&%@o2Dk zgQ{G50!3>$5~C?0X*D?b=?N6AWiTk7K+(ZhfQ9=n)IU1JNCP)DC?`79AfI~*su3NQ zl9Zl6(cz{RQJ0=T(Gg@pqoXHKw9cS}OHZKaNQ06tJ%OU5t`y2jm!3e;`W{lJDwm!> z(b4Th!?i9wfudvBfjnh{TzUdU$C|XksKqPD6VT-9zkp1}@0W;c8Nn0d(P>NziYHL? zVQ!4(aoZ^R$W8>&fNX?EjriK}m#}V+{f#*q+hBnkD=3DxUGA>M3K?XR^Q=>>wL#kD z9#yQUP}B;iS^XI@`%gsXSQ`oc6Kx-BD{WtcU{$Pq2U(s~j_CFblBFi9Fi5*!VPK0T zIfJwH<%nPhgYw;nOOe*GtK>_Freen#-6Y*aYov8DTq+?-J5c#+S$^k!2)44$P0t~m zk#cT|qH6LM6V6TDFnvH8ZJnF?NdrXXBp#fbcx=K`mZPn6lYzNTE9=};1V;H0*qAss z-2&B{jBluKq$1|+6P%lj1)3MIaBfPKVx60IVEE@N*khfWv~_MW#O<_oZelC*?2UD9 zGO$^9gA{OX+5s8F&CfbF8N5en>)d4UUZt&b6SLE~N&msQNq-FcZ{gget#i{iFiadb z^ym&~d^k55yxpU%bCbclJ=!`q8T_C}TjwT&_jt5*ZZdeU2L=p&Us!(okdFQj=O&ZB z-=nQ_lfef(+B!EG{JBS4=O%*>dvp*D4(BF=k9hPh@;|KpmxCLeo3wRqdIqYebCd4L z`h2Ip5WL5$t#eZ*ED6V~y!sI3`9XCNyw9t-4GQO``GWT&Kk?6Mr{Dv4x@LR)s#eNy z`neYeY=M81mZx)*w$4o^{fJkuK`(@J6B`@y`LuOz+AaLn`m}X!dQ|WRpSI3T7mK`b zZVG5xbwKZe0R;5J>;-ggij?k=2%g3)P)z(1|2YhpOK0dMN8;?{&Mp1$_qYUafl89` zL7XyOp3I65CR=u09$twL877@0-=%X?e5f(ggyW}kQ>45Pas~82Turf@hW}Wsl<22( zQ>4qOLVr50*^1M03tAEOsEB?#H$}Q_mJBya2F~1n%wU+)xhc}?8KHd<*8=GwoXuO% zx8P0ZrbyrKg#Kq-vlZ6?IqWtjgJD~ZW{p(0Mp8hR;A-l~WkE}-d!WF`@IgW~La3;1 zE)ZH2&Zrg*;HPs_WaJXb^rv(tt}*_Y$%sPdrpV~qg#J!kvlZ7IEofWRj?PVy<6jrr z{X)wXNef!TlFm($@jnR}zv*3|wxPkfZZM>Viq1`uiER-Z(B-%qDlTIT)nwG(tQh@c zz$9|gC?RbSQr4R5pB#>iIidq;=A(+1I8zc=gpnziNcLB7EfnXb_;5wg`d-Z2@ewjq zx2AJbe7u@bg7|sh9G|Ej!RA0~Iyc29smX|koE#MRZ<_TW8{iME5YX$9?x%B8WU2=f zd_ZelO;)Z%TPor**hyr1QmDG%nyt7xZAtMsiik9k!v75x!I2r0gm?? z8R*;;nRTvEtr03tE?1LEE~1x!z5x+o>FacEip;)KGTbW}SfjVn86@;Bq14por}6`u zJ=ag?rpTN_!r+Wf(}sW9c-#8pA-w;(H=n<#f{>)f;pr59W0rajOwWrtIFU5SB$$cAvpU68Ayev#Tz_^n^9FI|o*h-Pf)# zo#0bwkgDvqd@~}8q3jaB5IXM9XuIro37d`1P1zOPh~^@S&P~}JmkG*u>D-jv+2l*O zbZ*M7G$`rPxhcDcL6xxI-%(JGTx(O-kB3a8`sv)1-IGcS=#_{F-U7O+>;at2jp7Cx#X0U^v{`mF zErn{Nb5nMWK~a~^P1&^u<-2rl${u7;!liRl_F#jOE}fgQhZt0ejeEAu(Df*-m3407 zYs0qU+|70=6;EO*C&bn4Ct1bCaRX z7sp*VHyPT5)5>*XAhZL=ca6I zUQlYDeS&k-8mx|>0w*ZWO*v&ucIn)d)6N#}@<3}&c>^W}8Vvmva@s%NjwtFjVF8s> zVGDEV+?10vs0eB-0JAU)4~lbB?nss|@c)HLaGw8 zU{ICIQwq5g4XSnN+>|@XpgNb%O}Ufvq^1onottt`FeNm)bZ*LRtQR?(Tsk-9o>U~1 zb6q+&r?9DG5s1$XLO|Q#vD(jubBE>D(EtJ)N6mOCfhA zhd{_YOWfvj}WNhOq-z3SjMiEm)FA5Q3xb!zfEtz~R>Fi8=6fo%rnZG5I2$IZ**GM8M zL_#tH&tjY%@#u{Z&{Rr5XQG<~bkB6&Ye^^5pa>w4=9jar1%sOq4x9X~C4Vy_u-hkB zcIbeXmmB(ZNj-v+%&Umc$wVZtr}$Q$zauuP^CpF`YAV6;#=OZCKE@rNyc4+NgO@@G zC-TM}4+_~9$Q#cft({v2c@qrM(!hBW&7M&-Z?Tit_!XFQTnK2g_tOO`??eVs;>oLU zkI@AxZ<+*!+4P-=1^p!4g5m;|H-%AokZh`b8X@bZCPW(67b4`R3sl}I5}XAaiuZbs z3}zyDIR5)C*r`0H$CZ7+cXR+IANQn(Il4f_y(%>@(*-K-v%_4z<^lS+-wtyL@qvs7 zY-&=|1uC9lVJGpIjB9mff&x~`Gk)AvGz9(t%v5xWc$RtvdneeT(x-I+4BNbQnxk_( zsuoM$20aZUdOYm{)n=j-&$gsfHC>?Mx$0aYZPIjsil<$mG+m(L`D%@%Hft`@(uYXjQE}UuJC&L)Q1PQKP?|1K z@y?0^756e6KV6{Wy&pz92e%ojhCqvLfjzk zpz(7or$$W|sQ7YaHcFfH4ipBDXgRq&;=^K z%3`=cVR(ibOrI%6C`T@K)clgF1uC9$fy$!` zRQ%k|kduoK?gqxwE>Qj=O0c3U`H2fu{DSS|_dXlqLN+>wT)IHTFEYsI_~`-_zjzh% zhznHwgVF@@Z~+nj&`j@+JBqgQk;$W7x~1uFizLB%+gLN;IA$CAYbD*n@~bjd#-Q}QpV`pPcSucli?9Y0;5;=gr9u7IZ0 z0Zk48EpO~K>z*MlQ1PDXxRTTe-uu`Qyw9;Cc-65ZcwZTExYH^lc)w#u@cuT{j^G0< zv?KVy^a#!sW4ul%y z)yL^-jXJhk^~Y9g^s&_%b8NN79$T&BkF8e2vDF%vsumt?*n*Q4H$F1N1uA}`bZA-C z$4|1I+tGA^icgWzm}~2Rrr53M0u`UC4kL{1M+`8f;sVvGm>oMt7pPWkIbqAF)~a2X zc8L82{k2txQxGhH3skEvS22<6hB>{@`qi8=%Bbj71S&& zgMoAPSdemoa-TpI3I-W7^NH~n3_gh5>|}I-DzI;PqHZ_HRWS5Y%(}1&mo88R!v+gw z!hMctxIuW!L^Q&nN|!EB1tSg5D)(d5y~4LpbEyAv<8R!$TqFEljitJ(PFQDIH@B&=SsKzG)W4DwjwIF;SvDgGf%jC$#j(F!F zI-q}-D_K7Klv&Ez`X)>J zR2J^Ew5mvDp$k+i`v8V4iB=u?pk}fhm&zigbV_BR3skGld;^B3Dv0+AGK4uu`4cdt zoDO_^^S@ewEyWq1BPsZI1P>HGKD|Abk7qV7bPAv7FYV#DO-L@>W7=Q4OHuK{r%F2t zU-$YJDCsW8D!1^NZ#yxq*1ZxSSLb?&o-@)lxbG!^_EK@4vPO3bxx8S|RQIpw zLWM65GS=We2WbmmGHK0j4f;c&oRasHEpqR}BC+sQleWx#m}uW#$RN*PUlG1ZW0YsG zn;~)GTkoKj^3cUc+QPS))mw&_$xpy63g4+m%kZJg-JS&e7oSk%W%6~{|0sO7UWoBB z`8-zhJwuF_$$Qc93;%5(UM7Fd)DH~tUX?fd%sEcsN2Z{C%GC*^emM@6^Oo($dX4+C zg)`?v>%mC~CJJwU3}Lr3HL|r=%NDpL)dC%;$!(_AQFjQ)3vV|l$9)#9TDYBCOtg9b zd2G5_d&zwbf{E6>vY-I>W`tY!ZbT(Hk^UJXweFL$n|VyDGAJtAZ{62$2fr7|Z+1R- z1Yba~uIRK)<-<(TnU8f~Sy6dvDrz<;U)sIsEW3fu%dQt~3>lx@Tij zY~9VO$Q^?is-IlMsb1K=IdEX>~|Gb zi#jJnb>@LHg|vG)mDQ!aQEMLkIEpG+Uk>IsqST^pDH(iZ-owb3G%D|DP$!qKU5ok{ zgx$X7WZLmZ%5e^Y(V~7VTwY!m^-sycm)9x8IdB%p1DXQ2^A%N(K!);qkT~VV=m@Gj z2Gw~Se?C^&MT0vbHTVYXH@|4&C1m{%sJv+M(+H;2Tr~Y1LE0UMoJFVD(HtW+SwDdP z9J$bja#(+JIUmHPt?e-V9ykXy%^mXz$Y3HQmTZ>f1w{DYgfvAn$0Hbg3U$sen$5Az z)On6ERP(~V$Rh2=6UZ`;QgCSXKTWO8KM06Ml`A;}{|Ak1D4ah5;k<=2oWcbULD{IK zMOP}Dt-z5JZHg7QUA zh36VQC0y=76t3JXX-W49a#{75ph}lV!U|V^B&f=zi$~#_EHhd|zx8M|mTvS*ex}P< zZBa%;M4=4PL1a0GD2yaCr&3l!MDt2@xgP0sN^jCF{|V`&Kc|y8sG3%lI7`XcfIcHl z&+4@(iVw@F@@`BMy_Y7EP^wT=DxjZEQ&Af&Sc&ND=qY|*fYRTG1Mqp%kTv)XEWNSt zf@hJ|6Wk7%Wvis-e4%}+|8Ki3vX;E^G5?(gvQEw zIY%xBStGe1vy&rlBxLNgTHzufpjj!?maK)zDP7M}{hm%|y;`KVRB&4)878c5i{gQt z9HDGk^3XInYw}0(mXr(Al+;HH%A@saN&c5KIThA|9LSnjOL8tTjM&uSAIZfcT9Wg@ z&i`N7Lg7ut&`LmeL@t~bL|6{Qa=w>?iV?(exn+2B^_|02ylD(BmF3K0i;IY76@CK9 zvlW-gCKJ=ZZv!HN3o#^Awki1;VOgTLDNUVN;7X@W8EH8;@OijRJL-t@6Q76Ml&3ay z_$bk)y`k*n`1#{eZ7OaDM?byXi$_Mgvba243lvpw96~L6zyDUuJH?~qL?j*Fi!T`@ z8AeD3vR%LovKuVJwY<+g+PnO8crU(miDddyI@1#;x1;z9bVCyKB51XNPF z;&rC);2^UGyY;=1z#hm`D#aTp0oNgP)+^q~vSpn_CdGedN#gaW3^r2J;i0&q`#g^Mla-DztRDGDDxghKy>n5rOwV zxEd4aLIUAqSj;a4)u6sYRg3*?$+bA0Ydv#`d9*0t8qA%E zA=e4%79piA|Ba+FV;M1alWM3~;)mot>?O(Ynq(l$514@g$A6eDHk<_h7x3hf81bXf z{wB2X?s&Gxe-0nlQj@?^=*IsYY3s_6hWG!tVn3R6LTrTSit#xya+pxm<65SqvUCPd zjG8LZGjJ`CUAwap5=A=xi^#eD6F&-hfGsk5t zHL^XhYw7l72`5tJ*v5CiCeM@h2LGT#(xm&dtzQY$g_KY|+E3vo}JoDX_F^G#yD zY1EmgtjYbIX! z)U!P0@ufmyo=L@19!H(TX;hPawZ^XPUk&XgPN!o1w_}1WIqnDug0~^qP%>mADkSgk zN`}?-qCAc}9LXhhei=!$+lOc*heGP~1e8!x{}3`!rx&BCOGbYzC?T3E8S4s{q-d(- z_&hJBzwGj+k1>R1DG7R!P4SNs!nC1>|XexGXVDGRxE7$r&;nq#C=(ne?|^VD2M-j48*1Fs2Bf#Azo?6&d3;KFj;!sN`&*cgHzhcPDXh= zZd2OX>@I7ZLN4t>Bc;jwhNW3)*K;uU(`2@y$Cp;N0t0kI$A2r7SK9Lu@@6nm+Us5N z=2@Q7-sU0Q$y>AtsE_6CehJ-_R+(8mMj^=#wf6XjUfpIJCOGbX;Z?+K--prQ;7vZ*mO#2`Tn}X4*u9YTbdX<|Kpa z+$t(@vOx{*5{h+#L2x6X3K|V+auq}`J<$j<*Ij@{C_Sk`nxffVMan6r;>+?*o#~WL zWld$U@IL`DOHZBw9K0IJYA9{GixP7wPl` zI_n`p`C@LRvyHhW++0%5F)}9IZ(#eSry5jgnpeg&jt$*e(|IPX*3CheES+yg`#LC; zN(^WU9nc?g6u5>0EyzK%|5Y}~8UGN9f8lB-p6?OLkqevH%!vX0gix`sMDoiDEBnGK zW;$i94<5e?&fpht0wyT3)t*OxP*P?ef#G zDYwf{997{9pW5=akAqN#-15X^Q=Xdv^^}*Gb*j%@j;1d!HAsuKmbV{{Oz2J0Gs-Jy zUDAIl!LmHbqJv3PzpA|B@P6RV0k-@&O2%Pg0(4Z~$@Cas-s!V?Ipv*6h9=CLdG<`F zyi0%Kp6~J($;!K$d_`_A%27F4DB)#;ikI7cprkvQs5{38OqouI6(M2zA%Kmoxr+CCRkNh1yB3|T;@<}4yH7IhK)s~lz!A|0 z1|{9rl;WcWl2$33Fclxu(}Q~a675~_$;FaZ>rSIopYogumq9O35ue{AX$|i8n={O?30 z$}(s=OZ;}eKzL5IBm9mFd)e%Mm-YC8+K|oernZ1bxZae_?)ymmlY!Xm9#3(9F?q4s zUBJ{|FB8t#><+_%D}FPq_9}NbCH|c)DxMY1#ej~yAMuvGsdPP)J?b{ey9*U=7K)j{xTkM7R&kS%Ym zM|YzUg~bgR%R!GGNB$AjRp>W+bQbFtRigxN@n9`eac2&MDSx4@gX@{-DXRP)Ts`IA zp`Ee3Vgx-Ec7W93p04*k=yG$QlB0`heFI7wfF zEB{luxNjKHXCWqA(TL237#9{D5Pt*iKcROMmSs1E?Yck2J{di=8 z+E)t+WP41S>6a=b*i2u6JX=gJha)6PXoGA4-3!<7>*!$xa(tDxmCDi&VgL6qr(~xo z2qwQnut6oeBnM%@`YnN&t_*T$+zoR}R+^!)*8K?fmh4vVnBjzHl9Ju4gt9>fx?~UL zW0!MdOq3Cs7Vi$gppoolI^<@L%OCtr_NLy^^F8jny#V`=FM7Vm?c5qrwi9p`$>X+z zu_xu-58Q-6jLw+sXY%gxxIDa&l))QLLLRftMVVxVt1-APg;zPrx4^v8ldOFdM&*{5 zA@ktWL+WXGW=IYhD%HaQa7r=Mps4!-(J+JZWnq*YZcsuNM#&KdC1qihtTU+6T|qt6 z(}FlU^PxC-yeY5NeV(P;N8~|nVKLA+lQ!6$%}HdUK||cHS^6ZyrOxF+jO1j48rv% z4;qN7$2{MnT)hI>)Z-o_t)t)a06t-%b~@IhF5Z<&j!SrBL$J}uUwbqsgq)8515@vL zHnm?W^*lK*bR1SoNaLf3(=qjX0!er_unanR%-s?+clAh;uyaFwwt}#CtrUoHTC!R1s4B2<$=4mSYNJPRiQgpqw z6rebu)S`5j;=s`?HY{0+1CpguZG^?($2OHZc^I@0PYVFa@;iz`m`{G4_LK1;FSkZJ zZKrHD{=X#>7J6ZO$V;}}R+9cJ$=m&N$kIc8EbSYKaJv7Tj``WfgiyAj`Jas`PRBSt zir{n~fS7LlD1q|%=wjQg0Ii|?J__Y@ADgDnH}pLXze0nYO4SyorgosBXAid(-O-|= zJEUAw(H$)+YU8D%mR>5lLn_*X&#!8yCjFOgEmDRL==z_c{_L8S$z_*s8v6_&7bgZKV1?^zukqH@mFyQt z*7t{~(D0!iqREdeKIP%ly_(op6!DZOLXE4Qr4adyhq=_a0b&c!Nm-snT+2KUeGU8! zPmc;G!ezL<%;=!Ky+aq9M&hR#rNp?GB@RM``$?H`FUy=oETzW1R6pHzYlgG~&ulM! zTEAgR`hU2Fd-=`g}hlPz;jldaTJM0+_c%Rh+-iP>M zoT}7kEHHuuXT$+qu1?c^Lb?->VCX&}-OZ%y-`0v-Y45|QPsfjA9wLE10RpxjC8>*X zs$XV*<&Ty5oh$=*j{#Pf^+bCGi)<-8hEM$Uzl^C3c!b0OvY znou;lkaEV`1B#pidl8D90=biKtO@zWghm37NGSZmH8OUSpA?PA;S=+dq9*)j5DjerQO$4+$||Dh z1N0&yYbJ4yjYvs{fE}m)j*Yy*!NuNR0N?6II3t~;_(I&faln(kxvfaaskAayw{ zyU?$iZSKf5!o+zCL_Hhu!`PfS3jI{}@{Z8V?+5^M*4a%glSiSSGkf>rfMxP9>63_M z`Y`z~BNqEFl#%U8;2J(2sv`eEu&u=TgPK{?UmGcJ6>#-orWZ?jo{hEDJRYFh`k@50 zeO(QYNvedO>*fcTJDvb8@$=kA9z?E@k-Sv6pAB3qzZqO6+;;-kU$Y5}@OO*7PdXk% zGKme7A`8$SPW_WM^%Z+RH%(O`R0D)6_iAC~l-T>tX{ymeHC?FAvZ*WV{rNOirBIzG zRJYmGd+q&DnyOl;ZWXH6Z0dXVo{jc0(pL%9UQ+e{pG^(gmNk>~AfZr`X#-b5ed<@A zidu?a?foF~j~i!g;8!0M=6ux^Z74SItE`IoO48WCud<51l2~lOq~A|0HemAqhgfXj zS08P$v>RIKEsOx_f5Lwyv9#LnKH9|nz^2h1+vpATYpr-!*?SNonreroQePpDQ-6<5 zeazk~(^Nj8igl-QzOt!5+q+Rtb_?aamkZ&B`;@a8^!R(#-g_edvhh|q!$tU>Jy1DO z&TvuAd}8S!!$mpI5sPw6`WM8a9FxCoPhe5ba8b@A;OgrVD8Eklc{Vmw(<3qs8*8Rv z+E62XT&3Ov>e?AtnT+yLF8*?3{W2@;#r8f42~ZUV^F|-HUmi%@(( z3h0F%TWYsYZLu_-zc3z4ufCWjCRx4gmO|zr%IIacu(*L(^s-y@a)em)V$xef@kTEu z|2SgN%Wl!j#lXgh?iGICjayb3D@x(*tBe`lCyLrfdKs1fDm~D~v*{0rQ?=k4n=>7?znl4mFY^v`D!?RDC>SLig zPpHnasVnULR~Os)MBJ|BX%k&ECzx z^PW(cQF$I`<~bOZ|6(McvBliO)fk3Pur@PCl-P_YV>5Gv!EMB1GjqgdJT<^#GbX*9 zSo)I5KZRIqW{%hlx2H{Cnk)S9jfYjHGtCnld57s@BlG`YBMU^H-78@BL`&QQj*UH}2K;ic> zaCPqlF%`KGOaSm*e zoAXg)s!eYemiv*(sXymtlX`)@XP%g%8Y@)42vzM?L#4NwyC2%9maV@tWoL}TV2y15 zHB0pq!(nr>Lw1&MITN@(c8kfp#oia8s2bTdTOmY`g9uN{%5gqKO112nZIV4RHLY59 z%{IxdnL4?Fd&UPWn~&{XZSp>Wd}FReTGj}#8qMwNRO;tPsm63cd?hI`aM5M!{djuiPx6_hlKx=z_r+o z$db}S^+^3IQ^Ou^=?nuNu?Gt83Mp&x@Z7<}gQKfR#uKHgqcC|GxW99o)X7T8Ur1;noV!=HMy?}DHCY09-8_h`*R$i^hU* zR4I~wHO`_Nrw%;~-55jkd$@V4rW!-^%WkKdEQa`*t;#`rSAI_&Y9fZX6=fJhOr?Hx zlu8WoSyE6l4aMO9h*D9jk&o>Ca}YduvenEZqM1=+Q8TfkN2DgF6N_dZ5zX9BESfRt z|0EX8nEbyIi)J1X&9ub>k+Gu3gx_4?YO#~YW$Vwgar%In7;T=gdaA2FJ_%v>DDh(6 z*f65fgRkg7Mx(1&#f>+mt5-#vR##hXrEar#^{QHge4?wHP^!^YDs{(ED$&(bq;UGb zWurf^cVlNSi|7ig&Oz$Qa)`G>sl%3H@eXEmoKL`D`82D`9iq!k4bX+?a)&T@kXUrN zLv*R|q+@hx(%TV>E=~Rs#G=a`qRaWf)i>m!{5#TC)tCUhQ;PF!oZ7#um8PG)n?dJ( zDgHiW$8#cIORcua&)WN2NcslCk10Z8DV{yXBoP?KKu-kbA~2fyuDA40+B-Jugy2RH z^#5bxc=8xejx3h1srK1yWA2bR=SgIXHK7phSUm&vj#-Dm&kS6Lz+@~Z$J~a%ISky3 zz%B+JN8l3#CcKEiMDqRC@@A+e(A^-J#EkoZ#*^oGmN1^> z)u;IU&+_~0bPykpX-a-irheg0BlcQ*e}l<`kSxEQ+JB3UPStL58ItfQIH@b{V<%(F zm9I6wuvCZaUB!IlI*za88mwL(yUP@G5i;#O1>&EE#3n2~#w>KV8lfi(zBxDSB|PvA0<%$Hc^hpD6;ULkvt^yGPn0hUsRd2VRJi;6M#aG*)# zunB?jEMYv$8_&|~Qw-0uj6ZAw@vPyF^Ad=i`s433m1weeg^El%fQ(-;d1y1rnCt^J z)|iu`xW_Q@PbxxSBm(u>Rt2l=eJpc#M(P@*4&6-w`vE-!1bl$ZMgps;lPB>uL59zJ zZ05h&JKl|52tJLR$JJCbAf(b#ItNW7XL0s+{*B`+#66Zl!EO_I7{y*b11ejM#Q$Q3 z8*>=~xg44wL0}pK&m(X<0+ZiBfP00L-^+$<+u@<)d&f zLYH@(KI$IM5BGai!d9=dy<>+mYo;C2?w4tP0&f|30w{Y4^(_!-Fma0fHqEV3$W#+JgHX6<}RTfL$U$IWialc8LIW#3H~h5nuta z2w>9J5sLsO|2@Paz%CKsO<-r-dMKmTmRWocmEt%*BBk{#G~`w!p4f!I-3V-D;9&#~ zGVmM%Jz-H}UPoXB0u#yUQp+iUo{hJIlK32{NFJftkU=8@WVwz37VX$XD)F z2`kA~drv?W@!7UL%H?4p3;~Tg%4OBqjabxCF019`#G($9{ug3VhsnQ(SkzH24+|dw zn}thz??DK|oh*6zSs}$?im8^DY)LN#fgRPKYlXYW-oFM1GwXC0iE2RS^xt4pueWzY z(N!pBkiz`(ekp=-(nmoJ=jB2T-+9z-o9DWFN#{5hg6P^gRt?{YRjdUxYWPm9;tpa_ z!*^m88K(k^8ccdeVo`(1KZaP;@SRviGjNTp-@ca@!0!`_9)6JWymO%@dCU2uhi@~v z_hO!@j!5}X+lhRM9I+xd_)wMKJwAvv=0Aw9Kz^rwSSr=tJ0jUA#xIJQCR7bJb!v(_ zO%)KT^MvYjo4U;2m!zqJLUk*t`lnLQw^XLIETPy-3aCA&4P<>2FQ_fes{0rZgGYK4 z|MH^YO;N*F^Pslhkp_A`a>V8Xi`w55wVzHbYB%W{h(+xt|9!-w_BTcCZvh*%|3lP1 zY(YxxZ%KLcfQ{PU{)5{0AEWl)t>~+%_V+#KqV+`WS0KMrUui4U-`;OVvQfmx!sQvE zN~MmlRL`fWJ`<|Xg=&&bZL;@6X{v)l<(`HT`=?SDSSnN6S3=Q+6wXQN^a9A*+pAzr z&T30{vAts)o=na2r#L`El5xn^kLS&S>w$5`0xvmLyaQrz)|D@qH`v^%PYHobw>eQ5 zt5=;5Zef1BCB)xE*$gDTF2|Y5v#si`yAYG<-E%<+n1XUGV5l_VI8UW0y-s!aGEg@9 z_Y3Ljm6r706zQQ9DVl6aIY_7by)c(dZ__|!xW1Pn%}2bH?{TX6vkPjrzl)G=Sz)>I z7dS;5^GklH#o3O8^cD4T(NlL7?3?-=; zdRKq(=fUDLy<1XAQr6A+NZRV}CP|6&TZkbl7>;<+md~kv4TS;NNg~#5)am2?yCmsAO02Z{pF+Hp?RTo@P|Roi`-QZ2 zO{#K$C-d`I89Q=dUpQ*E0VbER(}GK&(TqC`m^P&?jB%%jXTp`5b~jY)y(5JNKz#3` z9(o&t2S22hcVTB_2H=OZ^1E7)*&Z8csg$i?Q8Ap~&QZ!7US5MyKQV!1zY_yKYz6c#H+70 z#3!Fb6PijF8KpuzuKOJri^gJ3)0rqzz3Ts%WoCM7uQPdstTno?kVTxPM`*nN^KuU^I(?x91SiqLJ=V}w}T#Y9ok24#3vR@MWxg(av7>xh;*A&LfW#}>WHL-zgq~s8g z`A))R0{;EH@Fh{kYd$14flGeCxd~DFYc!?_;vBL594RT}(|-YRKsIsU+M=I8IVpr+ z2r}Qe%+%hYEN@Pg@R!eBdWR&y_)V4>FrT{&cry6m*?{@nWx#yy(mM<~&b)|ZUVgYN z^I{6*EkMI(u9YA^T$Xu>5b(ognU_f{KU|i1xx~H*i^;s=K4#+wGc(sokUxW#xk-Z6 z2?V!EkiYGjxr4!={0=3A;fKrIyHKtD!I|99u-ssYg$<#fAZf7F!p2Z{4`4fWHex+xO`$i@(}V5RGJ$hL{Gw(sX=~IR z;ukf8oh)1w;ukf8T~wJ=a9N07!wYs-FGCu-iG<#ySUuDmg3nVS>Lu7ytpsm=tt@nT z4sexPCwQ$2@r#SKck$i(G>9hj9N5Be(+JMMDvT9 z!N-ht9L*081Rpob(VAb>3_fLAI|{q#j|@T0&$9cPaACb{*9ppJFC=zbDmELmI|0I^ z4a(<61LhN(-X$Mjc)QO|7;;^ickE{Flh zJ(qS0lQVMUflB1)V{rQvu3(pb87NYfb?rX#x(qFnb-lG;&5w6x-M|7pWj@W1cV_*? zAg$kH+KmQH4Jwjvre@tTK}tx-7gMvg z8I;ugcxTq_hD#+rUzBzA&k#{QPL{R3GlSog;t?uAK2DbP=zU=1c=_?ptjAcNpmt%u z@eFRkZU}cd2%sy=xyJcGip2LBVG`k99#Njw{5V;7V7{QJ=Eupx)meh_H9t-kuKkbX zE7JTpS$NQof)cu}9ncU{sdkzlCkqcVB_uUJP8J?zx=5uS%zR^q3ePIdkCTNP_?yrk z`~+tWMzQde_h1tI1EVgX3ApAf&7{r!) zF@nIaSUnGmI%m8P2ZpatOVymVJGG8Q% zt?nbJPVCgmpm>IzXr@NlEp6lS|EN5m|rA| zT~>)+3GW)E`9-qWRWr+@iHve*@K1mc%|@5hu7TQ@@rdQjWYNC2Kom9sf1f(qj|m)Jf_ec$;k%LO{Q^pk_Ghd8AL`x& zJjx>5AMWZcMZeu|I_ae8u!If?5RoMyOTrq$z6S`4f*M5>cLW4nP+V|F!8;>5>WIrQ zfE$iGZn(_oI4+Dj%8a5o?)x&Mjtk@e_dBP$lW_0-?tPx``=0Ol@;rIFPSvSXr%qL! zs&_f(gx zh-8szTGoJGl!8Lcl4|ID5}BEmjJKJ=h-8t&SQfK9x)n%VvdE5}kf6jRi#$X(`4~nH z#lzYO8wgdfhmVASRUmQ6ipx$w+R~L?aR(+j@Y_ZL>Zp*l2D?mgCvM~zFf=z1SfPV) zbSUmD$-iO-6nBvsFcQhy;%?uOB;%45cW06$>DsNhheFmeE?IFk_iD}o-oh3ipisur zAzpFM)lx4j@+v-1B~)!0m#ny#QmFweOUX(-+wXl5lDW8KCFqTJ4CX{8G{PJPAWqk! zh)c$37vLuD;*zbDVMxaLk#eZPoFHIHd?~sb8tuTAxMatHP)l60uP^{w@nyqJon&bl zjJRYv4{dw}Mi+6(a;?x4ap+a^9W*~?Q2Ye4^qMl$%amf^Z#>A(v{4J?YQ@;3Aw!POKj;;>I-$e%y zm#iNKBvg4_Fz(`#G2#yNO)!1{CGxIB;GsER+{Go+^6kO+JEZr%D}NvucX7$Id}lE3 z;*u%+Ob}}x_#Zj^T_{Hm;*x3k?qJ--B~y5BFz(`#Dg0qD9_P4!=J3A^#$8-8E&o?A zekbXFX=ph2BNs_>Y)sp+=!FAZB6)6$AAxdzG%f`se*`&>KGv2>hX)(mOWJP`M75s;AO&M;^t#F0e78XM* zG1Nu<-j@oWN(JuU`&UrnAEUfzU@^*LHiK)KtO)Mut?*mXegHizs%sGZ3Am;VPXi@f zHd;Z+j=-=MjW|}&PR3RH$iqQvth*qSkc3o19bGSMqM1(i@UmWT{wm|Fxb zdCJczm}o`wUz6$`i{SU}cF5?<)Hp*x;*zDti*A;g-im9PSpv<$Ltbl=FCeezs6PtUi-N_?rr3IR+`m2eeOT0v4h3HIZHcl9a!m=&GVja@x@C@^AnRGt9OlggKjjt zz&?QAlG@jB@5-B-qcKZL>fAXqP#|&1O1kjoSq?)b6|)2}V1EEZB~>T6YK%)((&G|= zl9qAFN_zf5pp0c)vXb6fFKZc>tfWSvYRkA}B?l?QxMWjNr$jDp04@c?F}ZoyiNyxn zARLU0lwr%7EyLg{FGsFcyC+{RsT~A&^5QT#EHTT&cFeN#F}oh6p(ojq0Tf!yK8K8g zn<1>)l40D;RdB;qZ~@zZ`BgH4GlgROj?x*aP|}{mai~`)V;PsMWRyZ#%eZ7E4GLA; zS16s)3e^Ncw{UF6>_N-9xMT~_a@)9Mr!q(4l6kqfWG^Et?&6Z^0T98*K{<4XeAfyF zU0gCwZ=8DL3=x62WQv=KF9T1AOU4^y-23F?lAQsvUM?zNQ$EgG$fU7UtRW z!+_@L-B+_^xS#YPV+3=tWn8lKVI#1x;4RTIE?N3;g;rVI!XZRdBQWK0#%dNG(GQvO zID@xT=_A>D#wC*n3+aVae|*?$X$6|53rK)?x^X1gTSSKVbOX{p0fLseWV~6*#UMN-jeB_%A_g2m(QQ2CYmsbgY&C~On_g8`Jo+nAYa+v?oQsLK z0$Sd3f^d55&^(dNM3P1%B|Gr%nCCEWLpo+Dl$f21wHUMX_pc0mKs?z7g#iIM{Yt)W zMc^}}eXW12)PEEiF&oCICuX^zV>T)2EYeBae)a%Bj5yYQ4%w23W9{eiiB6zEB9676 zu@V?^!{2@;lRWVAVLhND_&Ou% zI92KJ@d}3eI927!@d}0!$5P!Ke4B|M!XfHmeuNMhaV*Y=V<~-~Bu^x;UcLr#S7Ec{N1$q**RU(e1 zPB3_451lR%$5JOMl(e)FNS&lm#-7T_ak8GXvUVsXy+oh&Rok_gX{l562~aP42g{Z! z)Y~%RSZbLQr-sRMm)MsTGDNLoNtL980Z~sfqU; z@wuF-s|@|u1S=ckZ*t&IbCl-AD+!)%^g(EI{97WQA-D3u%EfVhD4#mhF|{OqJ}Iwu z;Ho$yj-}2LQnCil3Iaac!549F=?5|Bni+>wiCmn1E=JEnZ&KPvQ&jz$_A{lWkb-F@fi8z)zLm_MVoG*2zLh(R|5yw)i>1v_iS&W2Doxh9p7;!AM zmV-`ubsWD76bc7Ij5wCM@VBfZ5yw(*Ps*YWj|Qo~>&6|h^st_KN9$M#bDMfs%aV2s z%ihznjAg{J)cZPRJK1+A&kt0(oh>7dr9M=sJP=~UvD8Pevt@}mmiqp(e9J$yY5B)o zf93hmPr2Kd&~~n}eX~(3X32HTl0wW*)tp=|X zmBVMKqQQr>)!;*2sng)Y9OyLo@Vo}+Suj;^^e|W}hse|@qld_P+}^O@e|T0Av+RLH z97{ErJJHQfwW*0F-Zj^)No~6|xox+mwC&c^w%wZ6wp-KNcB`>%w`SzJr4h$ca||D8 zcndJSlBt7bLCbkPb%%kj&euO0_3JlEz9KM zG~SJ<7PCE&9kYB(Tq(OT7A|rMGnp(0)iUB(rh;cv?zj8^G1HYB6Yrr$g-}+h>Vpx- zGTk`(TBUog3SIMNdgQ7wL_x;gS~22SrYCQ~w90|GD$+`?Topzf%k<{EE!;1G)o)P2 z7bSK6fq3Uwdf?h_puqP?rM)=6s8aa`rL;F6){svg3`_ecy`%)dEbXgh8C!z>mDXs> zSzAe{pQ_GkOHUQ0{dF6wvGpuFNU7A?0X+c?(7x9PLTkZF>A)YrTEXkR@i4t~@H^y+ z4gyPS6)Rvz5~)r%SZhBby&>*+Dgi!AhbolOwuWh2*+A$GkSra(9ux~C{7=W@ET$O4 z|8zV)9+58N6$8#Y9Z&omNN$G%N(on1iQKht`sy40&72x?X+23&Ku;M?U9H42NP7x0 z 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 z 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�jPt<4cw1~GcZ$l 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 diff --git a/tests/extensions/5.6/apcu.so b/tests/extensions/5.6/apcu.so deleted file mode 100755 index fe952969ee5e3b7dae3305c44929714a4780e7c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 404405 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ⓈU(dm{;I@5ln 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

xj!FZKB|x6BnyYl(I<911{N`C}q(P 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 diff --git a/tests/extensions/7.0/redis.so b/tests/extensions/7.0/redis.so deleted file mode 100755 index e3aea8fcb15ca3b61d31dcedc298f7ae33ff39ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1541380 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

L#heBF9KImYV-??loSF1sjBNOsn#Ub zh8m<=lhk#PxYcZ&*^DYFb!bVcyV)`B9)Tt)b!SLjEDut-A-Bl*y?BLI`U_b1IB7}c zl2nHWEU8>Z{VKGQY8FGP8A&B>u%w!iR3WsIYF0)nxj+jnHp@ZZ7*fqh>X7&#Db?RV z<7Hvw|86lPsb(bQJ!nZaBdOldPQHV=y!NSPtn)&#A}Q4ep`$xY(1*?PPjSsYrAEKO zNJ$|OkV<>Vl4?d$`P3lQjHIrI#I0uI%w|+csY6Rj-OY({w+))4)SV%9MJhd_O&;7@esz*|tHd<2k81?JWN~%E&sRktFZ?dEskW@ElCDove)JvvdzpEA; zheA2}H6W>%#Ro~L{tvWMkc<2`7DJM1KvESpTT%^3sxP#YUtCJ60qeY2tVl|={Dww! zkO_LKS#A^8B&9~@VWgxG2uP)Gv7{Q1RC{WWYCuv$Au;+HXEvisN*!8K>TYg~yB*LZ zrS1%=Cy*aEgwfCV{jr-S>?>Fw{??KTkyOD}ODe>uKZn){a(R-u>|skLOfuJto|ugS zywH4{f{sBe*?2>?WM*_^p%ysHEO-6akg85ny&l0aB&Av=$jgxZkE~fX%_hN2fvR2& zji)>?+=L*sQ)RQ#?}{Z+sZM_VO5J@HSKK_C&Qnr@Jm^erJF>B=rD}QBta3ghtmxblb&{UC&{J--sorT@E8`v=f&3#gYg8 z+y!|K!&c?#P_KuMb~8bbFw1NNu3b-!-h+|yL?B4SFVw=*Ar!Bg0;q%OqC9qP*-x1=t_mEOv@=4vf)n_1R7Wk_9!lY3JS;us~R z`VcfaAM)Qv3`y!joZPJWk64n_g*drc(`2`hI#^2T0_!{+Ta}dR4bai=dukoRCis7e zYYviX^Z|^N6aoRM->4E~em+7Fi0X_Bh(ge7&FQE!D-QfFgGoh2#x4FM%}mZa+L zv82wHkqY$E0*lP@12hkw&d!q5MjWH0RKE<3TN%jzB{3wavn2JcSd!FPl4|;-BrUUv zBwfBo@+|8-5?hs&>W84C>rBx5&2kC?S5j*95sZ`+0s*P>)RNk>B$d9`kUC3J?IH1o zk8x%*s-)DRC8h3e;NfNN=0TH`x-+CwYJ=2w82yaj2d>e?y#VWRKUq@WkyPeWmehBQ z`VnX)bt;C`DU$jL`<2uwlB&1Qk~&pJYEK_6(CMqoJAO2zPLb4R9HXREzY2|88OZ;m zVn|Y_Na}mBB&ky*)%J0WPsgoqt07HXbBAzTGbwfWbu*59uo;IXT zlGIilqoh>7F2hSf3G&}d3`y!FN&QbON$Mm?wR~1cohT)Bl64+~tx8Ju7U<~j{k0CM zCiu6+HAg=+`UFNw3W0#sMQTayNs_AhoFR3Rq&h=l+GU*Cj4CO0Xi2HNn|XMdyG76> zrS1%=iylaQ5$6^ezZVB-rC);eD`zdKFG#BP^On>XjCu#Ok~$tk>NrXLjQvXLI7u~n z!IC;&Mk;xr7FcYS@1c2c^gB*ckKh<3rTPtMe62vPTU@%NvS(S>bEdReH7<9*J{FcU8l=eU_JI1OX?$%s&mMa z`iN2Qf>u&TV@Mq(sb8^QNgXArCNEi1N6SdPWD52NYr!*qHl&V{)HWQWq*T8JjawPW z|HEQPQb$SZv{;hVQIcx?vXI(XO6n-lA0jcdQ7B-IrX(=Ox8W>iV3LrY5Cjf!!%6q=;eogp_M`B1FA*plNucVHURMXcisUu~iCJfO651Qp=zZz0U zNNNX;QBtb^1&v!7$bU~UB&j1Lbyh4%>Ig}-eO*X7O(p4aMRA06o`|hVO7(W=XuJtJ z$1L9v*Bt%S=s1j&6aoRMz+p@32uWQ=4N^x)st^)y_!wt4qe@C0T2kt6bd0;@&?Keq z45=S+OWJ)c&Mh{66K>SR9ftKw=Paq$NUHuDmegyEx&&HD9gZP&n4~UXzmhskQqAAA zqz;#nsx?##9A}pAqIq!iJ4{l$aEy{t{Vp`-Q^@}XF(j$OB=w6}lGI_6YWJ3qDlR2; zn03AlTa}dRozT$(H)tJ>ndL+TuB6oHD;Oy$1OifVZ(CA_NvbY2NF64rZjiX;Zk*YS zDk*hnNvXTBG457Cla#tMq*}|&_r*BZH-vT#(@MVy>(Li1sTWDA;lC`Y7a8>)XeD(h zhSVXF`UCrw)FF~;`Hm%ZsEpLBrr^|@wBTvy4XHyU^*D}EQmWsB#(WC--y()2b%>;X z6HAghL{c5z6;cOFNgZOHr(mm+QvDcobdU*ps##V?;7Ur3{tY7~g+M^6+!0Ia5J}ae z2B||N)g2O}pK)e0s-)DRC8h4h#kjj4nxxd7A$7P4NbSeyXZ#Kxp$U5%)>Zznr1q0k zU~S zG!Kq`dq`>zj!{yoKZ3@53i&@Lh9tFzq<$AmlG;O31xJO{tWr{YSmznos-#pu0Uh08 zf4B-QK_OKLl#-Vd#$cE*s}NmA~omefv?Y74FN zshwq{o-zf8M{B`TaG!~!c9PUy@xdFps*gfrK85^m5<`;ONm3WZlB9N$RF}_$)QM73 zJ6Y#DuvJN^eiAy`%>+HdEYlIVl2W7ZV5FoF2uLM-Zb|JVsV3AQwUeZJLt^G@oY{;j zDRpQ`sk_NB?$$$-l)5vd_9TJSX3Ttz-|piyVei8_#Q_=q&e>*?YVn07wV6>LfL2ml zV@Pc!so!CpZFjt)t^9PKGhuGCoP5~wUwm) z6ibrYN>W!I7g8NzsiWUk)_EqjDk;@ZK}UbTRqK#yf`3(9bM#ZAQ!r9e2n3`OPgqi0 zNvbI|NNpvlzL1#t8fP}6N=hACQtEDMjJplcB&F^Qsg-dcwH`BHP9a>WAZhDNnhoDJH-5FAGxYy~f zjB}ldny_8B>GES(j|f^)D@iKvq$RbIQNIAKq*ld{T18Uj|6@t5BB@T$N@`UZsh3Q_ z{v<7!-`6dqR*}>J@j+6mzktSk3i*F1U`eeaslb0NsZ}Hug~ogech$8|tzw<$h!shx zeg--^$OJvrEYlFUl2W6eV5FoF2uLM=Ye}slsg~3rwTh$$K;qiZII|g5QtHr>Qg^q< zxZ4CxQtHl-dc6inEy1;)@jG~`ChQojlS7u&5|V23oh7w|Q6Gd>QcGh51M67v>NuQr6l#7_#i3O$DuKwlKhJy`_xjBiaTXVEhVWQ(3nq^ zl3L0-&lM|@QvED+G~NWAW0o(AYmRJs zW;3d!)S)G%?(U3nw*{J{)SV%9&k;YC##P%G0#K5oY-Yng>ZOAgLF`2T7^^3L5h%kW|I9med@Qx)NGR%_$@Gk}24~T??LA9$AplZw^Tv6dxp| z`rpu)Pa*#s#E_)skW~0*OKJ{D^@7HH3eOmI^qa#vFAyt|QvD)ybdU*ps##V=;7Ur3 zeua^eLLeZO`ip2GHHW0yQiIeSk{SYu(a$)u8C6p1(2`Pjcg47S6q=;eogr0Qt|+Eq z^fP`3&(ef_32Q&zlA1aaerKD!C&Wpr~q*Nb* zj>emybIkIPxaR1mM(1Fpq!0*5RXZgQSWfG5Q&2Hls>P9a>WA?(P_O z+o4HH-5FA2f*>^>qo47cFh>)20@epASW@Fjs`CX)YCNMp46URl#*mswQc1sCQWHrk z3az9jmXWG8TMHa#man6EkkmwydPRJYl`+ZAl2uT%&EU6)k`W zB>d=;(okT8Cq1ITC>@DK+{FMoJ2SfK;upO>Hqrb)g2SVv-sGiP6tEvl&%V>d=x> zcZ*}(JpoNp>dugQBMYSZWArnGo?D`o{ub6ZBwJGbNvfM?N%d#cN1&C|z!*{kNh&Sg zk{U=-eV~=pz%o+rn}RbJYr(fB8BzmD>TU5sQmTJ~#(WC-Un_3~7b}ud{T6g|lnHviSw;}Jl2RiVUw5XY5C}-srk3;6K$5zW8l(o2)F?>& za;kA=GpeMFL-Q+->h9hccTYl-l)5vd?o9@%qB!@U@jH5%ChU7yC!|nN8h|p>+r2v4oBch zN{vD(mJ|X3sT^t{6(y-CHAqECYAhsvIn_9`8C6p1(2`Pj_r}71f=Ry3#kH<>Ol=s1tc{N5+5=(&TK}NlsdGe)ZNM$ch5kR zl)5vd&ej5{)^Y9;Vqn~kRGpeN2p(Um6*2K7b9-5@oogwv%+@tU@`WZscZO}^p0_!0e zmXuFY{jw}6pHY7ft)!A;NF|fhWwk7+WRkibT1h3Bk^0CKob!Mdd`r3^l}u8{#0N>K z{sS8GDdhitF(j#ElFH1sq>@Q$BsAtzrKFNs=S^ZoQmT(aM@N~U=bL3E1g@misA6qP z3W0!BF13(KCaFHuAeBr~lOb{KXPnuLDk*hnNvXSaG45W3CMk7iNcEMc9${Sj8NZ`9 zYQlblb-0El6(*?xmswI_M*StUl8TQZ6;D!i>sV6pBsCOTNyV3uy6ZtL@Q7Jfu5L)h zlhmi;gQQeng2sFb`R^cxBo$9mwdz_@@g#K%H0D!nY@do}owtY;NvZw>I$F&HooANM zh-+T^sZpgIOA3L2R5NNJ6;D#vP=i!FNlk^swV!cjGpeN2p(Um69*A*w2%4nSogr04 z?k4REy5o%BL0dI(=V84d(~{a3l+RCft8Yo|3(DuG{td0)1GS%8el)7{{gvR*YW(g= zHoo4)=!OlDrA603KNFN+$GQ!=-MC2;@J>%~CA9nkr+$yrvqAZk&*QTH#xXZJ?sLJH z@IC~;XKJb3g?K(FpYr(@`wMb`VoXhBmcO3$X%LrfPVI&$h+pvZ84ZVs<`+nP9>izl zoCDCE>(_##FM=g_-8;B^*37Hd;EZ zXhC)|C?C&hOATZvg9lKLxscMX;ANFmQErk2*#Mu#9&LcC&xmD#(-yyhgQV+l9c0t0 z*BF*;TB;{NM}OI@_4K#sGKB}piB*lZ!AR>FWYcaHd1HEeY^2%1ex-AU8R?}%8mBV7#2ho+XpMlT~S2IWIjpGukd0N;J` z68G+)TWm^CcvM45&J8%tYuN_4n+u}60zY*6QD%7Y!bITKfpxs?fEj&xu z?#5GtX9?TgN=V##G0tp8m8UwiJk{O)7l;G5wrizRS_GUg zvMs4cNoq2-DXB*p^+jkUwLON^c9QDS(vsRvQZu2I)b=t`ubP5Ww`sxOqj|7TZ6~So z;)A4Ar(X`Uf+NU(3TmgMwv$vx>`_wNNos*u=Hov@YCG$ENUTUo^*QM1AQSXdv%FVa zlav}YYGp|w5RfXO7E;?uY9cjAZ6~Q!kht+|oY{;jDRpQ`sk;L)?v6u~l)5vdwqlTT zHwN7j<9F~bO<0w@fHUthOKKxYO~p1PwUJT(39Y0y#gN)WQdhRNq&AV%Y-lC5sf^Tw z9a`W)vs_u*klI92zl#r&QeC|b%nNdn{};rNeQFa)b;cegwTYw_iDiCqDXC4Y^UGpI zQmQXNN8?SBdIH}M@g+C zsU>2Wf3TF)I@bABu_7tee?UhMJg#*(W|m)zYxXHMYTCh)LLea3ms&`zBdICWAhnL9 z)ZJl}`W&k)-6gjTv*$AI%$jwN*;N!^ZZ zO6opF9q439t%xDDf~2C@ucTIx)LdvKwW5sFtF~#BXu;p2d2sYwK~jH;50X-y)fr|5 zr;-0;)J{pQAgQj{qoh`l)Kam`&unK$zZItaPxsxLuD2brL!n&o0~O;T#qyugw| zARyI`T1c%RscF<8wSuG`fW+u$oY{;jDRpQ`sk?(Q?oL9Jl)5vdx=5c|h|$mZ9lTc) zmT^VEnOonIT1ZlNVw;j$$f)DGSW=5)NG&F*9@wv>7L(LGXeG6{jMRiZTHrymysw@i zwV0%wu9lSQY-rphL;jx=L-wh~BvmMuy!En}q?U_i{;X0`i&^J4#EPU;{{d%mNvToGD=jGm0#XC0h16n_nn4Xxi%IH1NQ{2Qna!w@Qiqn5x_c?c-G8A;O5GV! zwW@&B?4Vn0{3blDiK|fh6^oci%yil)5vde$D`?DH#0>q352_N@sQtIG;4Kq^6M6UD&3irZDPo4@+uV z45?`()eHNT)HIS>1g)f|m63Yi6r8zV3;qw92S>kYBo%kHC8fG9G;Wf$$NRacosybH zQr*Rpq^6P7{bHHl5te$DK8R?Zh8fAi>Z{n9bNoonSk{Vw|>aGJ?;1RQ2*4U65Pg3Q2 zTT-g)L1R9J{2vfQ_NnnCb+uTM)OeCwEtdHkOG%Aqo!=EJl2RS&15(vY(0OLr0)ZiV3LrY5C9gcDL12jpgJ432o9grH1 zTQA0M#*3P`+I<5~qo$VBaFV(Q+mzIBMxAhtB{edJ)JT%L2K$xNNRnC#t)xblk!tq5 z7C6f++vFNjBS|XU&yrHz02=ctg_9B4@mjUhFZ zr21pOk{U`<%b}Ij&@xhAn}Tx>YQf*2d2sX_N>cHIEGgBEpfR69{u5C1YKyB%@DYfQlm~VQc?&6 zq;8@XQUgh99yLe}B&i*ccdugQG!3M#4!X09--fSi z;_4R%oCcR$Qdg7IVr)}VS2OD5p_Wup45=cLx)%GDR1rzt53Qt%%1Cv7RSR5ZmU%4= zsUng}yup%E-4q)0DdhhKF(j!XlIkaxBvnLG4~b=dW=A{v6|v4o#fqdSk&oRYXz?s6ncTq;^5#o2^YIZAO)pI<%zJ-H{k~ze1Cg zx-+Dvr?Qr#RH^C{#%0ku<7g(NjVEJ>=6q&A6V{;X0` zg{<=@VntG_D-8#!g(m22W_gdeCMh-Q0wX1bKtO5)wU8<#sYTQvRY+2gL*nNyjWe53 zC8Z86DRuXLjJtEtB&F^Qsn;riREMB@!uVbMHcG+2#v=mG%+{7v2a;NbZAz*GqpmX2 zlIj#gsuM|FkNrxj6G^RwR#KhHNNs*o3p{O>i}MVrP9&8)%92vu5*qU<oV!x7VLsILZl~kKDQeFO~1+Fp6%i9=IZAdC* ztRA z*X&bjbQO%06aoRM(bPh!4M{Df2B|hAwFeTT-@EG6W>iV3LrY5CeH`QN4``B7cZSp) z`8p_kv@qBBefvH2-)vmK`5@ntN+YS2*rue?7ekShPa*&DsGX8ZC#fM~NmA(~^{80pZ!9I1&N?3# zE0R)Oc_K(nH$ks4%e%!jNvTm3MoJ2SfYexOA(c*2%c(&souu|c;@ZzRvl&%V>d=x> zcgJGfU4kYlb!SNJmKRQwaqVaPj{Z;+)?!k?xud-$l}u8ruuVxNGwO`nEUAm;3p{3)3)>k|DI}FT*^*M-78>&@IL z7t4I7v+YwUtn*i5MN+D(P64TECg?n~Y>dE_lp6Jbk&;3nAT^F!NTra}3Tlu_A*p?k znE4uKHls>P9a>WA?$a1|e?gO!x-+C^$tzd!nE4vN2}e;1{#`yb;MDDCNyU@Y8f;Ti z@r=60G)pQWhExJc4aa^Zl|WLPpp{fY8L7)Y(gLTNWs44mR02s=n{G*|ZU>F|RA<~M z5JQqmAgLR~lB5zyYNuG{7sFCVzXaC#-(p2ls?%nG)Pav-ihsxKQe3l7sZmcDDJcX3 zQWL0!R02uePYqHDB()zB_x+4Bn^7gD4lOBl_j!yv_jW@{-H}ua_uW*G+U&W$A#|Bp z;%AZ8mV(;s$=e}$aG;<+r=ESkmaWHD1kvzVRS|G4OH zWb(1glQO=6&fA&}y_?l6Khv@=p>7C`YCTyS7MH@TmaWcL$CThrFe&V8t92<8AI=KAcl+LVHfYow`bY`W4p1z<`GNtD#yq@%} z$;7hGII^PmF_O_?JGO6vJBP2ZIHpnu#UkPr;H?s;IVz`3s~U6g1+2AcRr922)tN0? znpQPWUSqkH8cnO3C$F&_fK=z*jPoKmb<;iMrK|kx&3BrY(`N63pTHjf&f>TV1vW3= zF)zLEvU&NAd08)-xV~pzzJb=r{}-0X1IvpFN>JKwr6HWN7AtC%6#v3Tr0xB z>@M)HaGZ}(ZXP}`;v7DWE*8&O#`na08{b*h?|0ENZ-#@PS&xDRw*J4G`mdSU`Ln** zZ}rBRc{g?|)r(=Dw^as#-#mHc>mxb-kPIs4ynnv(b)NZ4TqvWpyz+H{<;ibQ?EM)<>XkHbit&McF-S(4h;@$7DE%(fEQc6mH{#M^Qi(z60biX&$4rmwV&T8jhD z@vcZYTYS$7G7{X4ZI@4wUqkF!;c!)anjgC50N6thz&B>(FTwty6NY1wUqMEaHPCtO zC562!NJ@W!*0<>TRFISgmUq{e?)uWa*S*LH-;KMbg0#qW(E2W1zY2Iw#Q9LP{t8$H z84nYeLi63Z{?u1ME0F;e^d-F0cjli!wCeOuI$ctF&`ch5Ml@fzo5e!p6D#_{-RufF zo-H#k+?~+Gnh}r!%|18`K|U!az5KEHx=^RK9pyZIHQn>Sca zcdgwo1@i6+Ziz{J`TuA+Lt${b(6;&A6=bM>8QZi^?`D~U??dI=3xE>pheZ?E69o1P zbmuSRmhc`1U-s6S>Vg%pXGWi7GVX)!JS7YEdzlPv)8hXr0{#s9+Y#_SmM3FE!A2zF z%`44S$(agr0!>;8zkI^4x&<^YBmS+Cy=9h{>_Ol1pwTeW9~4l{U)+Q5H;@FqL^;Dk zmx^;toywNS|GRZQ5rivCLWzB3u4pN!pi0}7nVWJn*@QO;`nld;p7j9cJ8_mkSNpUz}- zCdwHJ`g5`9ug;Z@Gd*hh>*@sQuX}Nf_E**4Lt_>J!>))%JKt)p{X?*1=Tm(Pbo9t~ z8veItd0xCT&1$p>Mw;f;O!F($lIGP+^M9$4=G9Dd{6@GdyvgL$=2knOI<%cn-8F{2 zUK6UjKG5uZ>W-an9AeSVw=_XI-$K}E=UbW}o$muF6J&qJ!tI&hE;FS+`J;wbVN<}l z@@h-AC&`Y%HYMAWxhc2VlI;~k*6L-3TyH~i&CGTaM-0X3v6QTi#(hY#C;a8!Asw~v z-$-6#NS2sZ($BOK8Ho{O-yVkSIFelhA4*pBJJ6WP!!Q%kDA{pZ`z@BN>YmWixF0n9 zOtXAnypyaN&4rPY9Y?a;sDv@&}v9)7Y*}MD-8QxGe|60@P5O$RDi1WjkTX&aZkbbhN=44XMa1 ze--a+B5Jf1M%qOFU=w+rTAIioY$D%Nqlx^%CQ@-1+!e-~JlT9{6H$lm;J>?T3VZGR z>aHI&n~1t&6DcWeB0sZIt1$^;;9FGffP{4PU~bDuU*)$+EU{v5mAk2xC?cqCCy z>~ikYpHsKmp8MWo3RdCxysN69KB~`(=7oal4o{(cX7v3;`}65}1oA;*ANW6v{hTWm zp3@|qK+AEfRLal1$__F$E6M32XCEB%W?Icka{3q#Ex*k@TyoW}lDp9)d52lzCe)9m zxo%fUa=jggXs+8;l3czmE?T$`h2Wu0CAY*Jd7#WgZXZjl4L$r+>Y)uiBtK0LCu_h% zt4i)cbL5+49wwA}Xhjb#afH^P6+H}455wgOv_&QNh&l3TnTNfl9$L`Dj8YFR=;1-} z(CaKFmj2`yVOy?a9Nt&2EA#peYj8#71()4X2dTn6|NhM!=R272r_w#A_j&1kSNw@B zUU1{(YEZ~z2=o{ESm8EFIZx7)@OJlY;A6QUXY31w(PIeb=~7Kc~<0VQlX-4+7 z;G*0lSp-7a$X{SzO~)yJkCYI23W+K|udA#^;Jo~Nu0DmtmR~@FrpQ&~3!+0rd1+3WF>Pl*#h2KzkBeo?3al*=Qt|y5?0~i2-f3e{VZ1KKIjC#Leoje7gB+anK+_p7(OwK;u%6> zlKTdI*o753i-qr_AreSaXboS0^0s-K42Y>Cy3%=E97 zo|$fBj$gGLWQLKs{y1?}-N-!uNpV%f$d3NI7!*AvwUJLBM10P zYeCj_BWTKj>|(zLf=aC8rcsXYUqiTwIVPfU{^Mwwi4BdMi1 zLD!#;9f`f&mMs4j*Gc*qO*wI2_ztwd)Wqi`CEgEMPkdgp<9H8A?q1O3guVYtyf3ju zWcz3~pQ8wQ=rYToTsd0sKvUef$6R2@@>xulinC^x#^FIY*szYr-8W^Z%7 z78tOSR`sDQalIF@lC=5_dfxAP_e(9-%;esKuJ?^pcp$DwvJb)J2w)S4;tkVD`7J z_Z=Ed@=BGbT~AV$e80-`uJbFMavViw9fF-Y0xhCL^ z?GCw4<;H;bE5b-#uX07!= zK?>S*2SYd#@Y2u)lehkWM4}l5y>oRTAFhbFg4xT0UY?+MB!_ZM(Ay;$ezbscW6*o( zddMBqDYpf^P9q_AK1Ep)^u83&j|nOaNkOljr0#K*&jr1&W$*51X?zgJ%HAhb9u9h+ zOGJAPaqp2Jh(JF1FUn(Zg_Y!k`&s7`IA8sS*6sIt4DG7QuUI`2S?QjFs49&tazDOp z6zh+m9+6G%dNkLn&2U{A+2U5gd8q2ms7GY0yA<1iA~@sFW` zk-hF++?(q^1saj3jH^7qT7SrW?oHg=(XUqza=%+mM-~4YV2?a&3Mz5}4_<@1^?evM z3>Q_vsz2>htb1~|*q?qceK;w%HFf;EKcxt#+}6zTmz<+Wa036T55s;Z<@mR;8umB* zh4mE7?)?W}!C4uxI)Mj}Y`=dz(iC2g)l7d=cdXNBvoT5;;rLr*Muu#pj2<@D@#TZT zP=3nfp`#uDVL1;5vQwSFvr^3g{l(bjFzo7e4y$fLKzo;t18;@q}A5@j;?G>ztRMqw-*N1vZPEIl~>_a2) zUmlwvD%T0LMw9Ueo=2rBU!tY)h30h*ucE~L!#=cC*EY3_Q;94F8gCHsR$qKryyu$t;;;AxL3u=+m>tYNN3 z!oD-k28l zC+>wznSz!*N!>u8W#49eI@NB&@D}!m4M0mze-rEe&B8l96D9k@w}D>zN74#Lh(X3g zN#960`(zBAfYnjb{W5O7VU*+FGD=n_44dfqqsQs$&5l22GHoRjV{ez$>60D*R;gkJ zQH+!8xD06(SLSZ>^1|~~1QzZVhIRAF(*F2Z{cgQFw=h7j#fU(N;%43kUD&T#B^PW{fr+S<} zL&ky()c&ruQqdoz+su(H%FekxFb1TrkhXS@Y)k)9T+icb3z+yk52p)Ge*w3~;{bml zS5HbCT*TFdsJ*{fR;A7>%S&w5?oQ%wZ zs{IO8*z1W=v2xu{seCVh##gz~8LCvTDnhHAtH)(}2kS#MSC!+1a6+hjxvE?Y4%=`{ zqWnQ#zCaYAuoSIBqm*U$?d4YKqEe>a`X<&Zx>7E;*iq&LHUw^!I<=B)gu>_WH~bf> zoL9M{Tu5mSI%y6Z?;R9hxwGt$%h#JF&;nJycRem&D_@~1)f*zgbWxS*okD-A+*MVM z_q^=8QdKSrmFt;+Bgrld!*zHTI_I(~jfdlk)O#Q2zACv(g&)$5F|Ri)!Pstj*U2&&V=RGHrQsCShDd-?nr;8p3OCFFP?14)%` z@6j^Xdk+JBm8<`v%JXWXUR8?l<>;tOM{f&iUFF)QRE3_*NUIE1HNd+|YIdEf5#ARF zs>&z{2Qz9Xz7O0@fnZw>Abz*SkOz$CLjR^33Sde|^*t8D0q zRW}?)22+!VpTHLH4IG#}LOZaNg0VPxq+WA{y*9{c@+jSfv!#cll5g38gQ|yMWR$xoZ$yyh4M zlJ~29*t-gCJoy<_30`kBvE&0b)-*r)xijKmqilZu3Y-q4`Z<|UFYEzvv|lGYtPV1o zT3hydQ!x6cUN!>diQ$cxL)BRTb7|=F5Or$ZZM8)Co{aseIa+Qihs4x+sxpv^Zg5sk ziVk0cUb-#qDk;tTB?m!u8*02)L$gYYcBP8*Cd(>YJvI3$nM0?)F75l-MaX@6Tn<)WlqLitnUvRb zP&Q77QWHJ<<&mJTXgLEw4T6&limPjdrS((gjy54hf*>r%N;m^UqdLR(I%bDBGU!{3^`?39U@R+crE`X`od z!=VBsa0U%0sa^w^hQdwpR}YmtNU?{C6DRNyI!aQrYiU0ee=E9jN9k|2EK7=U0;^HO zq?XdrLg6K}mHSOGwiskEiAiexIL-FruU0{BKmEE{DJBQEp-V-D;Xxb*n?GhP0AGo2X*v&oE4p%5>~Ch8pk%bRq&^Z!U^BW`Qokp8#54F?lXEV)C&Qzh zKrwnv(trfJy1KG4jRE~$nUgrFq78yCv*KoX)lS=VG=2c6T>_@d0=zHA@L-5?yr#N<@hho#G;}CD0e`g*$<0a@<^-CH z^Bb4YU9>%Px0%HeZvz8wYXzy(l@-+Z6~nq58+s zf+KxOZzRk^yTF4}dm~9su#s!bv6;RrvY(N)eHkMn{oVUmP>wHmO(TQcHI%u2U2!$o z80Y!>IzkRHGT&bzv0U#yNaK!vNL&pycNYp%`#O;ujA?iO5u8CHH@cr7tUz{=-&jnC z8Pfs&sYZ}D8#&k?kqJ56-Acd3{vB9GMz}jDNBCdjoEEvo+!z_>%Qa18jLF4RUoL7Q zV_7bru*hXih@}Q~&Z?Yu)AyvNjG#YDUqh27T@Rmqqlf8J#6k3~gKAGY5 z#ic-Gi>m70?UJ>vs%m%@B*PD@s_9fafN4Qo}_M5 zNOxIHe*hgK(u1qtOLBY3DsIuiN2H&`7Cw%V&5vyFfM)6C36C8e!~z$B@1WgBcB+a? zxnaUBPGpxVy?q~fOcic2o`93c!*b7=Gut!?BnEj~4JxjVVE%vPq& zI$CC4X?JFst^ZkOn}3$s_Mc_umz8;!^sIJnB8rI@zS6Nvr$$nEh@UqJg=N&iV|COm z6qd|ZDvrm8XCt%R31}NS9NlFKHN(-Ie-1}?v(w-0sCGwoCy zR%0l_i!5_XV3L&^S?$VAc@1;TKf_%6&oI~hGtBk>4D*41hPi>4vAAXJbJTtCpJ6`q z&oDRIQ5Q9DhT4=9B!OMHps!ZY89KZZt3~Ne&S9O?lk{92@g47X44COn-)|^s341NT zIlb8>N0jg7i)yYa)w@De3stx&f)<+IQoZDOU*epaez~e#Zxl*TZ}lC0=6OpnPNwH| zpwEupd{J#wUE%$L9HzHbRme$2x<2*K?v7Aep}K*77$nlV%6uX`0yW4?n=G>~on%ch zeJy3eu+dK1R8?V}9!*o_^G09V^yw@=6<+(`@N$wB+~<_lF#hO8gG{#cVo_>@{WoGm zMR%cWmOt2XiO1aufh!dkzYN6j8~1#;<#GHiC!I9>LoVJzOl{a6LmB6osdAGUNp<+F zFSW7Wj1PNq{!MM7H@19F9=@dJ>a&+j?*f`^YExbt;4wjbGpJ^&ay@y8F15K_#|5(U zyhocrwa~$%qc^PuR7)Mou5bcBqAIDEKZ&Y^ldvZzHSYnb)t$1rwI1VmuZU`+C;G6L zC?&U*sf#oNd0>>9uPRkbZl@(@;(}TVYkw6y1_of5%(!U}R>S?V`fbJwG6dorG||a; zaS2<4nQ3q zOGaC*!EfU3hSg{*m$NwP?+Jq|^_?;6x6jD}quZ=}a|RXl)zv`9_Uas_>{XNu1W-BK7dG>epx^g?sW5;~H&kJ?;c^ ztK`n450g+>Dx4fwqy5iVMmp9;4I)2zxPuz`UkKI5BL7J9NtF2Y5AXU&AgIfua2g6rvkrx& z6xtp_#$gfg!|;9MpHiI!w7Aq7!(PE=jr3-1nHW)}az=%{tI-u}jFht3k19$- z9km1!_k{RYJcN%T?aMMo|BCgLi8nYIWA4Cs1AI>481`h``k5F#Q4S}QjLC~IsCr)& zV)N98u_}=7&4ZfuDplA!+zD#>2`b;~h)ib8_>C%+*~z$L1hbRrRhCg?Ru)hTS4fQFTOCrI|?SnpN?~K7y#Edb(fgqx;#L~eeJFfosey$0HwRKfI{=KpuD~9^$hg}Q=6$6Xi-<@7B>{*&nG2%(ug+qAQokYHBtg!-llfn&~yrXHsr-YG&vd z?eLwJn$@Kz$+Ui`Q&TLkj;AxT6Hs!^pKxUMakS02%sPXxFLDx%Ze}DG=copei@3IE z6OnG)C~_SJqS}!L7_aMNYK#Li8`Xn-_Et2uxXj#za_sl8cOw76m4F+`#xyVx!G{nX zyyqlu`ee2o1{?gg(C;N;`#GIPG-=&ckB4?VUWm-Gk1l;J_`%cQZg6{Uxs69h=a^>uB>Nm%k*3i zm&Cq|;+gU6L8!FfzX@ao<2URtXa$+z&c~5(x_GY=2kSN|E%aPr+jGphN4#T@FnYCFjd1&M>LMP37og=T+CMNFy1udBDvy=Y04Ef_S#Zr2G7a1b+)^1;$ez~{EjVjg zSFA@K#2+WJ9=O~{1nX#(vO8;KU5IddtfppdScP?OE9N>`4`qoBPN<`?%-XC#!(QI4 zP!A6!qImB+oF=oj>C6rNPMltfX?ZRqURx`6M{epFGG^TS|e`PeMYoD$I zd}AAZI91wzR>;0G#WtB0cCV&!s&m;3EpVRf5^%D1%U2Qbb%?Ad?CTKHvafv+lH;x6KK%iapT zQJ8%t%;K`QK@Syw->AP5XnBbx>%Y2xKlDxLFIlHfvpxr*hqQ(MQHII5?1KwDZzuY3 z)=z`z|1fk%@qbntQe5^C=w8D2XBSb$Wq$~rB>ESv--nAl?|@|TSFPVM=sDu=H`Sjm z@|^e&P_+DC2P1^@R|m9VX?vmYCj8~OMCxXDa)T768-i+)gbh5~{OJUwab%s7HWrvG zss6n#{^G$U{#JAg@%L}TCZ!CsFc2T1Fx%-mxR_O|`f6-Rxn+t|tA;t#cn8Pfu0~C5 zKjD-cZ*Xd5%3TbMZ{C*f*p(%B5AYVb6A%}*YW1a0$$i~BfX(mf@+MgDHEhorH(fUH zsZ+gC5u|UB8~g%0>QBU~+%9q&{D6!9xGB6B7WCnxOjDgK*A{`z!o8do~pXz*s_PJw*jd5JFF0VkE^``$t8*m@xcjltvT{l|>W{N$^eN zAwMi7!x~-p5Tj#A-9u#|bq|$=)FUJz<;Vc$_6W&SPz|qVNZOEAtB5`|j9wvunafan zhvbo(Q#S)W%&n)VWcM6|CcR!

q3-MbRKmqG-wwNH=0pP5A?EX1yhLx{c#x%+u(auw0EUm*wW-)7rY$ zRHlA*o#>jzNz6@GNzIzkx~X(5yFN{7**s1z;+o1}?zV{QBlgoUu;4Q?(wm&R&Cycb z-^(e+X&5;)N57XV%hB)U%5wC3xzZf{QBFu`j{Yb|y4$2p=(^_Rk8)*sxmd0&FBi)( zFAqxmf6~3?#jGvaB1cUbky~CO(gq$VFLlt|>H7cC^&Q|*RNebC zyV+!iP*yitLJ4dN5PIl=P(ll#1VRyl2!!5?gsxN@AW{?*K@llR5e!zq77Ivy?%WuT%#*hG{@zIF7F&t>ptItY&W`!QV82fXgU&!eN0S2;sO(Mk!b!35=)R+h(xoENU+X~T}ZsgGy9P6tjub5K#n<9 z6AocTrjb}&hVWmFS3TBAHg*}AvtC1u4kGF;CgPCziwXXOJpFYf>L4)^iRKwd;8*d@ zJIK-BJWq~+jN5Qr4}px)NVIwbiK$2wGBF>CkC<4BMDt=K)*uo8CK4Md!^AGiFtHDb za&IAV0Est{Xm$b#el0TN3nX588(H5Y@jDV$6OMZUW6vb7+7+|kW7TU3yOJ<;^gjUQ z>msp{i3}w88ttYXkVrX*L>?03k!XG!60~cy9R1Cv;wY`oM`AN1GL|AS_z)CYBU!J` zYqnVuC37>1kl4YjSLF;|Yjy~U<2aM?Args)ktjjpX(U?wh(yMp_;V2`Gq1B{HS-~I zz4)$V%N0N$V`-IAzQ63MoGI*OiQZ^7Ur-% zV=%;`--G-(Bw8`i35lsp^hRPk5?aGA9<3oACGA$2)?3@7I2R_#usPYVy38C)Q0_45 zZzEHD5(m;c@8iV`GRr4%DCt2cb50V6(W0nyn_1xiOi8=N`(+jGvr~ko*j= ztxWpK975rbb}~n%aEkP-^h33Qu-Vq%bgnQe{U=HdxakPq5Yq~($&6Hox)kD68yOty zaPT+=sIAvw>j_8Q*6XnKA%N{=6t2fXn6KXIfC;KTOKwLVTK|q?dOsni#I)Lzk2B-n z8A$xg)`ggUB&O~$)Gx&JqcVAfLQFprQwS);^dm736A3Z-RPBAr3NiUq&2!u(CjUBO z@(nQ^<&|K!vLe0&acCmc8~_`D@}c&Au8YQr+66}4XQEYYzA5-7Sg2}`quOh6O4S}m zwO=C?wZ~EI56oWK?*;K#*1VDc0$mFUOzvbrmzIWqdT zw!h_U{(w+YWT@B~qm$pfV1I8G@g^83U85eg*_kdn}U3?<8GNjD<#@v8hjefXhaCC z{xL#8eEz~UK0k+v&OV~O*Vta{CB7OkwD$|x@8z#ERyj4nn_PNrZJBq_iY2C=fSeAQ&6N!tr z&TH^DEhr=Rh8kR^tfsraKpOOYNR2JyBVQ2x)DML+!q0Pj?P!nP3Ah0mhRBQ^Ib{T3a z4D|rJadEXTV5E$0Eo}~O z6M@Ql~MkrCx zUu=Ak;t~b@#l}OxYGeQFZM@aBF;8r~%|t=bJhgG4v2l;Fagf;fFKWw`9y3^ML@&sd z9^=9sSo{E^VTM|qZY;ISsl|;;Jl581*WwxKRh(0cXQ)?+#j7*atH5IMOwsW&p;$aq zEN*nhwRomjJO)@To_W2+v#z)J4ylb6h*R$rgFP#A)TeXA-X~BOEZWUDU8NQUj3qyt z<3|u#(#f@Gl~|PgU)Q2lVo^t8v1pZ8G#XeeS}i)PClrfTi$xz&Tzt4%EGqStYticK zEqeHRi`Ez)?lC@m)R>f|Cao2dvQSx`ENRy1J&Tp}Sw2VW=hVUr#zogAP>8pdlD?)z_-P|Bht{#copqX&RyQ(#46fRrw|UL|DdFTHf+?Qz z4J=%Ss}JOJxWpQ}>ybVJDUL;U9tXY`?Lz3`cnexo$61UqvWR|3ii}4Uxw1}v+17>f zhIy8}{+MNCht+FE(~cbbH;!{B)!NC%%40XHwfj-e5?r=upFAo4tnn?Zk&*eN__GVK z`17Rra{{pV^OP9BiBQNoC1m|baq;IV@n@6oVC|tY&_OY+m4W(G<6gfG)Snvn26Pzu zTq>W7!rg`0)8gJe#4-$hAw2v-%x7xtFQtoiKkKo2jF^b`Q7c|VJOdj%C31tN7!Zv7 z%|83LGIHE7@kvM-i9H8(ySj}0EsRXSIc4N;VPpldF!HxB@;tCG@{cI}51}yfk1*2l zdzX=agpp;y%823!BbTpdk%svnNFD7S$9|q37g@NqzxNE%b zvj3>~E6Q(Ud(j^5wN?1ZWAz+jh(2zP-A{9Vtsnx!AkcjW6{si`-bY+`6>kTo0|Pvj z?U4{DG4+&GRtFw|%=BE>f!Q7|xT;=o9hfckwj&k?W_!5U7y&E}>?9n1NGJ~MHw@U+{ts@Vk5_6)?FIIU8J@BxEmVyoft1r z0OpQpBf%I?*Lk=rItNhqQMy&s%F|J){fzYWB_#5^2^A9}o2sbT^C-BzyBjL!B~*NW zx}kDjbm>kkp>keA<#u2Rm7m1!BZLwvKS`*BF1exdlZ47(U=5X@t`C);-B9T+o75Mj zv8|BqUXL0pK0++-j{ikmUGp#3wg0=pwZ9tI&Y#!)9`>i-JTzs)%CKIZTiP$uCAA( z!H>kkTwO0aS-H!Gxq9;9mwZBDuAY~j>`7pqH|lxW$-W0x=IVK`>tyx4rjsdi4WzL~ z|GLa6&L@^_{)S!-J3BY9WSFVT$D2rjk-r;1e_b6mq^mJJqAS|X^UNhGW5W_|i3yTAU#m9gp z{%#XBz9AGAZxa^lVJ}iyyiHh~0jwqQaJ`{;6S0so#LLO*5V3p)W2l!`4XM~Z>@G_m#hr;| znKMk}Cvtl+$3DX0ezkIVJXyKxm8!=tSfrQB;cDS93;TjPU#}JphY<^htA)dRfrZ0| z#mZxZ!r{ZhVFLE&l*5OG!y&-R;ltN+_{jAfJ}R{xhqQ92_&34iP;vceC@g(&t#CMo zSU6O?j955Se1KRuTqm6T3k**7bHIN?IVnh0E-qEmV`J*Cg47By8u54jxTCa&ca%-3$#OWayOo~VG@+imt zh;!0Tz02uEsyLSX#b``%vEPeQSVY5seurR9lUSiq)ejy6|(7FPg zS%5`Jk8xPFyeBv0-;0M#J|+qKeQ6^e>)5<8!!b@AiE>}d++&<&AC2OT))~m{G2OC{ ziMm1#L zD=*Jja9yjLa4z0pHsKQFSqt5S)3k2FRgmW~YE}SovI(bn3bE|JRrD;>GE4k*HuS(4~$~Cuv zUzF-YEK1E6rDg!DQVT?>wagc#7Kl=>5sOj_)MLQvu>}#Oa_sjw!7oy!Rv2aOsjqS5 z#gckwf0wF7qE#NwsaA_bt3qPYYLRI5FtBR1ShRYd`J&Zg(aMwPYPDFj$^cfa7Q0$$ z=f2;>(bjo+@;H)<-EF<45=Xq-lq*kRD8?q3_U#8GoF=07+yNX5mPvSEQw#%M6ApM! z8=H--cc-h(n^EFPJZGnq$8=8Mf+#b#cT zs3*3H&8L9X=B?M;y!{57UF_Pt;|7}*M{IsZY%Yfdt=hcPCy!i;&8Z~ept0H5dZMA) z+z}-j;u#;c`JmYRNZ7UcpxC^RSp0lYY(5RFHXjn3t0%cO9}=4<5li$P5}Tg}R+|sG zHtXW_U9tHBvULi5PnzRY)qNhu8C!81tPH!4r45SL0do=YzL@+qb7iTixLykIm?TIm z9#1SozT%gFb;3L%jOyfk)W?g7E6CG}iDSO&FD8!r+>43KVh;CmQF~F;9fU=iKH_uH z$BSCMpuOnh1??*2_26SZ7k#{-eOK0VIrg2r2v5{!r%H??lA5SPEUfju9q2kFQ68u2 zRo!(+qCE37jaVF#D34XG0ak}pk>{q)FkgbON;EIIDqv<;hg6a0r+NabL#jlZSyww= zSRT#tuEa)yP?jW*XRU>}QmptpVm>-zD^5vsDNT;%C6mr}DbYIHmCRM=ri$v`m1pO6 zznyE`YVw%Yc$C%kQ1xh~e5u*gs1dD{7tPJQK+0t#=pYL2VIf(}DCQD%ZevgH7|lu6yYkPux`@gagMOmIGOc$Kg}cirk`s+BeXChb7$*z3&Z6)=z+5Sf zqt6P}XX(aQhnlL-&Oqk#L9WjV#b@nnxjrkDdPWk9&kDt7i-FZ=Q^jW|m@ht?Dn6@( z6@dC|s`zX;u=;GO>oe^{(}j|q$kuh?3^DN^Vp++|6ce+tPUta|d)Kp~d4;wbShp7y z?_<7fuqytN>xvxv30nQQT7BGDyCp-dj;iamE(~_9eq5~Xi)loM%g4p)S;S)X<6`w= zz-sk+vHBb4i`DDJ>O>65YV~@t`W9fddcAA4UVm(m>enM%Blk%Yxkd9hG;9a*~A{!^77E*mS`?OJPm}CH#jtXuACGZE_O6mPBI|! zVS&rZ=fcT7IH#O^E=q4D7EV4FPTm7nPEHFaHbw;H){&~d}+Bh!8 zj8M>O2=@!7s8%ILo10rG^Bqyb8tQ5_MYLLpbISY_(dq?a(Q1ll^$DIXtD8AB%~q|#Mw{nbsaCsDB5t^=)ojtq+sxH!wrEwIShSig zT6G3it?m%5wlH6~xey z$`z|@JI{^bBzOV&Itk8`hi9*DGtZX?XgSz+-^BVB$OE!1;0TQ`#Y2dtS1ptWWh;Xc z9jWe;hi=CKYp=RHhP~<-aLaHvY~>AeC)RgG@yVH-Jh@x^t~?66P4Fn}7QZWx!+s?g zBig(2NNnYnpj@`1S&;woP^tZ8jGQ}W&duk!cVb!=<9H>~;6`b%A8ODBH%fz>1hc`7 z(%=EXXmF!67@&`(@|nil7g+hrF^JU895=t$m?$>6eOjwp*}w-QG4Z{`#Dh4ek@~%; z@*=Us#P<>tCxJC4evp_bi@G!>evp{xKrAuwgGMT_#>5Y9Oyo_*y~p$70zAi-dy-eh zKZ%FTmYvI^zceT?s!nX9DnAaqbA%hl9zVPM6*eg=V~;%9pA1=5*&{zf=m@MT zd*#;$%a|`=?3G_2d_pWLd*xRM;Vf5Wd~dwG+&$H>(O1yjiEIt4X!#Mslf<$}iIE>1 z93Yl0NWZ)o>mOj<@NoQmk3{3PFeS&f*jZE6%E{1x|0XgE|Hk1M9OYV>DpnrBIkhrX zth_)hj!6|OgY8@^tBIAj0jrhO#L6wiVr4b4@+V+*OttH+tRX+Y=+Yim%RQyq z2E@X0Phq(au*Om^VR;GjC6;;#%l{!3mU{`yl{>jC_i|a*DYB2S%sXGYesu9xb0_Q; zVd?5aF@1%V5vWdCP`nE`S6J+4$EC;>( z_XjGxV~oqmRN$;bf|C@&vGQGEF%7i21_FG~uL5XP1*{!pSILjl5|Q zP70@BaGPN`!PeUFLX2)M##XLGn&Ki9Z?J@sF;i@A&|dagB&`vp=f8)Xay3i1OYH*O zjcakm6Noo6A{D;`tYpm=Gr3&J3}a28TR)5ZoFlHVkZk=d7M$Fby!E!9p$_|%(-nrx zjpm5Gp1@d_()~hdK8k5)-Y;$4K`fN+7fROyE2Rg7(hr$0lpYXD{~(qyJ0O%+?B-H> zz@-#Q^`%lrRH-7P%v)EL+7eOfh$z(xm8eojM5zK|QR;{&br-NIbySqv%Y0Gls3`S0 zu_$#^l)3_}em)veD#!kpJ@O+}ik+JOeq(3FzvNu6wRoIM)kmV$-8iROeI!~PAr`GZ z60I%(t5zS2R@J+^T74{9O(GVpJ{GO^0IOCXyIOTEOo2|HNH(60(ZT+d1f$&p{+40w zQwgw4Vi~4Blb|XD)(-c%>2O@MzCz+JtBqE++Lnr*#ec7&#N*>#8!wBERq|XLFN=+B ziN(gtV&iaNweerEaV_(O`G3X6uZYFQf5nJ8JzX3Bb#2s92QP$@t5S?Qauw@hchs>R zUN&>o(fuqJy9;-Zd_{d4s_!npOj7(haBdlF71&-!H+#&e6XozWt&TdiIqD=jvXD-xByBuXu>6ALGa4j1xgh;L#rRdKj*FMo^6Q5A;^`xC&* zk>)t)7;#_yPx2mI)ykGbClcY7-`jH1s6c?d%u0yvbIr_8#1|_+#oh+1om+!_& zicpx;A9x+*Qys2l2LS68T{TB{6WqHi)rHchDgQDDqwhsNbSu`Pd9W*SA_gE?6t9+y z#mMO1f?Pf<1>XSfISQi^{}r2~uCgB!OUevjUd#EYs~PLs&*j!6DVxr$mPN?4O}7OlH4SazC*=CZqT};7qGKmdWEMVKMiK(;IEHzYRiBv{~^iU|E&khS7@u z@=(A&?)2e(g`14)=kweb&U;Wm?kzlqLVD++j*%#$#3@H|-(jv<>&`c|mN5Sx2i@*P z4(>6fB*f;M(tn}5@!yXq88z7rv6xZ}vDSm!5Q{0rKpjFXAr@1LA$B*gb~}G5hSU+} zOSkivV#tLC153B_mtsf_2G*g)U&<^Tb&sHwRKFG38UwMVGzQ#zl%?gtx{Juydz58L z@m_YQ081?FAt)LQq(rpI5hq@fJ79}eUb2(@&9JB^j4%!F@Ynd+`v=I(Y5DN!wgoAp+T@Kn@&p}(MehIR* z-?S5(za^H*U2*IPSS90ad$B4VSU1QN>)6{t__>9$vLsZzo>=Zkc9i@#fytkuMk8JJ zvkm+6=Yg+o4Ej5juYloceQ)KfABxnN;_`K;@U<7`bUM0I4Emh-V2V`2n7Kk+Edh8= zDVA+A%H?m4@HZD&`I{sB9R${!x^sm;+``fjnrrC8qndmUbiU9fBlCP-AGz3lICX*0 zg)NlaRU8vtXdb^^D1-z?!!DnnKj_b~=W*+Es~Wz+82pJj_J`ox5yQ8N;ahM{4c{t; zA0igRw@RS=0IY$sO$<#M;~Kh649zE&K-ngSE(caix4D4=trl=(D^jgWj5g&l((~T| zl;~gRx}!+6x_PXtRgq}bgjlpH60P!qRjb{i)iUNw`0f_1_7jU%yG5&ifK{vA*JyQ~ zL9$1+3L9;1HOEHdyw=YVt@emk^KeeN+9O(RBo?jqh*tZ7Rja+C)!)n)t@esm4ad7$ z?G>%Y0IODeuhD8+J!n;|S``>=)|%r6l(=WA%T=*x^&@MQ>*Qk5s@w!ut76frA+T!o zrf4;q`J&aEqSX_`!quCi)yKf9)tlF7b(S7HWa7$b^NBf@ndr4DO>?z6BwF>yIo0Zr zXmuyCaCJzudIDIrIxJd!#C*}}uxM3!lB?BW(W*VLYIXP;trFQOj+?kL+Hl3df3s0y zcSNh>qSXP`DqI~GtwuF(vw}SJ`kVs>vx}P3A zY2wOg)6X2gLy1n)U9L`wR*i0VwK^$U^(7XqPKs8ufR(FLqSdp^7p_i;R^JkfR;NU( z#3`;;r>@cJS#nik;>u|Ah&lE_iPI6SN<^y-IHz2dh*obCi&iC~)wjT^)fv&Ma-pl$ z8PTdYv1oNhv|0kJT%EZ_t7@Fs&YHL~+I(V;Z=yuu44133qE(rxu2yG7t8`+~>a1wh z8(6hECt59LzHoI;v??YRt&?sQkHi=x$VVAbm4HCh$15B_Q5%4m~~ z6)OL&Ly7P#SF1lotMfBlt^O3P0yAB${uHem0jpM*M615c7p*RdR!Sqv%sp= zC08r?!NK2V62ooPHCQj%|43^MW?^QQ7gb!ANsJfXJ?}AU7xq)NyB}v?iI3&Ns4oiW z^U76Yxo(?HERSM_W4YM=m=(%z7m{M#g_dRAVz=U!TkBXmFJJ3i)nAX7Ab)SJ#JblF~=7{ zdXK?_XXCfUQsX?7vHG4hxtGlGvxw9!BDGPZETd^zbF`tY*4102_KQ>nlRMHJ-;GH1 z5UI0}N*_EJKUU}w%e#^N_^O6PBYc}%b&uGl#W){(r`KxwA`&VPQMn)8-(j`Fs`Qn>4rp->~7g4 zd0i>hk1hHQ9~I2eo){~aP?IPD{uZv8Ld$Ewx{BW^wEPC#_k?MvnrUeC95;qvmP#uuaHI3(tI_$HDOAxE zvR~HdYzbkB&H^SFosS^V^G5Nj(dp*QzZ#uKS(MQky%5n^bB7JI@V(*CYTw!8r|p*Tl@fh@}I)E&vnu{fWDX?DFy(s~)oB7gD-jaa$h*&P&4#|jq2{?ak+d~xa`H@ zDrB^r4`KUV<#8qig~uW$7z}?Qky!+htK7K-OK{c&Tq!Qu!~C^rc#+&4%Xa_NB=Ny9DOm z!-4j6tnRTD&CA??sx&CR70Vhg1|3VlldX6xaNp&omM6@y2V9^}Myb#S2dQMGnzZdGPpBFv)pdg4+Ij*@EXq zDs>;9R`LW)9pJZgJ`PM0p|Jp#N#Jj&P>k0&SvQ4 z%oYi}xDs!3wVXSoE`C98O=SqiR^_8wA{d+G3NA-&vB|%%VhEOEV-G<}e)w90)V_jx z<@9#8cFXONCE_Kzs# zzCF1F=~9;|jAG_@ww^P1lV2JA{%vJDcDS*|6LC(GSre*-!LhtqJzeHE$|D5(Kh zUVhLC0|M{EXXO$M3P&qObAq{1n9o(G#nU!P%Kx;h!w6jqC-;_NFYf z17x#YV|zL3_NLrr2bMPn&aj^ljL#C4V|8uq&qW?DVZE3H+(qA=h%Xle?qlEWYVZa- z@F(-T8H~>mT_MgjxX2Df)3omLlVX0ZAaEGhDdqCS7;j3k9pKHxa=q*=(*8U62Ei{a z;KZ~GaAheAC;4Bzk8t)QE8!-K7Zy&t*4R6@B!Upm8P}jFX9S|@CQD)mA1OI{4GLH{ zeT=SC?j`>mM0`TIS4p{Z9_ezgDf^c51by?mlJ9rEBcE?^tk8G=@>`caKf_Lfp1c|L z^z&bG_C@z>(DGb@(q;1)nx2N>?&jp`Hj-aJ;^vf(n4DP($y7P}8Dc0{Z#Em-fiMo% zXY%H#V1v?#``{SFkQIERie=5SJeW|dV2|=R@5jVwmBih-CmN>kd5gHYsxCg7fNmBy z{|stH>^l!2VB;3NBPif33j;0uQBZ=jDF}3T4U8h5l(6$T2#LGrK|yIwSF{^d}L3vI=DkSe8C@A0Akpi@I zkDvmlIqQ5tJ_(mH+F1!R4YnD;jP z6aOTVf7&A+RS}K;UbSSllrYuxWeQ74-SieRN)$rGqD@sT7?@t&Tkkx;@2tM z>T#BU%J|0=7I~ZpG4RAcu5gdX8HEWhe!aqCk28Ys35AC|PAnUG@(nTJxW}o0E*QVL zKFG$mTfEL6H2}Bt5V*qYWY85)jTN}Y>pVk-pT0-n2CwtPV8HFK3f$^-+KvI-@rS@7 zuk!_K-bq(s^z%BcNZl@luX>&TGWXfbB3z8J%-yZ0RO%Z^8W;C&W;)}9FS5w3yBa89Vm9)cdi ziIscAq20EIQy{<(oP?gUy_iAyQL2w26?$Ik!T1v3CxSvR*q0?P(*rNU8=)6XRT%+( z)+qFn{iWn)$%pPjdu)Dw6QhcJ7ccaRX(-R~tiJ{7_6b8ne_kxq49>a*Y5Cw(aJD1u zE!$w~_7u!N)*z zu-`otT8Csf_~b!XXrmFr*Vu!b-UG{EL*Bn1++0_Bc0j(`9^Aqy7hK?jbx<>ye}a|m zLoy>+G#UqKakpF9G%NVZ$I`mr;r2%GRqgJjo#HUiYyXqHfb$Q=(ctTvm*8B2PlInL zs_fj3Fb)daGDeN^@SqP!)XpTxlwZ54AK$$OQWrLaA~YNM;0s z_9=h_a)Q^$D=7hf;x`y?YmIn8`3Nf*GA<1SuE2-E^7a*xO$a!tfC=^`f#JX+bo*ch zyA;&L2SqGTCRz^O`ZXJDfn-9kvR#))Hkf#QgH_Br$giJJ({CuikqLYcLoh5056;w{ z)#$as0U4;K+$tm!f+_Yex6V|z&II0a4OTOO>9;(c*o$tP$Wqxz27+n!qi(61ky1nO zR93K-T~YK$sm8TYYT#xF`Wqn`D8H6mIvvrm%dh*NNLkJjOvdFO`&W=hKA>3saV0Ox zsf$o4zuv**hUl?8`6wM6`YZnW{D0wZ8435Y>|8j*-dMpOEUXV0hs}oIRILU8kv+}S zX;qwt>ioeG|AAN~zCbEClK!p~Mn?*c+5_)Z`iN05ngW&iD%#)}&OVig;PvXkvFv`8 zCk-8oPhNh*B)^3f9RGtRN8-zu7e$s#O#Fk%+0*g8OR88|TrrvJFMgOjcsu)fC5{Ne zDI5?g&zONPS(e607{ku$^Rvt0v&Zt6ox$g4z~L3;LIM5e^K*UU^K;_#Os6$7Xn%#M z;GOK(GM5JDaFpX*ItZhhi)<$bGo*_=j(jN7NP4ZrsNfwO3o1kVg>gt!I?rx%7g=QH zJ%@PiA@+!Sd9D(_1`)hZlGxw0f-(O)c&%USsV-x5iF!x+3@l1rd{a4D12`Julf zIYJUlOLHSFmHsN+GzC=fOi9 zhLN>w3Li_fJZB&lT*qqsM=>6UgInhzt)tU61{F(S6x*(&SRjr!+=DxGMS^kb7zzcS z>4Gwz9!&1y5C&tO=RhSd(GR}h8gmS%Tx2^BQrzTuoR`oMTqF*7j-we(+sj|FR}v!j ziD^DRRrL9}_VD?A=sVF|3YVg`?0u1?H#@2SC#;eZ5-RjVhJ4*Bp(2xv2Y%fup^_rM zGpq|xWgUhFVwG7_r&msRrX775jvWR-I<@+QQ1563W>GY>rWz?U!nOVq~%)>6RzHvO>C+!&e|{0X0&T=J+tLRcNY+pD+6!wU^}|@K6;8pPx zt)R-z8gz?_ZMO&tJKsa^itXL2fSnlsDt6Qs(wvXsl8RkE5Xp4s1I$+yyI&EM;Z%iQ z74u3V#&{L5$qzK3fuJ0R*YFi@QTC z2IHrv2S?H&?~>YlgmwJzk04UDACk7GZB3jQ@hwkeA!+;lU@#$mR5C77oWm#>KU&8j zdS(x-4l!_nsBdSUC$4}~t!kmHPC(8^ZvbR&#@?V7df%pki zS$!8OGm%Ngc3Gk+vjy=3bkqP75%Di56ko_%{dF;-CB#pA3u!Gs-7Vi7Iq`R>MsOg= zj-Ri?G4$gX$Kw~=k5Uy9;$JEYzc}APo%olHaSp#C9luBA{Z3~LMDcqSm3H_A>G*xF ztrY_CuQp-@8+iJ)dy%C2Xmzjl)jg*2wFr zhNd~|Fa{>}+9#6fPX8F7-ik7u&oNXc_ED7O%*W7|c#EPO=Nvqe*q5EolaeRjeogGB zE#y1@g<}%)5$DJ&aDp%^@z#?fIoi1gogr}$Ck1T6InA*+OdPCergIV@n^^EQBxP6P zN18N*y-z*`$}6zMp|sbVveb6CZcZG=)e*mPBww*j9InFHq*xD!B#xj${H77uUJ0TT z6XIXwRzRhLjPF;DK+Q@~X-K}o5P}f*7OL1>r|}}TEW;gVt0G-RhE-n6%2^o}<%@X8 zZzB0@nisd4Z;IX`SscK4p{rSpJd~OoDYczh9EZzFs)+C<{DBZ>C-fRgzl-jO@BKxJ zeH$sp#>xt$WZ8>a_D39|Nte(6nG}5sC-5y@2>bjF7Fs?(`yCd-lAbSVDyP3o^85_( zxatihGXsCC$m*HbkE*O_OsmUA&Y>tB;<<>xiK{t5iWWwSawF`(7?HaGXK5i>O+X5xC&63<3-ueiRy zNr}Jlw~AakayL#&S)KxnFLC)HobdT8OyKzOOa|PQnZO%7G)VP zW!YiM3KVzZQ(+i>Jr7q!slijE)J!SGe!w+sS@|PH*8rkz*5LE62Z)w6)g_EyaRs}8 zef}5lw~EcO+;f%2EKeg=K8#CgpZ_ELEn}a#nz<9BSln=CdUm1uxDkJd+-3aL-p##C z!@z`82#iU<37@|T{#LQ6S3L^w56(8mAzIdbpVfivxDLQRe;54KH5E4~uR6iSKLewra7PAkH6~cWTaFtN##Nw`Shcw8b-so*^Q(?b16W# zbEm5;uTTCTYwBVu2{jvr{Bm`WJRZumJM-dSw$6IU)){8ZKsx4wP@erx7)_+ufidX& zp`K>{L%&5I>LpIVgc{(t=tI3D`yuxsE<$}G`(d15L${c+_^cmcUjy}9^r3$Chtg15 zfSbReTkTH;rUz=Us@sfkM&P9^z(EGK3_L_z2HPbfoE7ka(a;dHXOff9#|jNKqTK>d zVFn2evnyec!BiNiN6`^RG(Ye|UBHnB-WC{*3PYppZ>8RXz#N2eXte#Kz|ny(FsFsa znf;x~f$uE935JWA0WNAn6Qy0bC>=Xp+Hq+W{_YLX$QuoMVwQk3Z2N!B(is^Y|g%g`1@RjrC#uSA79vyDpY z(A`2^SY_quL#9}vu9BQMZGsi*#$=_(un-I7O7eS>+mlJ`G@_1BU)tvX(vN@+ZCilq z9KLHHw7m@_uwp!l?ibpjC@OB~lyO$*8AW;*BD7Nx_DV}oNoZHgs;F-_f-(?#@dKot z6O6f+*d*&)jR`6AvZ5%*#_$k|ENIKqGwy;GAID{6?^}vG%I!qygisy316T;~R!FF> zJz6ykaN!=RXP`fD4@5)tO}7jL_MuCM(ha;RkV^X-*k^FolX7!lIepX6z@Qc0#lnqj zUWJ!ifQU~BHMRFd+H7WG*3vdJBHdZqY<_*4Ev|1f^ZGVhUTt$BdsZtu56zUKf7!7k zmqu*d=OxZL7rmPu`78_`Wqa~sPc54IDOCGS_gG$&f4x~Z<&@x-EI5M=aEtkHA zOGwy3hn5lP>t@=0C>|aH^Fk}^Um%i{9(qLXE$FDb=6W+9z23~VMmJ;Ty6eq+?0PdF zmnoQcA!O8DZ^{}opD@svxxpQEq50Hg%%60L=NT;MtF*rx*uMkGysGt^qi4zGN!4^t zyNvt4Fkn_~P_Z77-)Rg#S8XVZN?cI2BxuJ~Z7jBsVGCC*gj*UO_>iUP97nmBv5w4Z=lSEG0|S@HQd zFxdrvbZNoCOBajO2>DxZqKy4MPxBAXw7~((0{P2_5|U;7#-5Lyxq!dX?4G+jS z2?KlO2C_E5_sdH) z1Zt=#-D!=sD>vdg&XbbiJY65Cu?`+t&aB2jO>`*hV0nImDwUi52G#sGAt$YJ#uwCT zE>AbtGM4iWQ478C<#)=m@Vc7!y(+gCw$j*n9x z19|v;O3DRr3oqa?g?HOdTf*bq5R&WRiCE=^KhRuU0P|6g@Co@nF7DFnhn_!V#k>HP z&4=(uD!kQ>8!>6575+rwGr0ec{t^C^J6dvQdJaqsf5vN1K5OFK0)4`tLt#MNw0{Pp zP5875A)VAT8=5wr=nGc=#e~I^~kY2`(SQT!b51==YJG` z%h-qTx2*i3x!UvTQB9H5vhs)2pb)9^d_lu&pF&ChX(Tg}>b5|04(}5s)uUnL;}win zN%gyl9{xDqSV~HF`}kb+|0H=hkk71iL(NGI*$S<>6H5jcac`OYmgOI`eiU`%1iIvv+hI#~$3?#Q;<8sF#In&jHcN|>AoylGh z_xY*t%~8p%o1j223qyt#{Mo?{YVb!Nl)_ybjEl+bDk9fk&X2%N?lXf`hgn(QXOMJh zP9FTaAir}MJtKL@XrVHI5uH_U!2h!Hhv8F}_Uj+n^`Rlq|pn#JFCc}4R2ucuk!gG|JuoKTwWd1P8OLMZlKnv~?lem)ENT-T$5-zvJON^S}>+0?wV} zcX>QmVdtBTK_$FW%Z1fTtJF&v4jkk?(n2KDorD6QwZjEvIFHgVk1rFHNyei1d4vAIxIQFA(s6Vd{Iwzmj7rr8%l88#! z{o{sMDOEI$d3!-={I0DkD@geg5gV0S{W+wA-yjs{1=BHe)eioHm352Y7I?O9a3F@K z)L?Cl;Bq+S^(c^uNY`laO6YclDYsi2Z|`TRlr4YT%Cw2O4PCs9*Es>j{~5Xgl( zsb2H^vK9CMLr1EVxe=ddzXkQ!|>owPO8v4=E3MI75xOG&?i(t&=+ zOpP`5`U8ub0hX3;yP=}MT#V7FWlU~DfNz>kEo<{h7Cuz=EMYkV(*j+(0S4^eLO^<8 z89GbqO=deQBhaJ|p!<|~%K)!5QiCQpEAR#e{8T*YBxO4U_TDROfU`Q2mUMobe|zF2y8>|Os!;uM+e@e>k@5x){`koF zZfFTZf(0!Q+rdTcam^MCf;E5ebSpg7AM6i@-4uKrr9wdu(uu*zIG+^!0HtaM{{zX| z!R1KT30{Dw>jk@@hWf$Gw)p&Z@R2ObY83prHG3AXWHW;)&GD9&;QKi9xfT2fO8nOf zR)o}dR&XN@KU%>HXz6Y{_%JG1WC#C)lUMBETu9DGubGA7)eGp;a9QZ0R!32mGYB-M)mN0~T!Q^BDscX0o>??ccAkPe(i-U?IMWGM z0+J=FT*al~+9s_TudjL8hTrm1IO9_)cMlw1b8R*_st~C8pM6kW9xSQ(VF{8@-6;oB zYkoulu!di=0A|&y$=tZnQ>|LH_&^U$oM_dmt=HEX2s~DV3O|W$>{fU(56MV{ry>>=CIr|x+75F zJWI4wB}Y5m&<|?vYKFlZJ>EG0-D^I}M{c|+7d%c?C|I-TE(Du5(avHJS zL-e^f<<&|~dsg)>S;ggiB`1sUJ%w*pa?Vj_`93Y$t>m1cvp(Qf3m%@XC5$1AvWZSla%DCO)+9Q+ z=#;OumuyIMj?#o5)P${xh_SP{qA#@sbmJsBe}1D<^}ZrD((PME=jnbz5a$+J!pW66_}Z+Tcnon%&8 zeGcZBV74vWv)+w3Q)BXMo{;;YX=8(!2yV5#dy!GI5J?VvR;}}>C~Ai6(t7zt_?ld9 zMa|$ySesLC)C_7-X9|8W5;aST)U{V)4)!+C9}?L~;H~zvkn~DluKjZyliLx3wL4>! z^2#;0mG&amE73J;}ySIw7*={Mc-Ve9%CG4u!fpj6gKzI}2iiuN|R5534f zEDbi8js>hqH*SRYGkhoQHEtw7_&h5zNaOXtXQSxuM)IM!vr&A0xDnr}e>RFkIsTa< zKVMVi=WB}me6`5WrbrX}m9}Vnfo=Z|rE3c%3naj+pQP`SjqmEi4E0^|HNH#sa;$3H zV0sorQzgT)(pv#V#oD$vhlLQ0u@ZrBPkp*4$(A*ncQkUy0?Op3A zr@d?4)!y~E^sajLkt{G!%eHTYkasK_l>n}#3^cWaIK@xX+HWyWz9zj&exOmGKlDJ3 zM5q|J+P)gOtL1!y?g&%+VHu?wd|d(QM`V!+V|%mSLHb5W+mF~&V+Z-AjzO9$=jSkx z2m8xHrmRNq|yfOECt=1GOxl;G%tT0+A1G;Pe8mCmt21%VRgAMOzq1XuE(xPsA=08 zyrqW~xw#k6CWaBI;45TG1#KRcBAH=?U2XxnYbRfJJdg9aoSJ}1S?*fOW#>WcZAf(& z(Y7LLTiGjc`aDi+4ejOhYLw6#+Di?U)}e;%5=ioO?kd?qB!@{(Rv*hcha6?G;|(l! zR2F*)i$jK}ohwkp?&x_7rG&-*prTe}vE5^EOEGdEMwYO#9cOxwjoyYUzUh$vTt>cz zz%`7B>;%)!bLQwtQ}(hRzm|S0^8#8@gF1>qk0D=8?bUQ~c;lDlc2rST12|LRjz^NT3V;BQaK&YVlvz3_VIrJ^z~NVQy{_+H~XTJTj$%&aX?6%o1d(rv`~xjf!1Q z4H8d11Ps>)`eB5JsVX{HL_dp&4i?cNZ$dP?0Fu2Tk_95UO>&BnliuE?Ysd|B4Nipgz0OG$gXKkb4?UiP;BnMh2E5yuFP7CL`B1+kX2xv!xqd zHf?knrSfh!8tBRj$5!SB)T3q(5o7iuU%Sx|F+2Vpmd`GM*sai0MTd&$YGkSCP!auw zqS@*Fv2hZS943-e_Pdh9MDi#w1~5qOibxI@$?gYS$>Aco1K7$w4#|X$_=Yp)fW`7v z>v9JX^;0Jx?3Wm(OXlQSpw7Nu0zDh~*#)=ay({Q_8o^6%aLH12Nq%+_#F|1(ZCfgO z971`uZK>$d;t=%c#@S@^IHdS*02*8B5y$OAt~jnHUK^5~j-0iqPaL-rXT%}T<3)k| z_m?@k&U+66*Tk&I7MV6KnxpHy8t)oebsj84Qx_wuKOm~#1tC@a0a5*bz<6^HB-OxW zB31aFE456dUIhlz)@`^F0MjZuN<_o&BP%-#IgjD0w*w~sdBSn#5!VOvBh;!77D)aS z!eu2F!Nl!t}F!xiK!u?vN=0mp^14G^0KT~+ih z5&aBVDtecQ4*CG1nHM4z-YrsJ;H*mBEmAE`NQGI0@M6|Tg%647dSt2SLn3;XqS^By znVbWk$m@Q`37Osg14(%=)Ohj2$H3XvV9WZTlk0^E5qGHvUTE-<@j^GeNR_j`?*Ya;u?k$NNh0Mt=}Ju!sUg4+ z+W@f+!|_!I5AO=>$I0yQ5WE8kbmxXR&`k3bLhc-;v-6PC3iG%|`Sct3oqjF9PmgEy zSc;>FJ>3&RDd9Kel;L+f60#UAGLjd~5eywe>oQf|{HbA7Sn6V#L8Z4jid!Z@Murow zIorh?UAJs=D{-gGrT4yc9rk zWkhnONPdSL4dt04+2J%KyYXtg&jh8W2&L>r}JZYy)Sj2h4mCpVd8)JzrpX09GD zLTDuRne-ZClPsg!f9WopHVOTA0H^B8Xp5|j*5iaMoSrfZr{XzWRXr`zY2QFeStxCl zg;Jw$;r|lo^<-BKi#-w+TY0)*80HV0R_E`#VTjwO&exegVU{Xfj3V|vPdStl=f977 zWQZI5o!83YlG&BChGU*j^B(GA9KRa^(v`}dMO9t4-l-M-VvgeY3CI%1?=WY-H%Hg; zPeVW|@t%|7ndxXny}3_(G!*C6NBhJ_J47yfGQ{$rbDng7z0v`^E5Dp$Wp6;vY~*Mq zuSg~3zIQ8mMJjm^7=vJ>saHkn_lVT18V)~DrR?+(_$X|o!uLdUDYLRCBPSNl?jX~D zkAs}w2ll@z!e1rXZE@j*uo?XL|Z?p5~J*M|0# zd1}@M6tTbde1ZCO_C`e#+O7V8ZpV@P9I_-%m*b2qYOMuY*212X8>5GjDKT2@Pi9Iy z&oynlYmV-maW_OnwnStz(@~H5=W8*h%_Y}AUyJpNfN34X>cct}{YFH8jEH_CqMiSO zXm$}K`$r_d70I2*QKx?^lD|_jGcd-j?>mv2{-!4bQTyVVB`~K6MOC&p$&fTPS~s7qb5aE`x!?;gz8M~a z4GjIG4C&?XUSn?CGENMsivcQ>-c>>qGOLb~_8YW7-lbWN`o zeVyqdyWh0q+#TUpxpifQTd;(d)bxMEpwq}#)Bh3Ehr}pL6R_6|3sv;8h#p6lie47c zEWfY>$^3}qzse_aRPtYu%yA%@Sqw?7?}|vhjI%0rMWo_NvFVQXsP>>L+ADjxB_`~j zU8-gcgiH>$tTgio#D4TvUrhhq?O~0^m z8W`4j)-mp#(*#x&rEc}GFz~~2*h7pk26>(&ol=~UybViF4$7KYgth@~DV%zcJ6%RhQ zu@k$Rc1W@(A{(t-VJnH+O2E`U${bfhBo3Q_8ZL>_N~Jirl|(NTu!aNYEoy~Iszh4Z z%MrM$w6Yf2XocH1c6DuKvZ?)oIlcyw=OV3CmsWPfqZQd*ukPhV!mq%(XsqGo?ppZ} zFn=&$r%5w~z}Y1r_90ZHYpB}ZLa66iIapV~+f8ffx1&%4udIN#p%k_I40kN-7OI`A zRkvlz^e>R9iCsxuOSJHElYKK@n4xQl7Sj58Vp&TlZkpgSpK1C7K2asqw-ovD$j9A> zKBVapubpR_I=Eb$T#oFsXtHfO7v_&h7kVG>T5T)$7a#HRu8yZ-l+{7LwX#O?Gk|l2 zFvSy@k2SZV%|yedVaikdspL+=yVVtUu7uKa=%YtP!KZ<{<=`5jw^6BptnK%<8h~?O zB9rPLqjo=d?c+w~iYf+r>B!B!Os6RRhIlJOSMltsz)xmI@H@aA@Y6q&ofLMtQSpAj%8lX;#Em1LmrnuagK)sCuYj1(*a5S%ZUgSc2hQy8d}p9GzTcC3Fa_|OkGJ;TM|PKG52}8z znPcwacnJLNtBdk!sdRlu`zzYDQ17CyF!p?Hj)NdmF&}nG#S7)e>gz1m(SDA@M1mH( zV8nAD*5+cWMOi;aiX}*~?CMe2ikxoQ6{GoRmwbOx-dFEpbgSiUQD?)hX^W0csnISp=klf7Y-)YZHu0+zqk zvBwBr2y8XuE$t#x%aTWwrB{&i9BOM-gv?5{FsHqW#Be4KAyJG(HG!V5$8!A;N%BprN8aj*2u>h@pMdEoT@GUi~TwN@(Snj(U z%Hh^`mmANuV2Kti--5N_U#%JFE_N4Lqu3K~#dsjW(uF}-M=sjBGJJc>0H{g1bnC`W z(R*ql3QN+eRe=@N*NR?_R8;?JMe?%`tw@tnkvvVotb!t~X%IeFXJ=ymYt_SQVUp8Gz_!nSy`V>6KflB&G ze|(1Bu|qoe&nM02O^cHs)fVkN_d~Q*Q)G>TP?sC?yS7En66AF4hQwP;^h4qsB>E3U z!m|>czB!q_!_-)S=Iz&7&BG~wgD9**fV(aMstwe2H4?*^*nq^nNc7)8-&MVMAGj5f(&X~^Wuw%X;eEtfYD<2ttNpP!1i1$&Ga5wp7 zc`9=%aBcugYkRM>u^l+G1PXS-OV8iZ=QvmPkevi6|3Boj-`z#$f1!G%8+~}QlK$gm-1}jNA~ePV6(HN24atOCBluujp9< z+=GuCS*DSj%+dCET0MsIp{7)^^Q{(X&7hP# zukmO!r^c?t5@>5>uvi7$N&3WNW>m||l^*c8j1{Mmohvb|*vBM5FvW}9>OfTodD2RSUVsoVmL8{leA?<0!J`nir+s|l>at)2`e`4Zx=L`eBsfytK~_ia)}VKn`0R93W89N!{m&41ufX;0lK-Ra zz2mDWxS!vy2C0^^U zl6ub~meyS*t$To2TK6hR5B2g|_bO@Kp}@L>T_de~39)p@Yo&?y0he9u3Ubi>)r#IC z4F3m$_VQt$EkRDiL-{z>9GCDx_g8HR7Pm)y-7_%b6d=%*foufEBT)8l@^L+o(uEH+ zdMV{--oWPK*AXQ=o=1Q@x>X}#U@cHJ(Aj)Aa=lyTE$(k%DYZ(y7I2R49aKqIpKrRq z4`r2&rXmi;^lSIA?GLxHt~E9H?zNLW_kmcQHm?JpwV6c~&A z>YM?52<}`rX{P(DZVL2UPmYR={^qf+!<|Cx$mB76&U}qQ^_!XME z3RsVUvqTfG0c%Z+_B5f}YD|D_b=m;PJYL-cyQJm^RE;a?VfPo$gtpPlRJ=v6;^ z#}vfrB43p)+Dc+6@>M^3$A5^WNS$7PAao#;t+gwf5VvuKZKl9V(4iQY7`7>!CAVbX^Oqb$yQL+CSXW^#o7X zd|0MJPQNA~vFr<1s{gsax`$sbrLO}4d&}2cs`mA(B!>3Sv-CteLF(P9vDNxT$ZiRQHsofrz?Yf@+GXnOEM*+!08i&$& zHg^oTmpr6#D2;EucQ^}O3~w{3C)3`A6%Kl9%%b*@y26-vBxlG)p&=B(A zQfN5>sq7w+EQ<~zMV3X^xmh=QES7RG8fCREZsN9)?gq!CLO&w2uF$a)74kA9+zjfN zu28G7ZiR*+peyum(i0W(QsOR?|JMrro9NUE1>CG*kAYxq33V6r$usb8B2ITYpNURw)EVb>I-h*8st>X3>HOmCZY%KMVI?ReWPXNT7(E-E zM^;8@LO%`|V(j1wW4s;-yWQe9?ABr|0Lk;%BD;! zZR#X_9k6Ta(m1WW2(0(N6Vmvy524?ycwbrGm zBE?l{{dVKG5@h``Y8JW7*UZIfKjQZwUJugiq!@dGH%PCOLE29+r>pB^kS-RCL3*7G z(w6~^b9i*S@jfk9jr*&vPlGXQ$`LK)fCu1*T z&(?bBow8knhj&GmK05N=K=> z-|Z-xyIg;I_)uNY!3l@N=Lk!yP59nPYNR(_*TpnPYNRvyoUd^Ol^|d`v8wc}vn;R(hIw zJH!Du8(3@R-H;w|o@U;Y!yK&{9o|4%(aihewUmQXYvw^M;8tqp_s~MzqLd+Y02%NE zy=aD0j9)ZUG!6Yome?9!1U&Yj%i@^(`x|&@b!wtJK%~{_7j-@>nCkS4I=>VQb^1k} zk?AP8tiP+ciSDmn_GyM{)XD7HgI>3eW;af7irr$e!;Ja{r{y2D#{#cavL4ly-vOF0 zaYCqD=SSHz{c*n6IzP&$X!-?S>-;F2rrU_6b$*icuZU&H{Up1#ybFQleLjt^1JxI`4p)QOdUr&vz*bNs$PyL@kQH zLwvo9Fdcr3z+eQrRU>uGA3*-K)pG~(X{UD(-LDqWEs!9tApF_*(9o+!tW z((gF<{0Dq=zs{C3mxqx@cf@Qtt_#faI%2jQ*OdrHN6ePvy48TXh-_E>60aVOf_T|$ z9L-1fpqxo3-HzGX{l%uf0y{9>KfB8b(bHh3?P_tZ<}8U~C~cR{bJgD$nuEv?syQr}3G+CfU4MXqAaI|$eH#8PSpDfKa8DYc`dzXz;K z?I@)hbEMQ@FO=F+ms;+YHt|HMgD@NGy0(;3^N>rI+ETdoBbHKIN~zO`rPNlEel@Tz zwUv~5A290r6sN8xy3{JSw98JE`b4VKCQ|AvsXDzIur9TU zlzI-Z(Rn)7G1YD%2OrZNco9+-!n77&io}lrikBhKavlO}5tz-uW&{o*FlGk=yq7R; zF9Kq-i(EFzd8p4$)t;O$%2dTCiW&q7{l8f^nf+s!HA!OTC)shmTrBC{j*E**?Haf* zsKqQ)?9(EbQ)4SsziRw80eijMSS_rt1+kvnG=3U*>?$|oCinMsSWImLG*R;fo()(n z@52uk%(-o~;zn+!VAz1w;&gNaAdc)^X7yY}>bXrb^o}$%y957X_xGvniWh>NuHnNj z&)uwHwUSyNb>ogRZV{cm&?CS|)rC8Q-^I`rx*}YANGoUxIKB6)Ew+aZL z&QrQAj=GU+7#Y=V@sS%>!?@{6f6wSkh%7!E{Ma8X(A+lS0@-|b0@j=H z(@pMr#{i?)cZ)#LLl^56*Y*ir?6n|ieVJEm4=MIL#FfaK)jfnw(UmCn&k|w6&GRDS zOpfE8BF6K;MrXdcU*dA@_N3;Xu_|Ev7u<{KK4~^U@rMW;WZ)YF(yqc?Xy&VR^JTjE z@XEdrl!HL>&P1T>-&9@6?El1$`5m$TwMVZQ%Dbn(xST^C=gUmFb9y5f%HK%UpTp{~ z46_oKYTr{@{zpNz?Q+i|9i~NUaJ6TV4%3XBODIitm=@_CLi+Jn&(ZVR&xCx^Og%@( z0zFs5=7`gQBXlX?U%P+dMDaWD!U)KMZPCKB#*p|$Hr`wi_h1!Xb%pxKtHQ@pgBql> zbuVO&<+^oiT)D4$TFY-;6EGTH;kE8^Y2EV?*Gq(0E{+WzBo;%s!pv=m8@<4KudvG0 zdxfKO2TN;TB_CMncdc})UpVjGsf%6X7T4(+UF`WF>At`#_D(7G2;zFtp)xYD6Mai8 z#oj5!c3y+N`~`NY>Tc4NFjP>4X>Ni*F z-3!a_L@TM^T&c^)8i%#Q;_mPEgB5UbUQOV07Nxo%__#p<5baNbNC$tSjlL9$-<$VWD zo>G&FA^>;0;M#2Xkd%k8ka8ay@$}=kknEKV{IsC+4W*2|gYZ0(u?9I!rCK7YPCqb7 zgM+c>bZv9q*T0uO55lckDEKwB9lmRyR?`$cGZ371(JaFsNP8Lxj?E=0c=PF`2(IUk zFKq&H1HIM-k)O~k{`i74s8HH+mf+iKA-DqK7`_ZJPg^O$ zz0DEiPTBCaMHSMnlGrDpm$a)Tb~xlsTTL70>j_<_T_eGM2?Wp~> zowkF)pgEIyGEtOy#x@2Yh5+WUjSOa>DDxSTnIkjdH_w{KCnv2tKy1zbj1=(u2B(*X zb(ye*-#2JzACiOSR8XGg4+OqF57jgSOY%Uy9aj0Yb`zPg7!7q=hkgvkP|9gVtVO!I z9l63kAP5fY5w64VqXUa(x4X3slU_Esjv*el13s8BTgbQtF43t&}-=~$-gp<_(u)YhE&N%dxIt^S{;M5BN zHgsX3Q$<$UE-Z2$Age|$EOB1ObA5G^E-ZDPq!^7|Snh1Y8lq0F3r9QG<^txck08G< zufk~rh1F@QJ{MT&JOdrnDUfLrp5&Z+(b4L(bm1%~ov^j~4~XEP-Fd4iU^_Jt(2ozN zJ9Ju9r@dMvc(HQmfxk`%Jf+O^CCa(B3viM8TkuNdM9Hp~dROSzDChgez-5XJLRXW{ z3Ut0Y16{mTIlnS}kc+n~=O5z1F5a!25UVys9(|*$cIUY;aJfkD&#O_+N-(Z7Tm>+y z{ds8E*c<4ibsjRWz=W8nv!5!lzCpOo0j(Xwx`nzssFh<{pRvAAa42dIq`dPIG}$fc zu#2-^(uo+${vok(lqRtmhOZqe6v=Ih48HdnjW4$$IPWwB;ZhU9chIoW*7I4yPV}v4 z8wN81(Y6&Jk55Cx8u3Go;3L-XtwaIQq30vsT!CP%eR&vt#^Pg(_5v*|gL8okI~G(?bXU6TY_JV#K%x;+DE=^oVA2QTCALJRh?h8WxQdp;25+ROO`HGG@H zdMyoTMX96}SrzDe_R1N8N;0S7fgk(I#|4#U&LmoOi=c9=A|I4jeI{tMwKEUs>N8+4 ze0ddC7xG+PPf(?`7S*(`nI@>p3Nqidvjokv3R&Ko?}6CmtXECIwfh9FR90RK!0Y%O zML$1-*Qp8M_3H)VGk6JT&c5MEfm@aJBXZg6;uwegppVso_UFbNfxDG;2xRv9b;22L z|JPB94Fe^$Mp>Uw;F}f+gxmi%)aAze1;XwBwQPhDMc;5%V!w^FhE@#( zcUI}Nq%{qLz>%kVeR=s-HRN=z9*cp%hP*cpv|6J=>mj0RG%B+A;S1;5_hE*7_@2WJ z7SI}v=2^F(-#TlF{P@(7)eKs8uCIeg_*+!g7m(4pLBr$9;>YNmbsGMvEZ(MYZq%^a zXSHDJdJR|lta+4ggNAE-*8k98&P^I_@L3mO5_L9exYcL=)?BTK*aOEbW2 zw+me3w~E*lcODeD!Ef!NhVOby;8wr5eRS@&F*?wV{Nnb}**Q?)ZolEphnet=J=q zpLE0rg`H2(?6G}rLFI;T%b5^&80cPgx zc1CpdGXn7aDLTfFj(U*!UqH}|Zhi^jOspcKTdtSkfO2n!nxkjEMWRgzCZo4DfXamE zHkT-~(um%k11&=vnY{BEy`zq8Ke94a1UEGNuUG$CC#?}%mX^8(-PKiXw&E+jT%}PqZ>zSG)h_z zvRNP3DBqfcsz#sSJ~(z8uj7T_!q)Urx7+5G;yjs8$d(i#mxbS478$ z{t(UNu^*+(nmomb)^(fIcK$&dM(e2&B4)y|&}h+w8Z0pBti-U7W~p-o78t&6C^-~|J9ae3U7Uoo_#KaEQdR?;o_*^uYNO+>CeIrXOhoh4 z!yeCkk7t%NO%u0cnuf1GN730&FjqeW?P!7e#>;hjD%V7Op+4GNT_o}&SBDnJHU3u+ zn(Yy^Gd5C}R$p}4j7|N~D75dZFd1juJY0}ZzUY>*St~DK@%uv=TjWFCSmOFdQy-H` zaSfV7a7~x)9_HXe3`b*qGdgn#VCG!(>{*%(0DAUpm#5KSHs(q*I;9zUdc)@tj-JQ% zZkU7#j!vC{W^4F5dqE`$viXL2bQ))$?1^wt8lBGJmpx7!RONSUwacdNC`H+VAh6ZKISDXS|j&JA-w z@5}R1kn1i$7D~B6BoZla5`JcFk`29v15;KC(Y1_+9x`D@HnJ#q9mmcFWYJAzW4;7? zoQ!UN6=7|icCb@12dvoL+KSnkJe7>@)GHF$Eq+5Zx@!yB4Q23NnlM!60U9d#s8aMn zuQD$q%0tTHmp47+vmU}g@Q^gX{j_GR+M{29c|%deV^X!CNfGe@FS(8ZS*QX{T!+$f z^rhC@9Hi#ASi~mcSv!#+UZCUk8DxK$Ga4)B1{#^-Ry^KN+hcx8U>2#qL*{t41c#$z z#dEk*G)JO}Me!z`Flyy{GV!JilH~$e-gq;OOpA}k#0y%Xb-7&Q5m@|mjS}*`p?LHC zA_~^lScS)1%ooZ6YY$@MEj1T@)ScLOCJK$WdX~ZNG$HGy8!V#z*uY>NR{cWAk*dD1 zdkdvzW-mazc?`kPnO&YlICJK#bB)Zd990y1av7q!>DDfi@6=~@-+l(6cdwK5BJ zK75w0IZ%6z3alXJwaiW$6~e~#K%Ou!(8Ekf9yD3Bw(u;=ODmIDS{jz=#d0T-u2`3S zQNlWhmFcTd()tQ!A+w)G1=azk_1B2a%4M0)z%nPZbPLPpSA5pgZT%O^e+3<>ZsE1k zAq?wdMAhxMTM$0=4y#eO_!&XA^(E7K>=Tr*&VYreTe4A5Lu*42sMj_@N$X3!2Z?kt4k=b-awPoE&Gp97Fz#l0W{=CK}A*$c#kDwCkc1T@!TwC-w_6}~W z>z=LAXlpSg8?RBN^&To!cRH(sg8`$?2r#GvWl8^}XL7C`eKbV%CN?6`Xmo4m+|xml z;FmLyQzbKy&|XYh!~7oN+s=iD$-a!jT*M#;NQCH;mjv;PSd;)05%CYm zjlY7jBGrKP45}kjJm%j^AoXfOCXh102sn+Hg^0j8-q7T5Ke!_WZ5_5kBah!+U z%31AU*qlc+y=n2Iw$7s(g)M&4)_KgUYkk|UGDYKt+MFu5Bvhw9MmIg(0oB==KNVRjjX{MS=Js%m^CEJ zE!jXf&KjC8C?Q+ota2@J(i+tRX~T3y3#?5T%~`|y2xXx)CJktWMn%@UFqK&&H7c>H zF*jtLsZpu*1zIF))Y+1+Ts}sbb(St+wDl1xl{Fgu98y81)1&hP12^!6_j-ktDRa^v0IhFXDRhr1r$B$gdG@mtz_4VV#$Ktv5*F&>&e;D3BkYQ-QWWg-neS&R!k`G<=r*dtYNjH4GFWc!*sHL_Ax_QCz2S z6B}lCN9;_5bHuE2FDtQP7Q-wJ=~qfR0XMfg-Oai~l4!uV(bY*Za&RP?%JnysXotfR z3ZM^#Ps0VQIXhtyC*ejif<8!P>q4KFm)A9#eplE(f2*Nn|=B(lJ&1tRvw@IJ1dzOzSl9l>~_tN4i({=u6Uew z60MZQVhrDU2yb^BA+%$JmWOTsqJ?R~4$kf}U&yL))zb6q?IhBa^oFkv482`^x6tp# zHC^%4?gXvj3u2tuXS^U3ui&aV^8D^(Dn1Zom;E4AzX}x#;Yr>JD(P;bH5|nw)gGLS zbbLV$S3SG)9PnTChHq+pkd_Y?(h<0(D;^1+M5YxK!_;GsU`>3tamO}lq2%DFhH_-X z$um63W%w#!mF&^H+Y&VIOcC(-P_rEj=`e|)_t@t=BUCR473;)f)6qbu*%>_i+9LH@}yP@ zGkopId@^q#1kDM!rYj!mx{3F|_Sxq$(RVM{+fx<^-3nZ_P2dTrtAPviK``w$A=oYi zlkzw&jqeM?Cfl=`gDPki;;MP~P32+;Npe8Z2S(4HGfHUBPSIYLqUC{ESRfURT(a)! zje6Phu9jTaNiLo~>`LX5&|uIb1KZKp1UA&3e?T%kDH+)8JRb{lSMz^r(+Vao*7+Nx z+bt118XK(m=~y|Jhz*qkH_Wq6A?#kPTn$O0h&<&?!`P1vb5BhI4!1cIb06Ks)U@=tg{r2AG_F1$L*vt7B*8v@#o#3^uggQvFdVr>04Gr zKgP$YR-ZG@m2*bk5O8=-5W7_EfRH@)Hhc^Hh$wm=ZEP+;Fd5r47tLi&?u6)@CxQ&k zojIebtr6S24+&^bpms)Vi$-DjqE_q{jcn^-jDy%#y{wAMc~IqefObHM~uu9HYTwSc3#&16W4GO&D&mGP2B`Gw~85Hc*26FimU_ zgAH%SfQSv2;FnbHFa~ix2_CUgteg1(ESnwMkq%W_MQo3|dy)d{%$LybV>>kpWKO!| zd?U6?BYp28c8^9lVXXy|*uBq?-yU>IJGOUR4*0#oKKBrdB)@f-kYW#O6tEP`LoBrn zu7~AS%v%QI8=vgikJSYa`yS*@#9FGC036;xiM5iijd0*`xgTroLep6W(pVdJSlZ5G z7}Bvq7dlQp>)%#&M653_=3L9RY3ITWqw&4W++Ll>G8du8Ct{t{8PL2gv$NZ0-7(eq-zannAb;!{rS~aCdaPY5V-2^TP!Bd}x=^1o)Am7)8c#&^Vr$gp zV3AuGTQ7GcwAI~kYBg^&gvt9kRO)!Z!8H_q!FvASDMt>!JKR&%Robs_oR z5HZ9i@$JGkpnmU2F8cjW1j`$=c@kmS)HEp67T>Ubhrw*n*1?tAunuVF2JK`~iEUVS zqV^gktUg2?G{R8|dT4`=noEK8K9)ZXI%!mBO-JbsI`cG&pPye1JK3P9r$JO=tt9HI zQ6K9^sJTHmjY?%J(m)=4@Z}9cRs0awHws1~s~>kEW+fy@X3gPVO>fQSRti@m^O7kS z7+Lc)GWDkFQjKiA{h2>sDEWcB9ApnsD?zh4gRdc|r+;qI^kR_`A-xSE($y$N^MaV? zBwrao(BxAad1Bv#W~Ic;$2G)3>N^h#Pc-KyH6zfl?SBy;kMuY2&Bpkm0r&~ThxEgb z8Na*?KX!Z{Q8IoFe`<{Q<@~Ky@n1{%>x+NY7e^BDAQ<@Kv-`lp#+QMD2lcQ_+2?)_ z_U2g#ex7~sQdk|B{h3Dgp%+?G<@)RC*)_W1OsifJS)TBNnmg@rC!qrC0vOfor|!Zk zl}+_{2RSdeomuvEq)I)`}tR-^hz`hWI4ifdmPPJ z+)!n|KuX!EcJ%|ktl?5+^&&jVlN;H%UX%v-ADxPw>T#yN!i^K>&&?{rP3U9U=RHFX8xTx3>i8Eqe1UMIPQ5Wt zu~9F9?2S6><}gLcjf%9AaZ;X%4v{_8Yzhu>1j}YN$37m#~G&Z~2(G)hYI=3K1hE3k5W$Tv%WEWXfsi#+GPDN+@Q969r~9HoZuF=(k# zVhzd;nm6K_uAasWtzn-q2~( zniZoy_F=KnsLcS8!_4FzphktdZnm{xDAMGaIX+x62%HN>(ltF#_e^~gB*#Uj#Sbs%Ok>$Hj_RVxL; z6alMx5(7%;JtBD*l6?20F|wETL@x6WbeYlF%NJmZveHjO^vZi=wlb`PIY3t(7GzpJ z&NHh&7i3$PQ@_{7#P}wx`54yOYjwV)ZlxP^E5RICNL$}oC=0Db1<=Ncf{Lu0*e;u| z5>#UCpq<~MDNC(ASO8^j-79J3P%Jwk=h3iSq|M|ab>rSuj3BhY`8sH_KDsqn0iB5~ zkn}(*iM2VA6n2&QQYxMGJE=S=N_0VaP}AlfO?#s{W{b z^3P`x&g4h!lYf1LAPkDEW0HSUTpASK-cSDXxunsg@@R;ZC$=FnG@777zdxsyjPu6i$6AoSQCL@ZEeDLWje$+l0 zbzy-+2cOB*N9`Sc)E=8Twhnd=4nJz2^qz6*AFvi+*yG`63q|40Y%vGugyF8p4ytR}BVkDqC-U z)ZXDo?UVX3XgM0xUTu^~^ z1x7;SPGy1$t(Vbb8+Wb{RAlvKT9KwKvA)5`Y1~C~DRl=cOV{@>#H8>7!6~dB%#_EQ zME0g)DVNU0U1nHJC{JgR^+<|{1)W4BlrGWn6cJ@RL4^F7h|oo|N;-+T!e|xpFO+mL zVN+Ob7Mr`X`j0zoH{dNbZ_7h>mYTVMcrtqE$QtVVD9Y=iYEJQq3L+CUQ5| zB5cuRa_%iZgObg20cdk?TZznUo}w8*+a?M+&H4gOkh@(!!G=4zWRkmM6L|RcB6set zpUIlRWbR)*+JW^2XCeBZ^EwD?M_BvhVR`V*;^rwAp0w+qv!z|jDS7~KaD@T|X$osq+3v1$+@{k(HI}RLTDKBgM zD{zbUz^|Sz>D7x&Yald}`vx}yfxPNPmUSlczr~URd21F~nH-(D?{eJ~$lHK)j?Uco zScgE~R^VEyx|Zz_$h!l0E8FZtwpk!=JMcvE`$Y5G4b108bx@IMS(LjFInBL(t~0uN*R{%@eK+EteI z7&ZBmmhTww67u_5d|yN0|08 z9|fS@evar9jc`iGV*ks#)O?YGo0YuJZdXJ_Ry~&R`EcP`V)dX@U+_jXpS($9zOSZB zTDkQud46-FpwZS0*7I9B+3@96SktJ_@17J?X;qT)`}YM^S?7|=4_^wJXFWyqBOfv4 zjA<=k#-IK~qMZEfqBK97T?ldVW027=&p=e3{8&u~0RBd8$jMKP@DH83U0F8apBnB~ z)?ViQTSFZ6+)B>>&;`%} zSP5#bLq{Vinx7z?uGn{k1f?98RL)63(+Ab0D>nE^r%hZ{BQ1hhxBg5Yj7Y>bPsNrq zmKVe}5N;)@vYMOGzQrWcXNke)0(AcpEYmsWwL`hrFZP z)?2sq0YBAF8$#3SM&ur6g)J_#RY%Rwwz$exowSKfSllP5BAtdu7};67=(Gaa2&rx? zjPC>l%@GXEAW1QU^=;KNj)~OzfKk14*@ip^tIp6UAcsY&56NiIgXSCLQHjx@`hAFC z!4;V4qy3cJwej}`+38jD86l5JG@p)M9~i6rW0BBeCW2f78ZE!cNAz4p;9t!o&QO85 zl)DwTCxN*vzBT<71}>E>ZPeNzh}-H{1r?nUwBjf5{B7CMTYVR2a3cCps{!>HBrBu& z-c0m>X3GB#$)8FikZxP>qUcFU?^i>*L+VSaE`;V3D|))Pa_FKsI19y%Ll?0og_Cuz z!H94B2^v*(69#P+cZ3Qe>U$t_C@phkUv%>M^yT5#46 zY+{|Xwl|&7_NEis-YnW~r;_OUzIsY_nEnREDDMEq9b(A9X#OJ$T&dLgNNFLeTS?8e zmp*+Z`*eHxBETwk%+~TP0SUGj{~_{jTYLG0fCPE0mO!^t42#DietLjs!dZDQ5Q{7hbsiyyms@mcO3;1T1bAXQW8NSbTyX~Kj_%DRs z=%o0mWOYQlG4fvV&U0V&hHtGNJ>Xqn0z3W@dB8xO+4&{PaRieCenxiwTO&4(8U}v# zNc*my2n$_4?N9K#CdKbvA0LDA_`T~pncurUmtT<@NcoOxekCrSv)$iL8v@3wVAw?j zJSq$qBd#m@)GI!&ARhrTx;@4Y^oCLuZoV7b-*=HZ8NHy3sP+v}ZTU^`4k1I~4QYj$ zz+GfGydf&Pk?GQc2D{!8w~+G7Q26_*X9R871c|iHoM1{g zJh!?;^Pcx30bY7Q@C>;2{0)is0rU)l)vpZDhhpnZ@M@5az_|#VaXJEY%h9VN0ql^-cxoVeb-Vx3G2BbJ8U_Kv-9|H7TjzGQ52waE2USDb2+StAN1Eg&pknz5$-O@2{292n zjFtO*V$j!?7t6@ju;o^eKSBw1`#4}%0{0P%pfNrlR4hhekB=7M6C^{yF{~Mi)XRO4 z?QAW$d;%Nktp6L-9%b=(KJRc}wn%S6X_B2Mdg zndtaAV$re23T`a4j+cp!2LZG5EEgTm0Ma^MAv%5(SnGJ@sXEs1JCcizuM`~@-RbFA zN)`i-DoaW;+9o!C?#UQ2oXzOPY$4Ia7E5LntlA^*s z+7ViSK0HfF;YSu0b2*{vNS)j4Kz^AQB7zSS%N(t7{BB^LsVI%d6aQKP92J!w1s=qe zNAAm71+|wUt(9*u=c|owoP9asn)y~hGd&L=KC~0v@FoO?AW&9;)Lzd4tq1D$Isz{; z@F4BwLtp~}_?k9LTz6ELxSQP1^ch1`cN&5LYCp=Ubz9^+_ZO2@(5#PK zLsd`q$sy{vo3fW1UHsL8^I#~l{36a#L&&7nJ#h0P)AtZg-;)VtO*(|O<|aZ}lny0) znef*NX0S=Qz!7wfgE(U}szfhFPs>T%||5Yu@i%6xB)1pkKDAVrq z@=sy@`vHgZm|Y2s?gqOeHID3B>e;%``x3wtk9kLmr z4pEJd%6wMt=3aSBOZE>aYEVkK%&CuZj{f0(R8;!+qwLPj9{`l@EpR#Du-TWuRsTNb z+W|Q8w07XXU-$0a3x|)vdRXme=Wg*Jh~b3r0QDK(3ozXU*8&Wm$%c`%e+WhJLsZVV zhv;Do-C(%1&I`rJTKzNbkAn`Du^!t~Xx{pxWPM#9!_Xwc0-BGsPYlPBv0w+8Q zD4(zs_zz*(Ajt9`uFP(E3`&$vlBw8Bn(u?(tWmFNecj;xvSo&e-)MQ1qyjXK*79jdn67(PJwvyLcx`+>0 z#2+N!+{f+SP}Q0CXO>#yW(d5ci*36fevly6S<+Kyv8R2AsNqt**{ogQ0H7>m=a6kT zz|k^23-qn67%8h+*{k^0UNBr1w1Un7DLU7xJ}7yE>?v%w)IZ(dMWC?I_jI@4z?NNf z5RMOJy|saz>tVprRs3S+MpOV_MjOrUB4Oi@H$b*f>@vWztx(eNvsl&J*yxWTZUEQq zz1;$9sl=US$ptV#?GYcUf!*nWKrsUY5U4=ljFAZ7Lxa60;CCq^_*bOXLig$pJ=l!5 zwf^eyh6eub0@eGdi5_fs$Y8S`_olWxWRuqtSWixO%F5ffwxn1TtKlRh7t4l9;|;{3 z&AVh|{zxpk*0}fy7=GD|X$-$wdT8&Ep*<1FU8Pm;7Tqrc*1dM8^xEyfsMJ9A$@%Vj z->uBtcXg$H0LiNug1S=krBWlG^eQ!9Dm9l_=4*}b0oL>NeAyEG3al%A`KguGu)$Ma zr8VwBES0`OD!qbODy{KRU|ngAQBQ4sr58!1y8)xp%US7iUFmAK zvYsESWgzK?v92p!E|vZaae9$bE|os*8L!gi^89!SuuRQgI_-IT+n(z}4Q5;R83 z>q?A}N^;pDb=J^)7P+5hCmt!ehXMClMpJmEOFpQrn8LH9&bq#%rM~wttBl<-Qs1M% zJ-AAo`krpm3iNz+vFgbcV*SIIV!24{!2lPD6A>ue3X&5GLoZ`3Vgs9da$zX>xHe?I zvciS#+Hfh*7_JSkbIYnmN?@rBkM{IS7MU{)dJP581Y_x!EWCpbiIf^kzhvRNT|mEN z_2n5WqXp+5GaE8J*f=s=8NV;-g& z*Kyfh&|oADx3%8as^}7hAo%m4SVc zrTvEyAI86hGcBXp2{&P{1O9(?6XyE}cTrEfjGWR{LzwUIHUu##@SxFTxE%sg(kULp z>U2-SJy;m$#gIBe_h4b~*0~X(d$4dlbr*8uG%WH0n=;`-JHpp@!dWg%MCcwYT;GMs z2;GB)8@R9_!q@%64P96msUoXv7Zye69xU9*g(VTX2MZ@%SQ>efVl;MPd4%r4!nrOS z9ie-$aK0*q{Jy-32;GB)o2nrKDK;E8r9E!6H8L?tt(1VDSz2U=g|p3m2(#1+P>Qx(5sQQX9dJHz6Y5HwG?K)IRS* zMCcwYJkZ5kRfLXd!h>AAT}9{~EIio7yH$kl!NMopgGHXhLMmJ?rh&I2A}d+B;cB{+ z5BFeEx(5qCWQ%*S@P4Yq`KBSF4ruMbpAdC-P%FoD=pHQmL{F(bpXG3nA!rt(qex$6 zy`;x5miB4A&>v zd(dd8ZxAJld$7<@uH0>L4;H!nk|5-#d$5S-9?YS8ut@c5s0BKtvl_i9vglz!wsTDq zXz>Sv5)R#iMV8jbi6|c+p?k2%vSoq_9J&XKEI(6Fp+omzkrjIc6*+t{-45V{A8TyG?Fe{$#^EOJ9j zfm@YB_h6BAR|v%0Ky(ilxpAey-O8bRu*iD8_A0LC)=`QL4@zncUNxe?H+?A(H?`VO zmm3>^q1@A=d$7o+;gagP2Md*7f%eI~6?}eG(Isz!fp&1?xd)RsO`^-LN2g_D(LGpn zc^Ebvw;<(Ruc+r9%y|}eC%W=Gs0r@D9J&XKdhWp-x(AEOxhdR(In^j8dUXx@Iqudv zbPpC?tx=&v_h8X$G%9lL8wzx7ADAILneNa%Sagj>^BlSdi>@W&v)vBegGI03g?qGg z59ZK4So8)BaRZL-!J_Ll#QnDi&_km)YFO=aS}=9JhPeAi_h8Wt8m{p}{du*nd$7>3kD$=H;vUStK_xLd>d}jm zy-saHJJh=qmczbP9mPPX*AA7pZ&w4+Y3rQ_IqW;sQ;65@!9v4ZAa5OU4`$!1Ugj{P ziwFBYH4#<>PZ31u9?agYMv4J3BXkdD?@_ut*%3ZFZr`sCNN-L==pM{|K={DEMCcyO zeo#Fusf7``2ebFOtcoIZ4`x54Zj#iJ2;GC(kEo$yV@e~RpxNzxZb9YRJy>Yu+mO%} z_h6w*qv*U2U!Muhe^PiDakvKyEsbI=fuR+Ld$7>zmI7S&V4+d9xSvw7y6eH_jt@^8=-x4E=yA|78yhP^>7b6HA$cY_eh%U&^=h_piWCTbPpCf zq)|hM?!iJe8YLaN2Max}QNBa>V4){CInglCJy__;+r>=a8F;z}3yo<7q3Vcxuuz7& z9w0%T#FZ;zgl-P-NoUQ`OiZ3)gzCCYYDefEEL2ZDEn+4j78)&-P=^F2BP-EMLs{ym zzyj?aEHrit3+##(ON1J#&%6S&Pc1Oz9xTF+9nv?KeR<&o-$)20Wi`M@EyX=pXxtCv zDel2Sc`6$fluG1#JQLD1P27%&M^fk>EOd4O2-;pI9#m5c7dLd{jJ$d6of z4;C7K4hU^=59Vy7E*-i%a5nu{sNlg7ld*I2_kw&5{Yg2SwekWE-Gez>{`C0KJy>Yc zz2FfvAH+3Xx_g*|3o#sxS!QVF7l3ixQVGq{Yyi-+XS+O&`r;leG-WlIHK2R2(0Od{ z21%40n))7CHK2R2P$dZ(^3GRi8fTw|bPpDq&f(XP?!iLm-@+hYGYVa>O$X02LKp57 zT595=Jq*Gx7|I68sc1N9ZaE(mhz{Y6dCu^?Oq?-{8s2r`kd;QV!jNg>Lu+zgL`%`mr=s41=408}h_}VBR;rQMs};-n512T7Z7SCIV+6^BezjuxzX>v(p|Fav zW>H~cJM^m+Yt>Xx!l@SkD%AOsPEB*5_8Jv9K~y`|Nh7{e^&a?zd4XPH_z9XUTAN0e z<)wWgu^gf-(~JEFvFVECN{nY^GAYOm%G8i((~QNNFAP(mF7lmr#7{ z3?^{SqF=38Z;fn+ezjtKG)g%1s}<|3QPQDbtyn*e3LN^?iuKos52bKDBYw4FrNv}T zzf#e24h@4qFtRrq>*%ygVMAu9GlZ z(63bV{13n;+l0Wi+W2SuhYf0X02{iINRXrxY2@-a}^YQ)v4#3Us*%F$o!` zYec_N3&AYJqHXaj6(7LT9Py>+`IU0$S1RuLm2&7;D(?A}iqo%De6S4JJjA$D-|E1QISKxQt^=*l{nQH zDe*HkDs|{rDn9CI$ye^wX$j={m2y79B0WC3l|e03IP@zOAA3Y7E1hMqtnqVr$ix>L z=vOK}L8EyN{Yu3v-UOv=XX#feK9SZ=9A0pR6Hob-5`V1m$=f7#jdHH1rc!>T9Qu`t zPhpk$%qFzG6pGGHME7zJ*?%Rbh?|hRFXJ=xkJ)PGIE0E z>HxUl)vZ)EI_%KdSXfvfbsqji=luXbu3xE_BRiyIGJsz}UQhf{IL$_5b+QIXRtBdew*L3yTi9~vcik!00^nw+BI$=V4jw{Uuhid3=j#Y0qN z*7cI>rc|zbQ@I#Iv9d>{hp5P$Lqhv>iuRKfEzeBC0;y=^k`+X6tX?AXev({&NG@(b zGjTGY<&uyzc+g}!iifDk{H7qlatBvkT^^Byxv>2|wP}SWvOAALdYzUC9<}kcH*BS4 zYibXbGZ##)5qgNS%hkh}T;L%pLJv{)Fn8k|h|oinJzN?A6KaGmS?m$1jWj((*&|a2 zDV$*KGu^y+<&SWb3-cqKjP0}3vrovFb&Eg%Ns)vd?yjiy{w|0G{K*?h$&3 zvL}d#E1UpEf@C#O-&e~kO^h_`NiON22t7pElT`$52@g>bdWf=99-<=j5M`%4L`5o- zfK%05B1A=m9-{0@^?|_32t7pE=eq|3RS~Xf>`|F%qU?Lyu(`xTl)d*U@_U7S?jaV*6(~JK*$-q?iqEIepZT|tvp2aX;f8{BtN z!iv-I$cA05?t(SZ=4i27sB4avoNA7i%Ai3zYIC$KCBK-X<)@mX6{nh`m2O_w99`)` z*Bq@98F0!N;l{yUt@O^JFj9~NyhiCAN0A(e@)N0;*YBe{@6pSO=h*;e%r&jZpQ>(evv$~Lc zD$GuZO(GtmGJDUZ84?dsb=$N=tz~;sw~*5=`#s+SuiMtnB{H23=#_QbZIdx!JKc%e zYm{*M5OvT9hXUxKbvtS<1r9w#)$OEFp)(ztsoS}k@GNpxqf6BFJVZJ45LMUn5arNA zR9(+Qlx!;Mp5!6w0;GzEsCYl_C+tc{kc`h+?A0Rfd5DVBLsZ=J5T&wvdx%=WIUj??xahg|9)$E_v8RE*pt%7N>FN-pc|lA&{NNobx$-W3u1r!m^N^U2 zaSd^hiifC-=Ca-VrVx*AWVBq+7`o?nGlS=?qI=%<%4n_6rA_gUmeEF^@!0a2l8i!q z8zSlO(a?;xvP{5&AmD$FjYm#yqM@}z^lxGMe(!w||0o`-dv_ z^R~WD_ul?dzd&gL_qhzk8|S(%gQYG%NFsZRuFGJKM5EQcXi#;Q@}5C@(|s1{MLau| z-NeAlUK5eb&=t=Gj5c`GaWq-H1yD$BjdSUDF9e&jC2FM`Rm-SzC2E5km8?e8G>N*+ zjjCeQh3FrCaBKSpx{&IjRIQuZ<`X|k!;pPGU>t;U+cjt@1l$2mIubD2@$M%wQ2iT$ z+f&>git>I;D9?rV5dF=20p#*fTMw~YLtX@yZ&d5-%Yi!-!YaA!d~1!4#e^lo{UDM% z$@i~I?V5$1<+0FS@`z9UmjXsdc@5_bVgE32XQ->Upzn$BB(X*-UIy*gtZEKu^4_|z;~u8>mGnACk^VTQ8P!ic5JB7H%eNi(Grhko;Flu3V@(zC04c3L zoS}4lnf%IRNizz#Xe$(5f`wk^33zt3QdIII;=9x$zVn0$_+oUW6n6eAcw%vaCSt$r z#$G<*1KkeEMPOQ~y7RW;t03(DHxk>wiopL6n1(=?3Xl}Xk=}!&)62LOvHs27uRxUK zyTr}cr4|W2R|3@{a|uWKYPYO)?yr9-djL+qlZy@GN~r-qR9Yp{)_o0%oiM*0F)hbL z!MI-v%-d^A1>-}dRbonB0>qR4DCO>2zi=%FQ#Dr=t-BqizYQc4CDKGoK%^y_ClcK$m=etsiJlV-iROt!UjrJwzx!BA6LtGL7^E0R z@mYzJ8MYIAg5g<$Qw*;Gbn7_cL(TOl>$p%!y|=n?_7cQx&~ZoHxN^pA)^WBgQ8nYP zQbr$@fB{j@t3p@AD?iuF_n^Gzn;S-78Jf>?WcGavuL8-)e4YdILBKZi3k~%GQKz@@ zIIDE_7dUZn9QEU@qFlIjVgI`XlDLY*AGBsFp?~co3)@UP7oT`0O1uNU9Plr3ymtfY zKN1s?dYMz#qA$9wRIzl*j9@`2g@r za(;!JSJncCnEEOW@kJk^)7L;t{jXu%2b663M?vDWW#>Vu6vO{wCmSg!#o0H6qSI<$`q6D$>iGZhp3t*Vykw$S>s=5f!+YcSg>f# zW?sSyUh-eO2*+k#!uWMS{gXYAe>oG60AlhW?=p2ZrOFJdq|0lw`}-}3YLHjY9+^S* z(Jh~Ovqxr-jNSn5#O0)_Cm8P$3}m5+AbI=NR)YUK>@Y@9ZFTuJ`BKZA^LfBXw)FTL z!vAH&>DkE;erdRiq51nx;qO0*|BICWtAHwW`5th8%aOM_#s8}SkX{=KkM=@75yA` z`ImRxKnT}!bd4S`fHK5UZ z?l^|3v&)`xL(52Vm69P=lo_b^RZ3K6O7t%6|*E$XJFq)3K{#orX#Dtv`b7 zDGp_gj}af%m>P@y1hEoiGtAX^^}?b;uxS7`T6JE+nuT4ekf3|wFcEqXa_gQrOnTx| zg4q*?Nl*MtFnZ!J>4|NBW}lIn`I2vRFWCx?NHvO%B5)~1mlkKnI1agrYLKv5s>ZRqUaIj3610xii;fC^C#H_pi;gZ43>~c( z9o+}0TOj$puIh0xRrd#^RZ85r!j*`75btC?2k|$$k=2Ym80h_=ix0cMTB%QnG~bdv zjKX&ull%N?t7{B)|E3Ge_#F==1*pmc7ptQkC(q-AzDDPsXPaFlpC;dF=c#L>TD20P6h7 z+`5M!@pHQC8z5#6KjP%rR2k9Y@%)FT#Bmo8zLO2ud z1QJaML4q6+xx9k{f^tMTy+TwJ1jHa9C|3|vydWwbc!B478(pt;#SO0eTU}R;=jy8K z|NE`(H<^H+e?FhQsp_h(uCA^=UcY|z9t3J!{PFL#*HuFzs8&%u0Ntw+q*r|g-a>1Q zWzB<6%7D>c28;^7-zus?y7uk%;@dYOQQs@JZyhlF|ItM>fZ1-Z2aK1&Ci-5_!Vn0*X5c3-wFdD%6Dr%rj88id=k=mkW z5cGf=kt$pQQQDLd87%G>oTiM(VDX*cFl9sri-8F-Q%?Fx*E6^d>h;s3MbLEWDyC(j zYl7vKB$oH$`s0?<@WO1?4x3#=hbuLj&*{>G%BwUqD>?y~dvc zuKRueGv+ZzIMn%bZ$+bLwcW61=r(9gIO%N{EaeBFOL8epg=lW{%3)H>v?Q=euhjT@;?gUJiG7a( z*S&I>vFDN6!bXTmg&6HhE}^Nms09S=N;-GnWY?93Nw3^1I9+L&^vXX84p$l`y)qPx zxzgjmXjh8fh#C&_>Pf~jiB(M8;FXCd#iTVb?PhOb6Mj?RMAA{qy>sAKr$B5`a6Qww zsZ%Z$yBCSNqn67kD4RNkzv@z}zzjZ_*?$A0bkxd_>8K*F9xSaL)y>$1R5Pll7$0<@eab3lRv%ZWYI z|8x}^A;rGSTiAl%`@|kx4qPL3F9Emi;nzsBTqZc1Zoe*f`+zVX=2hBDrx`+iB-Z(TOR?fc(& zIOqQ@;`aS-JY0sRrO*hRh`tk?dmp;5f9G*GPP9w9IBpbVA^)9+vb`Hxq_2MO;hbI# zX~TFw`-6wG_!goIYf$MQJ-iEh6L?`Y^gbUs#hEP&9NI;0wBl6-U~<|{e^f{gmRfr&F+Xv$h> zeqCoOvTk-Y^^2x|K@M|J`36)>p5WSOm;7;1@>P#rW!j^MPb=@F`K^yj+;(|Y+T~!n z+b*w4JNz5CZkN|hyX-UV^15l4x)r)zT)f`2%j>3Hnx$Rdkaii;$+XLx(k}CeO1m^k zyW9zUNM$~>eapkuPc!i)3i#Wee!$Z+&~62&bq2JScKcW&x>O;(s3vBOk44ifk*M44 zV^3^(|DrKLBbe=vJu(Q~4n}FW7YVT4L|!vkTHDRd7~B|ZH_69#`xlEZ1wqlJ)yTCZp4!_p*63}O( z59-~Oed1K%F0NDU(=vc-r@BY**3N#W;%g=$hA<3Pg&o$D0Zzq0R9`I|W`+f+V&9+jS&B;c)1h-%+C`D|3K7rXN z?NU^QG+on;qE#grb(?H#ZIce|bT#Y2jEvGX7J^aQWFvqHY?GoUkXzfs%@o`kYY)l8 z_IMO|imE`6_VDDG_Q*Ho3^Tv6_NWJ|*76q7@>Jx|THeyCrJJh-%=Rr>%S*s0T5cvl zEnfprRB&s&rf$X>q#Dm@t7#($Z8cqHG+c3@Fml_AbtgIg!8oW_DYMHcna-Qj^ zBgI`t11)SpJwAgH(lIAU8MR0+D!9!x?*u7mHxjjZC$yUPWmBUXFxwNfdA+@ic}EbS zc`pG_)CiVVM{qN?Ak~;x^3f5Puc%;~G4Cc|(lNg?W&L7)y(?i3{K>*>k1=TtvLV79 z;{A-}`fa(#ZZVW!%`d`Yom}qPe7V^C6%?UuzFeyMC2&Z{;A(0S-hK?|=IUU6A>rzn zghe7D*xQw`NF)pb4hbu%!vc>TG&!?cB|I0Cus|eCjY(J_5-!scc=uw@_1IC9vu~>e z|0uT>b49}Tn1s0^;jor)j1p#InlL$wTP2(llQ2^xd=ir|QzZENNG%#D;XIFBYI2Ti zm2g8$!g(Sg4>`0Y&l3r!Qo>k!JMGXD>mQSIPU~^Z_3`l3k9R{;?aJNkRl3{^`~#c>-3#jH(|L9iTW0xn}_=)2L$IWKsOKf zOMLmHV>R5(^EEtp5>T3H3HAQXwp(2761ADufj$+RM|wv8P2QhgSKw;-n>;&y9=O)> zcfs*IMzpj<%RvL6Wphl+4x;77NYq+(5G{8LPAxl#mOly(Ejx&oqXud%57Hezwe1F@ z-JA|u%UaMG6I^Tgsc2a-$kp;w(dsJTTFd`4TI$C9ryQa93#dcN=_{YvTz!TH`>kO zBl*^j+LY~w`mJ!KYsy!|l=G0LyVfgG*S)~CDPI#@o{n%S@VeOYOOW+G<{M(mp2J{E zIWO|2*pkbDCV3({0~EbO`j!aV3cNgEG^sJadaW;~INl&V^8`9q(jSX}K3Ogd1fxvEQweaUSO=h}0W7UEg`2Snsb;2-e4HtmuPC^~%oGQJ$xLyI zDQlwnb!Q4#yqXq&7|v@*RY}_7|3KbrViwmS3OWvVE&j07s{**T_@jbzB!U$m6DzI< zQ99fqG5-rdQQUd#(G9lk7V|$-+;1QU$GQ#NAPxM*2)DQf5kI)tE$()~xki@~_DBg= zfvg{-?G^HF;KStn(Vf!ZF907W=a24^2Dhs0QCx@IA|7oe>w3E}pfIOh1$|D#BJdV2 zGwdm+Ekc9d(vR@u(We*UXA48{2fU>l#e4CT9p~EYr$MnkM_D|pb(riqC7{c3W#K3N zcf6I2yptFNtK-rg0NY*U1rtyHK1iWt@>ruwI868blN3`oJQs=JydCK2442jEZU*Q< zaA=0h>U57m7%nTtgv&a8ct9IK z9gxbB1KudVHj2N=7osNiBJN}`(w50<`L3s$9n+z@ZTAK3nW;E+oq&T*#+>6Tv{`@n zKQP+u7ANAzP~^1W&?wosd$3 znzb9eoGHG97oeEWVdla$k~!fBQ*_uarr;Y!lJ4IEp}t38FLgcBeGkJ7Y8A!!!pNdl z3;1wvfHw%k07zA9CG}Y}kXk3Hlh{BvQc>T@835M{I658RW`gaMZyU5!dnNq(Jy*a>g7XWEbAldT$ zR0TMC3Q`g$r{GXt;_};>YcFIbB4VH9QdVzZSFmIps)kV$o&XtNJ_NagUp<1Kj-rsy z_tk9hDBrax$~S&G$kBH|4KVAc0Ho9eE6L^;Dw8|l0x={62)9G?2#4459fN~+ET*}G zHeRg)#a9}IBMis^7%B$%L2}6EvpI*6!;&3ankL(F7&$CC&CY>9XzMU?SaP~SgAOBy zC3iAty2Hp}$r%QXI*c5aoN3TJhmpgQvkY3`FmhOOXM+|xj2xES#h|4QBZnnN4LZho zj%su>Xq9sZmMF>H4LaRn$*y*~pi;^DlUn$Okn}!&`i>$6@5K{PniO$YIH2Z8iws>v0%4EcsM}H`op%hb50Q_&(cVN7a#-@yLqI@-Dn<@VW~5$Vj2y;CHhx972+#>xFL5HK(my11hMO9;d;?LTVE2&_ z;QJWK!R!?Tm$wI)BdH7`7V1BlsrSOSLiq$EzR-aC!JZ;P#8Pw+G2dzw5SsWH(i0_e zm~-VB?Vvv+hdH&zBNZcuId$1^HR^vOyvVt_onS$=J__ub{(_||Mh43 zs?GuD+FgQ8SNCQETlZhVsud%LIXAv3*c` zLe4Gk3)*NaMh4vam6RY;gvFgWm}4GJ%z8kyrXU`Mh**K|63}tpyE4# z;Z-AG7`(`$7&$DwdcI&;>If7MuhF!qn%f=NT8-tXI_Mc*cNaPWUfofQ92UM&V+D$l z!@}z|R;c6^iktc*le1K93V>FxvE_=9!@?Vg@%=f)$YJ4)JCMl8VTzH%!kaYOVk<@t z3vbrwPqumpej2_-qjes|$YJ3v8eQ#Cj2sr;s?mCn`VI{izEz`JJ&KXT!nbL(!J`;C zEWAyl`#fq5Oc~y;(MFGA??DgY zY{wJ2kq2z;PCR74I7-5AD%i-1#>@AckboNB`YiLU`vp&glkQ*pZ(+Q~pOT)g-gIV?0S zmrN2lEVO%$%XGV8N}Xec_RNEoVU1M2D-znvfegJjmEoyEcW@qL3fq?h$)HUl zCSI0>4HwW;@VW(asf-I|S)o+Zq(O&~!$N8H-$l)ICxAu^rP~Z>!}e$7uuv!aD?#%t z-w2c(I`cXfI2>SlD9e7sEwFRj0=t;ih;r!Ip{@q)kil?Op{T3|I6eC|qt}Mc`U_=B z3Ua?$^92V+r|4;NsuEBl4 ze@+D$6D4w3%5Aizsz6^&+4epf1x}PV*}0Iy-k6|5i<*F@B63)0u@(aap1s7#v@(}qu2i8}i#y`IVMY!M z&1QRNF>+Yw!e`NXS?|&d=8&N?UoHyG&LQr2b}>!08YUCDkeb7|-*j&ig$BZs+|tr$7X z#XO3ygS(j5>f#Gs!Lgt-w7-Tywz3$LD|GFAn3}}v_?~|jBZq~qXId5`hlN%NI2d?H z=HVXrd6TO%17?K`W-CSx3vD_J?2H@+ zMQ%Q1wA?BhiI%sDyu{`x8@irjhpZGr8<-9|q+%GkjYTnXnC}?aLfa@NQ6h(h_C?{q zdUU#*PQ@G;#qQCgSTL1uB!%wPD-w)bZ()Uffm&LrF50 zWXys0C7WTz%HZ+gP|&^&Sui5OS-52BdLJ@`LiU}2{ffb}I^GX239!QKz_BGe`+4G5 zSZJlEb{c`6sMyk}83gH4OQ!?N)RY@Cywi&Fu#`d!H8piSuoCS+RaT z8%As31ra19{tlH3(@MFd)HW#7Hn7wiC_Zf@3GhP27;0(d8Vf4MP)i%7v2?{4YH1Z3 ziz>!YOB=1RJjEDlX{Tt6&B~RX&!Vx%^k?~X7^dpe``-wG>TNhudcoZ&m!|zY$mv5~ z5lpFZ7wkr-ssOAiW`rMCKkl^qr=s{RGN(;4s{GIP}ZaKQ8m zT|%CE4^5Ij_6wmEsJHq6n~(%w=N(~J=#^g8Nw8A22U@3}b(vse6!!+w&(_#QkxVuxa=rO~Sf?G33G*H$hAn6<(N+ z%$&r+H8P0_?Z*XXAHUU0)lJ+#*hnNdahs`hNuCyl5q7ghFo|Nr6x)Y zx$u%Z0qgur-TaIp7rsJkga(AM!*%n;*wBwL3g5f9A(6EXze+K8D1Rl|ttEtgknF9lzEE>IHhEtln0PA(uH*Gh?5F zG2}95Q6aC6A-4pE?vfsUn7avCj3F0(;sL0c#Tato!*n4u3AZPrHkW$54^F1LBh?Ey zD#I#ohb2{p#blNSdMBY1;r+3uo2*4b;sC~bWlf^zA=l(suJt5w98MN!6Vcmn0TX8@ z#MTmcKe{76BpA!~N-P@-OBR%z$?xjCP59|ZbK%>tlJ!m`-~-oS_Ny-iX20{Rp9P)? zI4T>55v=2qX}V;J1HdmPQI{>H{pm8(eJ)u?KCM97_Wyo7?vd+|ucFK(am@ z%lghAWR=Oy_b$4jGw^T1{|{U-O5?^jt{5qc#aO;AsH8I_5p)bzxF*@$*E^0ELs%x* ziUtZ>A+B0`Zu7;l#Ym&{~$2c}^ zASVR2(^(VW?dZJD)M1jN1lOo6Lb+Wixh!8b`lK^`x-gs@W8hAu7P}qVVY2B5u5)XJ zi(#*#vQpumN-|w8ncXp2P-#xT;+Br@7OelA^A1X`$0Zkg**nZ77s>h|P*jH!{Hhs9 zN3Pg>KVx)ss#}2L8}qnEZRX{suoPzb22uD0Atd-!8m>t;_m2&c5$c?oB>MK#yt9gg z?_^x{*ul+KV}bcOV3>QRFjNZz^{OEQFA%>JKOMQM^!*iMsB`i4!2N0+uG$XVL2YdW z%XcegYG>Yk!u4Q`i#xcjT&8eFsB>!A_`VJ(z*+o>$g}gLMWE^#y&=lfOHAmn@bjyh6WRj=PO6OoaLh=*I3ELjgFbITmo$eKQgc#eLXPNI+rhz z3|B~o5g>9aE}1t_C)9=!>LSDL06ioT>LTOqwRAw)+nXqRdziQ#MyQKa+3mVknIUeE`-hCl z2$gDB$2p8p7rDUB!03bsbxwb>&N8gioiF+Uoo&#wojFmU7utCux7uNZy2u>6zo2s* zMyQKiZ0;0m9Ilum^NlT*J6tkF7D&1BB#Vot$U=j!v>h&+A~nL!>DXacja*{rcyh%E zb&*A)iZ^?ct@kJw%cw3-vc6(PmKr*qWXYE1GTG8(ggS>?5|PX7AEBf~sPnDBeNCkB z7Bo>}Ex>4GTRw`A1y^MI-C#p=r!qoaWXB>9(4N2sT9KU^>!6m>=({u)RE$s;X_${n zd$J?4^%U8yv2+z>Yu&Ez+q2YUEbt=Rs%GD2PCUXA%u8KEw6pT?BpW0J`I8pCF8GgerU{k4=Q5$Yljhog|k2z8N1SR~~! zLS5uhjro*~;UN-RE~oVZc1Y78??HO@<8MG-NFvll`r6k+pu=Z1k$yH83ha2iEr|3t zsB#!BE|PD$WzcyXT{===(2$eE`VX+LK`J8DIgC&j8EDY3)pb8J53;+Un2LMlf%_t_o_GQP8-FUHZzoJR}ildHS zv5owSmJLaSx=5Y)Q@TtJ)4nt4&@r z9Nl10GaRiE9k3zd@U|p!qpj~t3Y*T&)!ZRdFdhv!GV1PZTg_c-Dm&o&7f~LbH174pOR?P6XCxfzZa_c6vI{ z0Sv1Zc!ILf8_j-@FBcL!XQU$~tp`BWuFDOVqpzv@DY%zdqc?wW++-@SwHgbmp3{J> z?}x#T50TfN4(z59V7LwOsG~&dHHxQqe8|(~X72c6#Pg_^p;(tq`Vr%2*rS~U^pCInCBJGR^=XYZ`_+}y`3P@eyxBkD=7?H7qw8DOPMyYrp4atV-A=to^hK>keLJ_ z3QY;>?=0v84sDQ}pgc6)Uo`qrf;z+semF}Pl%RI81^;@npr0kEbrf|}--CY^P*;%p zw+yLKOF(5%=TG>s0+tLYMYI2%T@@8flt&O<{-LM)&l0V~FVPLsyLMtb$RWD9>bJ6fPt%PCN+=;USB~-*DQ9@#8EJBdbAJis~&`6(Y{=+y7Hp^ zSVitla+5jQUt=BAt*Cx9Uzd!HrV0p&7Q6y^zFshSx3gy>m+x%aXk-B-`_)if6VF0I zdiS;S0gJZX&2a-Ob=HEJR(EsUK*|1j_x0>rqV0W<+ogOtB}h!o?0k+JvR#v1px3CD zVoc5K0cQ=;+73o<$sTCxtBQ#Y(pb70MQpIfut|(LHG7DblBYg~KW7isSb<_p&Fo>9 zip)aAn3~yzy3VDFF*UP?YiyKaOwH^O8XF^dQMmxyoGuibPT$8k72xFxHx-BBrUK6#&3&50IjBiAb18ST^ybwvv&u+i zY|hN(8dG}P=`xK4^#;-9a9CJ39c90T>>X)czxs&a^|=2D4^vyVf;K4*Z_{je5}FPoEZZ(R-pLqYQ`Xr5Zq(FD<9Zk z_G%thxoj}7A@ilYQI?OfIWvY9Lz6^_&6!cy12ckRY|e}#oySs)&6zP=A5TzJ`B2On|6l*M9mt3Mtj$&7Vg`M;^r2D4hk~4DzgeOh|_*u@=&*7#Y3-Lu(&f!#X zFdWl_L*yLMj;T}{%$D=)c&N{U&WgkG)Vxg4p8sZmuGBN_fW4sejZvk$@S8ng|n4ZEIY~_4Jcafu2_hA^zIjWg)w5k$aGUsEBHreVJ zR_i&(H1h|x8ikIY^I2ci)|=golU6e_fzNsa&5&3PFg<6FAB8EN2+P?U6r-3Xxx;Oe zZ$hBmNnK@Jx|QuUJBVC~TLDIU3^|1oJ_p=m=#BlzWvNS{dyiqdIg}W=N1-+{9+ReI zL&AlVC?Nu{DtF$Sl+Z4UqsjB9OVeu|au*m0@}x0$;g|g>JzYl1+!~EVrFnBN(fZ}7 zt{zBPe6C0-P=BG!W!)rSq3Dr&xz+=VyT_qykMs{wHuisTO|p}bk}MaB(}RF)VhT#5 z%66uEJeH>OeawpYi6Up>QGkU#`dtlJ@o9dK{;U}7!N-$5@;8FOJ5fIV?@^%Z7F4wp zK^vg;kE(Gfq{l!O#Xf!|?weeUkMC&TWL}_h3V!^Gh51_Gb-BIxA-=?~0T$-=WtFwn z`c0AA>*uq%22(0Kf-L>d5?V0z{3Wxk+yYjW_B{jga|dW0qKbn`b|XHP%-i zrsodRSb^o^6P(=MeIYb42r!sCoQ2Cn-P{qb9(<_lVtn{>tJH%EM|`;_U5Ve6(i~`z z@^ygQ!~e$Zw+GMGJ0RD{85EtY;JCSypQh+2g-!hqpljyb^Ze5IO1+Gpkvqdo02tgU z`k(kumJ8h{nf2Ef*;r>qpiu#8>{oPa9H#(Bl1-7x%z>1j@#Di&6f5_FRR9w^z~j<$ zXK~EZHJzGni5n&=qs%vTW%`9qFhSr6L!$ROLzuiSNT2^*Qru0jDlH-^!WK zgQuvQ<@*4XoP{&V^Z-V?F*!>k7^Bs(F-TtCA0RETz7W`D69iN0r7~cb&lN1FN@2;I zD^>`WF4oDpN?RwYQaFm#-6OO-HQEd8>Q@9SP<)V@bIlim6{ji;2SXc9^}-s@&>lcFX*n*bFP00 zIaDHt>DB)cJ6Y;!&OvJ(Oxc{|J!sRM8wUy&R9DizH=Qk5y5jmYXM@fc)vdHiw-SZ} z^oX1-R|{u>O0NcX+r5Gns$1DEJKhznRNb8kY^UZNqaMHlFQ?∓e(x=!Bd{ljS09 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%0Du^0q=okxfPkxq<$4K+#KKv}7 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

)mv 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�abM0n`xn0WFhHg%}c>O>;~gH+O4g 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

q4_+O$Rr-0X@pB+e_5T(|#~5QD)n zN~(_b5uHdwT)4sZEnGDL3a@?A8JSZ5;fn#7Bm6pVw_@-yd^q5+^lguDV8qi+m;S)Hic%uULhjFVLHZX zm6Pb`j#-F0A5rlCmc^t+#XcgA@deXH(y}cCuv#LrPPAHG@FHfyf*jrGHM6N1)){9B z7Pn5G-dXCCNe$DU?NGWY9D}_vgIl2jDPr)!-sKOO@bY^XZ-#2OwkePj7hl2n`~^*E zXXgeWZ76V~)f_6p&g!dKC1*&imE;^kB0glkW4NCqb6~GZXoK#0k$xrR%k_%bqA0$o zbI1r``{S?NmRn<~UR*^;;axqy8zDe<_Xx5eR-U_OqP@@!TpH?vqcYq={hGYo84!b7 zPKw6m-?H(m$$3Vop0$+bt9;P0Db2ZZ8&D{mn-byw%_(Axly;dRn-&kXY>q5-8K(THbHfIOd#gP#3PWCcfwyO5N~kBE^#R|fNQKoqqP zQw2qAs>uqnf5aN(@Nq8vS!mofmYzwQq5eVy6n1Mnlbf*xFw2~A(Db;ejoE(-+-F&A zj!XF;(IWN!Z!Qrna{7B0nf=~H8j6dYS>_%^yr;s9uu_ zTUl&ww4u2HEH<^qL3?=swWsNugS-k&-x1__ntn6LtI%|8ux+0Npy|DXyo#N{@nzQ* zFmsDb-W6mO*ZP+G=~wPE9-FY?Etd{@o3P=%>g^dbYkMtpk^14^iraavsQ}3ebomnL?*WM!QuMl zYL|%#8?EVUg1nvQ6v6D7v`o2B2+_(*zzGQFBJ9&eqgTYLR&{v!5bG;8~ z?FU`=_3jgN@vQ58uRUz*$oj79ylgK&Yqjh9G51-5pcItbLipA6<3Zj?O+OXnd74h` zt3zBm(x!sH+Wx(+9_Ho=m;6n-u?)oW5g#^$u${i=|Z*smUNMYL!< zwzj>PUbIZ8<>Df*x@KN;pWIzEJ=pCj$g9wFPmniK)9E0uLetBFJWtarg1nKMUK!+7 zXnIwU7s0#2yN@dayle77S!}L;ey4}raj#2dH@sJKj7Y~jwDIodT>4rt0h zvZhSG2N0Z5T=h6tWsVPIttNw7k!|sMSNdD-las0GzXW-sH67c}_RDgb-ap72t?2`T zyvTCUKELflI!BWOepcOIU2<>NQi=K0hQ9CDs@LROe%4%VXsF*%1Pjl&(pnTm&`^xu zk*@j)p^<5NagaA!)60T9Pt%tMd80M`g&;42Dedz_R|YFalkI+1UB)F>23b1;ezISr zUXxvZ)?97qvq4sILv`*aEm{gwYh0PHxKGlGrhgUWjn?$bL7u1ScY?gpn*Ljm7r~VF z`88Jtn9}4^epcPDUGk4XR`CF}p}YJd^_qO%&zh?Z{W-`gZs>dNCoNhEQ?X&T*8c93 zn9_7zkT+V>4MCo#>8>Dew5Gd*ya=YW&jVch1bL%1y)eidq3JV%yb4X98RSJ~ zNXJ^{%3y{xsUTw8P$;yQ5<7`D^dAlxkhCTBw7?RR9ifYXSp&> zp^<4iALLbN`q3b7q^2JW@+vg_RFLOs`Zqz|NKNku@+vg_VvrZXvch|=D+4TR@^Y6| zQf||FDZgI5CKY5%#n0TY3Q39CdDRtp&3%%jHT_PISD|UMzfZm+G(9`W8?EWNLEZ>W z&kOP@G~E#7MUbPg?dQq>Ihy>C%QCTCRHxE}ywN*r|9saLK!5Kd^;-H(zt+Oy&PoWj7r4^b__b=IwIVC!vZ7k?SbBuE@RHwB zL=@I)L6L}{eS@p5MM}x*TILqNr&>*FwGtdaYXwCj@ZISed^~7alR>Sz;<*^4R+Cz-1U+c2phyJ1#je52gN8L3 z)QZr<3$FC9+^1~xF8xlBSE1?Hfg$$b(q52Pq3Jz>ya>0`K3{h2m!Oeok$NqykbuSi z%{8E%mf-robwvjJzG}6Wf>^>P_g}7bsITIMRN)3VG&E369~%f;>;t?Ll6JraOYX2;#KQX|7BO#6^qLYiWf9SUS@+pq-XLT$L*_ z+wZGZYbl5&5I5764)s-xxU?&>*nJXln*KtNSE1=OL7u1StAo4>OwcAtHG5iu|A?j>b11?P+~DOx&{+|!;wi%yPuZ2 zPmV*=M;~k{5Vm&MechMQ?vn*IeOtI-G1Y1h-|&H$qsgDRtdcsL*1N#37g0;J$d!JP z(_eN;1*sG%Ejq};_;DZXwKt#N;Jz#lXBLm;tFGoZ+-C_^;<@@G-Dim++JC64JI*ie z>Si~Pdt6sg-zR*;wW+W&Pa6paymmryTX(p6-*cawJ53M%pe>EO(VDIZ@;pr+9^{SI z^btW`gs8R8pZIu~qshPeS#{;Eg%7#U5=0zrs9qZ?^J~r3h7Jv~im@=-{WQjX5(}EH z3Gym5Jw3>aj85A+$(1RwWTQpuwY2t7Vzjlc!P%kCH9bGbtI+hBLEZ>W-yh_S)bs;E z-Uv-U735WD`spAqGH8Wut}9bw&|2gyzes(vOKJ}#_9Jaji^5FtYD~W@@|&vDWY*7` zlXJ;lKP$qjOSWE292dI!Uv!@)w)}!J{c@Av_*_kX+s~@gVIgV0yt7|BGL3si*1dKV(NBr>y$rW|zrzqv@x^1&c?m zJq&e)KrfoS)X%EB!6mh+5{p6``jB6%UXxe(Sqq~LEi8tp%9YllWT4-4dD=hoit8m^ znTy>gXt=mq39{~v)SCO9qRvW)EV)Rng~i}Ri$o|cS|qadFLfQQaG&wmlE{)*cz@{1 zV5jyXr^(-QKWZTghMHEamc>pl9>oi;+F$(sL4Yp(nSZmTL4YsB^vfETNdnaLw>}&y zxU(LHx{4$~m(2NDbr-v&HdTrM9ataoYt?JA*Uy?8Z74D=?XI*IHL0Zu4RfC*gpD!or<$D?x!d)taFifQ{YgOtwO{EPQ~*mXfS(o(tVF}n zA`6QL87&ea<;Pt|zi^-N*zyP|E4W$Q|ydJKZNKRny-N@+vg_+aPbGrvEd@tI+iCgS^P} zYM&3fG9^UI@4BC!^ovB;fVT32-_z&~ntsu*77-9%bw7P@q(vwPtm%&(>MyxhWBO&Z z%Va@KfBvvgL7NQzY7YmFw0RhwCWpATK$l0m&LorvO&;gYo#*177S`>TJ&F7!sn$UWZX@?v9UF~YV?mkiIzVqL4 zU$h0uOt|Kjx=+cx#C=&E&is=566*NA^MfvJ>UP(Mc3fg<{IsYDRDeGfHxs+cb#|@$ zL_3;%*Uvg5IvHmaxAdSZ{Rj7n-YzbS){D^ik#2sqD5giJToF)W#*}`PbU+=^T9Jiu zlIts|6%lBnMgD~6kB~L>NR<%55-)CLI+fWo{n&(wWuuiCtRl1lgwg7 zjBDBI2I6$;sWU)Nt%4<SGX_Ng)=v} zFPq$F2@ar8wEIxhYw~m-in$7f4yXh@{m?a_MP;$)T@zZbBnSAZt9iUD{Qpt*_JLYf zWxhYC4Wu~G3xg^AwoN%`!hmH5QaE5~n89Hqj}NK)5iA`l<)D?b^93KAYM_Jzlm!`{ zZKZ=D4D{kaieWDfX7JlO$Q}GJy3n!!jGZprJi#xembTnC_3RPPiS`%gpXg`JKm`LxE|14Bz!SLrDdi8}jzWI@8QEY<8HBN50LjUEU6@R7+_rm z;(i9`CfUubA&$hI$<(mTFbr@v=w8;y0M^e8OF#N6XlRdGt^;s7ru(C-6K;p~GP8R! zxk@j?02b0l6tk&eBpS}@F@Wl=fpHmN8V?037(jA)Jca?T!)(-$3|M6VW%wo;0s~0A_sqlq68{lX{K&r*<$sm9H;{e=DMLFYzB^SBnBp9i z*(L61g>6WM)g-=_64sSadvvbY*9f3(g&%8;ko-_O*H4a+d`61ir5)}rP3N1+&3A|I zC+LLKpzxM38Rdc8xF1E;M=q%z#M?=(VghJ=Qx&P4Xw+$D;p|WGe?)cv!WxJ|;>5Jg zhLR~y-#(3z6g!DEnARa`AHk%8R7)^bH*^(g?n>!8bsyyIu;6DZUXcZ@pBW)#(T>6m z(Up{UFsWP}opXDwytxFib zW3s-ll(85KpyYo%D+5+9t}^^P83I>IJo48qa3H+13A>Utm?Ykt$_O0zILcL|3^<8% zQWXLB36v!z?r4R5D-~9gI3QI~S7F+tbH_i!HdI@nm8H$&N4N6HFB+>QEVrI3JkJ&8 z`=EO%TDgngkCLpYfe$3Unojx(ZAjJj?Q@01*HLp3OtSV7f=Setf@gI@|4N?SDWAD< zmvgosPG@W&H}U8W*2r3)Gaa2)%`Uw56MCh~q5BX_x)S9!47upam4T*PFT*$MD7W-7 zfi0wsDCT^=L!wQr9&4%IuW)Sc2dppgP@safB$vly&iNAV$(5|(Zb$ z?K7bNi8Zp8_3NxQdOdYN!&;D$f0?SNs|sz%p{VJg1r2O;U-K|V z`9)*3gf2jjum;ek=A$Tz5RJn*gh{N?cp1vf+;}ZYme{`I-BL;He7ad#fkd9DZeTXa zu3-&vB=#m#!#2ZwiCqeMGi$u8*3Vql1K?NQ5h08Ynre{^-i?XX_ctD8NZyHt7^ z3bK$kqL@vcNTMOE9tEl1^TD_jbRG`{Dkw;Dc|6mq8=ZyQ?J0kEf5~5ecMBaEK+1g> z3kHxpU`z%)T80H=2n-;xNvzP5Zy>JwFD?IIlK4GL@wM+MlpVQoC(28#4g+YPD^e9T ziJwnZ)K!HxbVm9?KiMFtM44`^n0pl{j%RCgQU;xSG@fZde#PFWW zS`4=2E$?aVxnO|4Ql7wA041L}E(2E2Nf~Y=Ltp@je@(2=6NzI|&hQ1ljj0;(# z@vW4oV9{7Tu>Wqu&2J90;pR72Va&qKH}5#hZMcQa2{zoq=Id>B3r)D0Hr&!?KO1go zv&UAq)P(zj4Y#8CP8)7T^NSZ zSohCC%j?)afBRY=>u15SxSwCW)U-(Zg5Ddr{Wr#&swUvpQb`RmDeB6*Y;MrYh>HLK|{O=jQ|vG*HQX z&3S~7Uo=(^;#oW=0uG=}%?nTzAsSa<+>#@Ig|ag@?nTKG+YiQvDv84-tJXG@IAv|l zFF@=D^gGrNN8;3U@(tUJMm5_Y&=XkW2V?7Js_X_YXlRdGt^;s7ru(C-6K;p~GUV|E za+O~7>NYH-jVNYQi%GPc)uSNQ`x7wk2jd^|P@sZ>B$vlyC}rJhdC_?puqBrNRfc|K2n-U`-{l!vI?=5ck!1Imy1p8sbPil1vTT48s7AfZoR%8Nm9PVHr+;1r6;{ z%XI)w$8>*mb;9kiUS@XBAy?^T7{Ef>h+;N1o zSc=<2DS!AiXyhl~+b8}nGJuq)F%}FUdEEIKuqBrNRfY{j4GbW09kD`B{snR8`&ypC zB=LDn@s;;=q&hLB474y1Wf-f&0IK4#R7FkV_fr*hRiOXxvc#4Fx+;ks2DqgHaT#C|$v(&$;z(SV zOby!%!vNQT?qQ7#VExRnYzHrBXpdU118_Q~`=hH9Zin?Uv-={sN-x6z7Scu(v#FOz zbb!@k0M+~UbnBhhkhk(spn?G;m&aomU?OfGXDtR>@`ejqS2r-g(!Nq2!B_w#pFJr9 zRxiFX+(CxG01}rFEA(Wea=pLhA56z04q%N8a2Co-B~Ie2Iqo3JU`lgHL4{qK3ad%{ zYAUR*!n8-{iv2n0Hzlu5iJwcRh7DvU;{wp7m$@%jRUq!D{t1u{E7Yt*(zyhaj^;nu znU1~;G~IfcYyTIVed%R}$3ohOVh-aiBzhaGH$19$Xu6}$@VteG0u{p}xjY^-JX6Sf zDQloaiQiAAD^4J|Dsg@r;wI4FVvUq&{mcPOB=pBwfUGXGGP~Pt=s@wPu z=oeVy{98X$<*kI)2!$K3EIKx42sDzK#xQW!%kc3sa+O|&)h(orC}vZ4km!H1daSN` z_kwX>ihj#OfeKcaTpo{M^+E9AIjjX&O1^bUt99Wc(Gz{8%*R*&B_C#65m;@vGW;zW z0;x(oo>-wL65o_^hL@eQP$reQCgK~Ww(#bbt!O@D!&!2lX2ad*g}blmY}S(dcpGlL7jAuX@PBI^lv*8*WqtEq^!@yF z8G`X3Nj3P1ByV;)bjyQ+KsDFL2HfjG7jxQ9o_5G8&U(a`CXoxhYM z{D_pju8~%21Fu&*<50c0qYO0NdYJ(qM%vQLjIM>W5yhO(sU&&_t4H;=UQ>)9ThqOD zKI8wChXNH}4J7MsJFTz~vy0a=b(+3#56dgik}d!;jWs|dK9EdTREN@eQ}_hvds*Wr z1?y)DxRiOnnl%(4@u$gjg|6hM@plUNd(hu!jRjagQ@}g0h-1QK)v1@mYC8KKygrZ$ z>Xg?DrKx(_@3*;*2FU{dXOM7V`Uqa`!hC$N5UMHTYYTgLa%`XL`}dTOTaxxC&@lld zxjZowTaf?76X#0*9gj3N&VBuQ1rmv?TA}{J+nJm}@%cyc^%u9$@w>-TK8>*eN`8MW z10F5Ia597sN)q2dtm4xS;>jsz_@FcvWon801mY7=RdAK~6sGt==~pPjDb4u^q^*xl zh1DcpniAHPP8joj_1Jllf z`%siDvHjxgBo05#CU&-uO-(29rsikJF(5TQe0zHsN|xAuC;JI@ygePdx&8Zi^!l{b z@Y8GsFZ&6qyl%qlFhi$wHFO(-NdZ))(@)*OGSGDE#R-(@-%h!umtjH+X(Ng`pW!4r zi`Dyrt9t)BHOTpDdL|DAD!!UZE|15Y^AqHKmerx2A?)iE)&P;Hs}nxe>Gr6%&Ii4T z1o$Lt{7Pm0%o%mEwI*x9R+0zOm-sDlDT6Q;K*?)nBwAbAw`_DvQiku7A<&w{-Iuf= zfpZjU5}|@g;^Y)PFx?!K8**be%3@ZB!&JrMR7FkV%c+XGs?dg1C0Ako@G1~s30QA6q@1 zu1>fe*2~Q9@!X}-%P@e2v=PN@>O>L^Vf7e5^_~yLWq|W|C{V!wlFQ>U3@{6~+f)AV z55thZetGK>M+T5`AI5?KBoCm+5m>$W%CLZ_fdM2oi4}VC4a9YPgDIFKeh*W8D|rfK zM{eAS@)E1V0IFg|s-hcs4gM?K}|3P?RjOWq=WcQDTPyCRZRX0}LVADXbxm#AY%zY%>f4OawibH8Ozp zGsCh9yr7{yYPk-;>6q@1u1>fe*2~Q9gXAi`3A(xjY`j0E1}Hxva%tOPa2RK7_FVO5QXxql)UqSB5cU2n--`6tO~2E=HV5 zs9=(KRf--y1AGo;ac*3Kau=(^0IK4`R7FkVdr}p3RiOZgAnp_8DJBh zrvi?{ej17*MB@UCt8(OnD4TQRR+KEUWq@ZZi5&(wRDrk*u!UqlV-0a6j!Gxru+1QL zc`^1Apd&Rg{25^DXNF}4ctJyZ)N&nw(=pv2U7c_{te2VHkCUtPG7MlLZA3AfnnR*) zR*wNx?>%5#2KYJ;1u7Uoa(O(40Up8acPW2&f60BX%m!OZe~bkKNWS*N8L(`0OHzj2 zWC#o(ap$ZSB=qDM!VV-p(sK$jv?F()@b}V%G}&|3rd#QGQgrrVuu0NR3I(` zbd&66)(}VH&SYxXW*7#z8+0#g{0w0I%&_#Mzk-JLsO35Ur(?Q5x;o)@ST8fXCzGr6 zG7MlLZA3Af8b+ewtR4fX-WnK}0jBX#pn?G;m&aom;5yvCmhyM^m;CLI^ojqAp8=$7 z#8@zZ&1a2s{W%mxh{oL*`=w2W5Bw*i zbmYbnC|P350OKl&9R}#CKwJhGPO>vuLmY`qlBr>vVHltjbdxnQfb}!OvH`rHp*?E3 z4#4S{?vJicxEM7Dpn?G;m&aom z;8ff`$XX1xv%a#wEbL0QY{Fo3G~P^zLP@dK%fx~kBIocr`+0tgz1eGVby=Q6-nJVyhL!~Sj* zMTo}b7&qj|$5DE7{u}7c{g-E!P1!9n<~M)d{!5dYRe1iCm?ZVE_whBZ}G7JQ6Kn^%y|)eh-ZM z8Q|MI6sTYT$>s4F26zIuKc)QP4}l}U{$tr-OBsZ*U;xS2emnzK89*5h5H&D>#N8il zfkRJ@C+t8%1(U=#r|8970Lu8>I00n}tHS`QqJK(RllXEn)m4QyU`Zvh!vO0l5ce}cH_2{h4RIvy zOs0lyhGBraLHDvo2C#l+So+alK|_1gavgxvG2I_sop3v>mzmv@$yIt82C$GeqL@t$ zBhheHj{#I~4UEeG(|9OQd<(!<9*<#w>wtWXwHR#4-?}CnY$@w87C^}Z=48Ms11Q6C zG8AtChzp1nda@dE-L;7nBz_N5>t_KdJ96Vrl$Tf?22d6Er7CI?m!&G|szMub?$e)v z7Bmj~0~qBOjn#t~28 zGQbd$ox&R8NNgrk!#2Y(z(mk zG7MlLZA3AfdYD98SUm<%y{~|A8Q^6e3REzF3MMp1-l>|n=Z($2$| z`-@Pf=Eg3REU{&Pxs}8Y11zaPTn3myvddXR9EqEgsbQO87+^l=b*%9-fc5LFUZvgO z1r6;{%XI)w$8>*mb;9kiUS@U=lB@JG3}7K`L@}E>Orm~VUXKA(@7d|rJKqUBi-!Ug z3?R8Yo@v!X?ZoZol)t;b_07LOmVuu07RUj?{^ds2`>6kku&P}F}Vk7c{g-E!P1!9n<~M)d{!5dQGoh+tuVMy$l0bNE=birq+^Z1FOdX zs`q&??q`7KcqmZ80Fuk&F$~b>hSt{4WGx0;@_B!o4YrgEF&03{Yq~RF^(d8LI2i&1 zNbDe1=*fE#rxGfdB-T>&;w=DWUT$onEM|2WKvj%QRn#P&k*cVx3T?=_Pv0bfpmErz z5JG+~1FXSw5a2lMgHaSA8oMzr&ylN9*5$@cC|P3509z}G9R}E2fw&B?fn*P{hBy*C z(#bb$GYkXlBEZ1Z#NsW$G|>#pBp`x@_Ne7L0Hq-1E}6Rz_<*sjE4fnw*YMA@fZeJ59ArvVz4E@{ON44rM!l*07~w?Ky{$?p-5+}QFDCW*(T=z#%FLm8PHN1=>obr?WZ>`7JBB>pm0QCAh( zkaM4=gBCOn`(cdoa~Yru&?BsI*dIkvglHVjAxvV8#>-G<=EiGLvc#4FZmA@87+_@u z;xfQ&l3l|Z;z;aGriN{XVSuHeH?zji0M^e8%K`9$hW4oCIsm6*x<9%);dWRrGrPxg zmr5_g02b0l6tk%lNi>AjV*u5AJ{Xq)&f}p#1p`PfkH;{;EZlBS`NO{pK>qs8*)ih6O|o3?Q*dtk9EhAg;S5F@VJHVQT#>0A)vR+==oMtHS`QVnwQ= zCh_yBin^-MhMfC!ENDUFu&>1^zi6x;!~j6ovBtT+0Ywp_aXZEXIr4ZmH!$tI_*no- zme?}D2*N0_!vK>j5SIalkn9xJ5JzG&nHshkh5;smp352;!1|eCSp{Cu&>pp12jFx} z_vfip<8k1R4_@brRc?50Lr}F*hE>( z>M($+xG+^wlXzCDqOK~mA?H5*0|5k$!+r@Nb(Pu%K*!GC{TP0z*ZiQ zVF3M!!e>(c@D_ml^89SDrM!kQz6Bt6{aprZiRFKlVLMTaw*bUm{6kNEk9g#^mS-?Y zJT65q-U3iY=EhMd<5?XBP!)Sp6*Y;!OjXoXg*N2er|F;tjl+Hzqx@V3=mPWzYaI4R zQ4}E>hjR#%SflYWl$p8lT9hoYWq?~Mi5&)5S%J6=Fq>r8u!cAidy}bQn_(DWDd^3t z@iTz+GsAKKyr7{yYPk-;>6q@1u1>fe*2~Q9@!X}-%P@e2v=PN@>O>L^Vf7e5^_~yL zWq|W|C{TP0z*ZiQVSrgcZf7k9Tk_XG(tT~%m9&V4!-w4ia= z*J6}kG?quuk_G^}jy2Bp4Je8bjoUFE$dSjhxq)ft#ajSMme?}D2*N0_!vK>j5SIal zkn9xJ5JzG&nHshkh5;smp352;!1|eCSp{Cu&>pp12jFx}_vfip<8V~G7~XSPi@}!sfxmBE-N*n^Ix!YN z$!iv5!0N?UhB0I)-U1MZ6Dv&YMTk=g6-*LqDSGi1fHE&PHc=L{It-vHE=*O_B%YP3 zsH+NX$hlAdKmbAGuwOz5`MC_R4$n6Mj>A3-MG>O0iE(+3T#d3WH*P}75?co7tt56B zU|$8|GQb9sJ;WN~NbE=_->}Uv46us;15*==w*b>bGc1#U2pZa>mg@kVj_LmB>V(^2 zz0B-hMy}G!Fo1=$5yfn(L895L9s{V}JHWUMu#ATS#kT-#SP$eG)?%CZx zskyNWB};4>U~VO`!vISv5SIaFknD2S5J%$XWNO%E7zUURdL3*03}F4tuber)Q|E!22j0cgK-()EFKC}Fo5Lpcnkw{ z;&yY&AATbUdD-pRU`ttnv0wnnhZbePDg!7(H&FuvNSsToFtJ}m>>*SzNxUaT55xW_ z%C_9ti?V~&VE|RJFjY~Lcw?%ft}3)4=RW;`0D{J0zl#v^a~a?ep0fbQVZRzh5u$Mm z#@#vcAj*;4IDj2zi7f*R#Y2f51{ha?xD3#bWGAF!?vyw;nHshkh5<%{p2->+!1|eC zSq@&%&>pp12jFx}_eWPJ+z#tyW_LBYN-x6z7Scu(v#GTt+Q8~Dfa-l7jLQJe@lc?G z0VJ2lV;G>%;?~5T$yyAy_1(b$b~d5&C-vMx7nLdg@dLI3dCiA4J3PrHN=tFkxss0n_(DW7Xb#Q zCKhi2rio@)CIJyNv_~!10XQAg{n6D4x5Ijw*}aThrI%p<3uz;Y*;Io>vspa`P`!76 zaT#D44+V;E0ocmpF$}OC$TO_PU`zh((rmD$?8jIDB~Sih2COoGGHfS9@fLu%g;=2{ zFCy++*76S~iLYR4y#`7JBBtDs{sH+NX$hlAN2Q6qE_E#~= z&t-tAfWFHbhy7s`MTo`@c04BSym$*hnVK8BP_o390p?Z`I}EU-0&y8&2FWgG4RIuH zPNs%!hGBsDpx3d+&j8l1vwD?wgBLWkM=jR@I33gd8C>2D>t$y5Ah}8}!vGf2MijHD z!zAiQc^(6(-m}5D3~&|?1&VJ0*vjLXRz1{CAUCrXgDrX4ms;r7zY9QF!CC+%AG#w0 z9xX#R8H%?6#JR)@6Z=KP9zq3^#CuZo;w=DWTW;({*}>{CfcCjCRZ)|8W2&OADzqWz zKK+3Jg2rLLixBd28Q>6}vjE3ozZyjmqHznx-8u3g%8}eSfE{OvEdvb2Lx~**7*~O~ z4A75cC!}NUlsGq;8nzjR0Y-zK$r>5J`k7%_4qnjE9<^Kt;B-v)M^`7@4(m0&dTm#e ztMoDqU?FWpF`HUTq7AGb1E}8T!MF_Y91jJGZvoiK<1q}-=gY0FpUGMbw&e4^k`1<$ z3o#Zz$!nHp!0J&d!*DVbZvluM#0oumFXB`}1(U>Die9`0pv=pSO_ar~4g;u)v8jrh z#4}PAbycAaIrr(C1Q0Y1`xHXR&t-r$cn$&_hkY=LB1B_1#^pJ3HOji&xCtdoY#Cr{ zC9%T*dn*u^0XC5AA=VH_Vn;gphHZvnfL#O_n3`C;1(+t9VVML((9j;WTnFHEO!r4u zC)^I}WoGv>a+O~7>dm&0HlmnKHApm@)nfqFdj}Yo0haMlp!gPmtvnvX0PBG~!&(fs z0Ci3?O;HT^X>-0Lrj{sDS|_Hi;E_@(sjwD-#1q{2r#( zZv>(2$c;NuUSf3^Kvk?rRn#PYK2=dy721${8%7c{g-E!P1!9n<}JD%CjMdYRdM5OnEf7{Ef>h+;PNFp0LXdJLd?UjgGX zz{@-os9*re7{u_N%UTS!!te+W{8Q=vC?NQ5h08Ynre{^-i?XX^Ec0W$8(#tS_g|rdHY-$dP zx>-F2P`&qnaT(z2JQS#40LkU?7zTI*x8J4w;r}lHx$oDr!Ish=W5EEDul+^_thQSj zb}MT5{|i9ed3OsOdU6b52NEinB)&OC4-D{bl<~Q70?HIthXGW@YpIHw#NVbW>Z(E; za_-YdKnog&{Y0vlpUVK(;`sz?9QLPB6d@YN5N-x*G=3aqZf?8U{NKp!vJe4 z5SIbENp>@9h$C@lGBs>73mzmv@ z$yIt82C$GeqL@t$BhheHj{#I~4UEeG(|9OQ!2puW<1q|y9d2Js`NLldMgI0T`^5jn zp9M(Sh_PS*$wTkWfGx57uQDtrYWNHwaS5?PPrie=`JR?%FiHFgruY`{0?JFdaUaTI zR)+yp#eJ!Yn#8ZBD(b328*=W`n?MU1hy7uU@{7ju2wKumK=WDSTz?Kl5u$N7#(rs& z;RFB4C>^Vut~`DiD_ehLh|})(}VHl4NSwW*7$O1l?qf3}F4tuxtP? zXlRdGt^;s7ru(C-6K;p~GPC=g514#KO#sVmL>#7V`z4*#7i41`OBu*e!=*fo>=MpNI zBz`JI51#?PjB;0Q>_J(}>M($+_)w~%Ch-HQin^-MhMfEKV*&^ohkXtqGt$y5CUTWth5;<3jVNYQ^GLLS)nfqF z`#mu3XMk_>P@sYVB$vly7~l!q{*>~E|Gxm_H+(A_Y$+#UEEquYEvqwNl>wCD08s-2 zNZfZ{3mkg#M#6RwDwrg`Jw*=;Z~@Ac+&C4b!Rj!8s_36m)+GKhnd+)S8*=W`CqWAu zhkY2;%g<$iTkzb)8i)M_6h(-}Nraot8jUxhG;`x3lq|7jfR&ZR4g+keK-|v&3rKc5 zYltIpUotgpGYkW)2fdRuGJy3n!!m^a3L4s@mg@kVj_LmB>V(^2z0B;sgIuMTVE_wh zBZ}G7ND_@<^%y|)UIoTwfGc?@P{9C_%i}Q&Fdw)3Q~vJ$l7D!AHrP@g!&oqY?(RcvkkhID07Vr*~QMqv(N|x9%z~o9| zhXJmwKwJhGL$Y&OLmY`c$<(mTFbpsY^b*#{0M^e8%NFp0hW4oCIsm6*x<9%);dWRr zGrPYcSLtOKz(U%HVm7sdM7vo%22j0kPJ`yW1-yZW0u>A(xjY`j0HblcoV6Hi$=Cm5 zHrP@=jj;eqes*03tX_O&m_dfX01~GXEA-?Oh)qHTlf=)a=;1TKy(nvQ<2sawSseyY z6(3Di)FfV-s;H|9ZOFM#KPP~oaoFb(LVhj-Jd5W;7}#8!vI5xQ`Y9Z1?&d&JJt|K;?#8V4ciRE0E0kJV2uo5{mig*gBLWkM=jR@ zI33gd(bWmJ!+M$7{Q|j4FT(&9(nb`ssl_B(&gwCM>ir2A_cOo`c_>i90Fuk&F$}N^ zw-eU4Cbt-D$#4C3HrP^5$5;R*FZxaftTKQy^dm!H0EtJEY5HFhb`+sPn1;qs^oZOFM#pCN#tao9%^LVhj-EW&d?YaI4hQ4}E> zXArKNH5$KwvLrXIM9C6c23S)`>@dLA3dH>ku$*LHV-0a69!aK#ZH8fhM?mjmjSOJ@ zI;&S{IQJogFU~ml#?(P3?TVmAIN}5%dmkAfdM2w@ZAmjLpPBy}}-Z7v$C zo~lNr1klE&>QRJfR7EN$8XqLbX4YujY7=j4Rug}Ul5b9XH{)|QaVD}l;nJ$l6D#ohorpbz3MPs7r09Y2A4S=g8+%c9 zum|8E|D6DW2By2Oc`qU47md{tj^OzbzyY+WIR`}%qOlj_ z?i_g#c%T_QPQZ9!l)+=Y$Hx{czZiWGAF!?vyw;nHshkW{5|Fp2-?N99lm! z#LK}88rq|l>j0dN>Hg^Igxg`g%!OG^uF|VsZK#E`5yfn3Er~X;dep3Xp9kZ9IDC$W z0u|IOxjY_2&3(S#+Si$^#b8T*&x5UJkpZMk#8?0&uY4#2R?kTphLa(%ro=zJ1el7#_ z;CU?IIP52)C_*&O#ke3x-igwa8`q#@i7f+ct|WFCU}pv5GQcX5-On20NF10>zG0hT z7~ly49AS+NVExRnj3JAlp*?E34#4S{?vJicxEV~4RPWj8);q6fXYo*=f&nC#$72|v6Ss>~{_g&gYo%?FqI}s&I02}Ed7=(z4d5*OOjtrV&->u8-CLq zzZGrxUF`U+H-67h-*0}XLs<6Mt*@YDl|E)XPv6|~$UL7&p1quNpui_?eGVlH_k;;| zJB29hF?~k&Nd8e-+{Rm_{1jtgE{WTc=@N-qbsKM$qWu*)hEZHr@y1)FsPY0R`QOT| zZM?Pbk6VW@DBYoD8*d$qlI?fn|J8mcaJ1q3CK6|+tzGedcL2}iXD3Am5fVtu&&1n) z^@9IL{n|+Vq5BJOmGXnMsc8~#FKb(PtCW>#r}b|js>bjIL1JF6)53-Aws4`>!i8Q7 z7q-3Mv`Asz~yD9UVZM0&c7jB^! zZlMX+?~zu56IjE1O8l#2YK-GrY66|_u#1W2C*JLPBLT*xK%El5kxX5ReL=eVof6A* zU41J5yPh;Rq;#DUwI-Q-0 zSkK{PLxmDWv*9y^&RzX;-aG3ASeRP$$%TEbpSdR!+0@0U8|DX17Gi(6gZC$n9%Yt+ zm?-ms+|OEk*GKXXA8oZIeuG%bV;Bpd>vQb%w;S``~p6*Y<9N>$WVg*N0g{cWHH4PQ_1Yi`3Rzi6zMFapq0 z)&Sbn{4$CnMB@RBL((RT-yudBl^e&QWQpy!hbC7NJ0p2*1>$~tXbj2DWess8_9Roo zHltBJiCLhRu*Po>SwAy4Tfhq%+M|~10Gy8L{=AxMoNm3$h4~fe(#w2%$U@qPVm7sd zM7vqNZx5;7H>W{!zCH8?9tu>@q2%&-4DpY~Z3b&G*ph$wSgUoB0i^80SO6s-`*;Sd zwp$tgh794`B@!l&ygKbZ%oQ42@ru)H{Y>kXMW-Bg zrlXX1FzEpQgZ-;#WuWQS%M8aH$}YXkh*?M*QOx;VN1~fpy%AHrze{(>8L>S)6sQ<6 z$>s5wb3PHbx1_7sDe=Z+y5bJR|5=)<7xo+;eT+2{r}Z-jFo^LyhqV}8$xD9L$`WRL zX*hs}U z)*`By@I_dn>T+I|zHO`8=v8HZg>NAFE>0zw+x6EKi2LE;1wb#CaU@=n8hVB8@fFo$ zdL!rySmO*>KU3uf@PdY}>n4>4X9!5wQa3VOI_qWb;p5~gy-Z*WX(Ni+)HV`rXZ4s( z^&Xez*I~B4JQS#4Hp%7j7-l;aw-2%wd?ESD|F_k;_~AjyM==&a$y<9fV71-KFo_I- zSR_s$R_Mux5$6&rm?VBGMGv1RzKn8LZtOu>%jyt|s`yZE8Z3pxWYltIpLOS_|ZHD=f)Ccq^*2ni90Fuk&nN~g2 z`MBMm@`rCBBLDE`Ep+Q!h$xS-77QSH#8VmYXc<-!H86n09%6-_{0Oo47cKu_lK2Zu zt#2Ws9L|kLP)?){hXJ(Djj4*7#BZf4>Z(E;a_-aHKnog&eH%vkMPv0KMgUsM8t3}U zD2fn`2QUsvn=HPCh%zcSjzh^3TLze1N$fDdwH1iV0AomYE^CM*u_u`twi$*2W`SP9 z8X3U)nPJ%iUeM4UwOj|_bWHc>)l}nj>ovXFtzUsIy$l0bNE=birgo5MH><}0s`t%l z(44n`H}FuPf&nC#$72{^G;Wu(7K1JMWB*_4SRw;Rxej9il)QC&2CN>XGRz=DU;v4e zh!rOGU?5iYp7+@fA%G$iQ06@>MhBy)@ zq?2#hW*7$O19}u|WB}`DhGjN*K|_1gavgxvG2I_sop3v>mzmw0$W?mPt2f(1+K6H{ zHIGCKSUm<%z25`leg^n94+V;E0ocmpF%0kokUy~&gDv?Dzsv?(%1IatpyXSAl>w{g zqznhhP`m{o?t8ig4n27zVLJ#FOcLLoq8D!gC{uFdRFnp*!vLzHe@a=C_{U_bs|sz% zxlf-2EodC}VN@?amjQ0Ua~EqI_7_kTAsQzUZZ>N)-h|T3jf+sS#Fhb8RuVf5u&DxZ zKLacv+3l<$j>LV*)UeGk46q*bPS(f(*3S&f5c(@X_cG=|k<0M&aH7?%OAz%`~{}gTL8-8+;{}#MCx!DKvir^Rn#PY zD^*cf721$3)s|PUx&{Ebo*I!0aglIf~aY))^@fLtGDmRWp$r4)z zm|RKhFu=7Hh|2(DNOmr3h$FElnHshkh5=@QUcwp~!1|eC*#chB&>pp12jFx}_vh7A z<8}of)ut@s(i)8H%?6#Hqvz6Z;9oCZU2!;%8Iz@EPDP3 z4g;u)kESYW5-&|v)K!Hxz#19A`k7(r1}|u6 zk6Nw+a5|>@qpK5chxIbE`vr29UWNfIq>U(MQ;SKooYi9h)%z1L?q`4>@=&0H0VJ2l zV;Eo;ZYS(&wW%0v$!~qGWsBbjka9Z40w{UWuQOmvEdQ$v{dTo%fdM2QNv7$4N!U?@ z3T={jc8VSt;1ZNZZk&lSht**KRdG^ES(EsBQisci90Fuk&F$}O2w}(>x@W;rIpZraq_`k>iQl7?GFo5K7FJ!=$SpHWT zHV`#1fW&pg3O)H3#GTK#JcCK%^O)jW!0SkLVoDikVIaydR)+yp#bc?8n#Av?D(b32 z8*=W`yFd#XhkXY|`9))S1TASCpu1V)T;GeL2+`P&9S=`C4{rhIpiIb(lTot7mI1md zi5&*Gr2=soU=qnb$Qt5ET$fA@+YG}1*MaU~jSOJ@%&=?+FKB3wTCM|dI;Q)hs}pXA z^)j>jBDqR0!vGf2MijHDmq>Jg)nfqF`}TC}owtCu@=&0H0VJ2lV;Eo}Zr89DgDv^C ze`{Uc$N*9nVl04?_wLSs)r+qTv&j$`K;ledg`WH!;u1mylf*kx^f2rXpgf!#H=}H0 zbr?WZd^%N8llZYzMO{^BL(YBr4FLp=!@igh@^cwrFP@!%`*Wz)C#(0FJ}{Iuu2S z#@QGbM?-o{VW)l0dC`= zKm`LxE|14Bz}>k0EaeY>a|-#n-(`a>We>)J0VGd;DFap+KpD0WH86n0&BO{l`8DFc zy)FM>lK2Xy_!cmjRKrrrKnooxBUv2=P!&(7DrypcnyRR)3T?=_PwxXQXdL#JFv`zm zfXRT?v&Lb607Vg^aR@selXf270xm+Cnj5=Nvc#4FuB{|?7+_Ha;xfPtl3mUk;z-<_ zOby!%!vOO^uVal2VExRn>;^ArXpdU118_Q~`=hH9Zin?UvwM(SrI%p<3uz;Y+0A(xjY`j0G+tqobq?~m%QwMWrHnc1;&B_Bp=$B0jmt4 z4Bcc13?OkXu|iM2h}c7@V3K%GiXMjjQIu`Du@_|rtHS`QVqvPHCh^8pMO{^BL(YBr z0|5k$!+sYbCR6iXueg7L2=db7yI}9+c0&y9j zAIVNg$J{A#ZZb7&GYkWa20fECGJy3n!?GN_prJi#xembTnC_3RPPiS`%gpX-a+O|& z0W74AC}vY@Nwk60V*u6rJQ(*gz;iqls9*re=(E2yv1hUtgDv^Im$Si^av{b7 zD0$8AGhmeglwmj-0s~0wAXezfdl9D+DwrhJQuM$8H=xYRjZKurtPTUHim|DRn#40w z6?Iji4LSGen*><_=M`A}h`G#$VVSrr(7?_$E{(!vo>#Sa-N#F$y?NQ5h08Ynre{^-i z?XX^Eb}u7W>17zeLfVL8Hq{`}Y*vo}RPP;NTn1RiLxBngkX#(vZ+v<%zH5EwvWFR?;Tevf$Mm6m@nNjxq^4-9Y`%E;U}3S~U2 z!vNano>WCm;xAJbbycAaIrnKgXhGw!AI2y@mjSu}J;EA?{ZSM}h{oX@!X(ycybNV# zZoC#HOKch7mP%rW0ajKZE(6Ra*)^;oj>O(%YS?BN23QJuGi&?|VExRn8~`t9XpdU1 z18_Q~`=hH9Zin@nUcI)*bC*gl!vGf2MijHD6G=3L)nfqFdp;PK0nX#0Km`LxE|14B zz%1N8obq?~mwZ)e8zlK}O4~`2KU&&OmHf%lcDm$GmA286Z!K+OCGYz8L@4uLC*?Ve zfqBMC{&i^^E&2JI@UuvO-Q3A*tG#Z%0(l;90FTn5y{}a|TOT!?q<(PVK&8 zCCB=Ysohto-3MrU-#%Boht4Z)Fjdp-C(S9ZwVoW)=iNP$uf`VsfX4lZZ%ZZACB75W zv3=(KS&x)YCtO3~l4P1G@zG?uLgM0des#4+dktT3wA~-2ZPz6ZOWW?c6j3J<93}Q7 zRO1(jZ%pGcQ{vf}!j_Kfk)p#4e}MGX9w}FYERId`SCZ|&^hjQvY-4&P|1sId_DEKz zj_LEBo@%SAH!)1f-kU$h5nhvyvQy&RWSS;XS24O)&o>%%!aA^MR8`rC?Y+5Lo;MZM z87?NJ>Q~)XxNV-BX8*!;K^n8nfHkU>I)D4+r+6Jnfja-)5W4y7u)E?FZozAPte^Rs zuPoWg@48uERrG}<9P+N4_0=RhWc&3&U#}D{d?~B?1yl8iPi9=?74!XPpf=(-A8&_| z>U37`GnOj;vm>n<+|O9Y@=&1SGnQl>k@MFq-$W~iq_54L5^qeVt``y2z`lLDs>ksz zARlE74PV&T`k9L|nz|>lhL0rG&=yjwLOSto96gGKm?*RGzV7HzEW|{akN4uEN3jqS zWhvedu@-N&lKb}U8xJ7lc%{Z*t4t`g^@D#B~>Cr~z*xT6*JSSqY0@wX{qT?w^E=lyCp=xOP^!e7g2pvW%_ zxBX*Uqw&;6188IODU6B`jkgo-zB0$g=7T7kbK_Q&Eb+!>b?;8%O(}7gt4#0J=SDob#z*KLOsBiVWW>wSMLRc7Z?P*cQVSY$f?E$7O6K zmN^YEr(qMJI^s9=K zQp#EqNgZyls?dg<3;nkQ5H!-&X@roU%VAACf5RF;o0>17C_*$&CERS*XuJufnHv|O zWQi@6^i&c%Jh8a~alfm#fMmC`hBy-UB~!yTqfxyQ>p|~ijo;O?e&(_cp}&HL_Ne7L z0H18O$LfVL8HZ_t&V^}>3QoUD!aX)ch$wPq(3X)tNkD;LX zxZR)fhkxmd{QW=623yJ_7z+lFJhXoXthQSjRuMHYfW+m*3O#ucadW>`s9=)#6HM{5 z&I>3n<;Hy|hgls4P!$_f6*Y-pPgT@ag*N2er@sI#XdL!OG0HC*s|V2m=rgQwt{0;y zLNxBf*e`7|aK*_e9l3D?N|x9%z=TR-hXH0*AT9$8C)t^-A&$f)$<(mTFbvQMy2%pp12jFx}_eWPJ+z#tyX7_P&m0pGcEToMnW>ecpw4K#s0M&b38Z_ra zNM9ZbR4{<#@^}mboQm5AS&PAzeCg}6!Im-$V*!-B>G%v-z4*#7i41`OB#t3g=*eY> zGYJ(;60b_p!)Jicp)AggOHl4&br?WZd?-~>lXzaLqOK~mA?H5*69EK`!+td(6q@1u1>fe*2~Q9$H`TC83wSBHlmnK%^^`Y ztH%JU_Z~3rXMnHsP@sYVB$vly7~m1yewXrh_m}*~6SBdU(q}-+STKO(t`jq0l>wAt zHyHv0NZd|>(369RM+hHG5|2yKi=XYIjLeOrP{y-544^7rOI6e)K9{Pfs|sz%xleV_ zg2rKg9aYQEWq_G@{+Klm`!*Csh{jQbo5UK8m!Ztejn|@Ni7f*(D~TNj=&3+l2AEB< zYgj`ZiM`3xu+117ze zLfVL8HgzJ2hOl}JpnA^-<1)Z`JQS#40LkU?7zUVy+wCcTcYn!W{qt|k{mKvk?rRn#Qjma3?$ z3T?=_PybE;LF2IBO9=TzWAz}8;Q0~2ajxf}C_*&$V%(i05275&jRV+mme?{t2Odi7 zFu;Tg#ASefBs(D;bEm|)$<(mTFbpsn^i0;s0M^e8%X097hW4oCIsm6*x<9%);dWRr zGrOzFReBi)u#h&Qm`$xE(FRtJ0aWkvU|a@xj)wvj3?R8Y9>V~A2DOIwOx9wsCBNql z*M($+ z7@Ml7NqlRnqOK~mA?H4Qg#d!aVZVS7@^cxW2hU>x$6-GaMG>NLF2)5p@=lbV+_(lM zOKcfnb0x9E06QxXmjPCh?0(h|N8-SA@(tSz!vIeZ;0SAE0PAOlWeiya4ee3ObpTGs zbboYp!tJnLW_B+kSLtOKz(U%HVm38}L^D`D22j191>-WnZ9EjHU;xSG@fZfU8@HdO z{NdmEAwTz~Y_O&5!B{YW6h(-} zA?$ce+IjKs{7|Om#x9gBv1Nd3D~TNjSX6Ft#`f? zcoq)@Di}a=c|3*zI&r%>eb1;&B_Bp(`*0jmt44BbQx3?OkXu|iM2 zh}c7@V3K%Gie9`0plr*Hy(l|a9R^Sp3sV&}i8rPy>Z(E;a_-X~2q0)2_PYonKbHXx z;W-O%9QLbG6d@Y7VBDP}5275&jRV+mme?}DP&|~_VSsTJh|2)|NOnRx=1z%olc`~w zVHjXE=$WjM0j!@HmgV3D4ee3ObpTGsbboYp!tJnLW_DMTtMoDqU?FWpF`HUTq7AGb z1E}8T!ML9Rp5viF@ht#bc|3*z`kd4n-ZNR_TL5z3w`7AYr9Z|3DEYshk^!p>pbWzm zHM|8N?o6iX#}M{otf5U3Kat7^46q#Km6Ka315V;$O!2eAF_hH7>X3p88=I=AN&Iax z)m51G=v=Wcf_@|w8h)ecQ55;Pr0_L7x3UIM_%$aKMTo{f5N=S~WI&#b(vcfSpk#^d zuQ@r1e~=Pq`}jfAN&JK6m&l=04kiAeIhdo)692&be$|gFJ9gOUbhbK*)qzan(Uq)` zu#~@Sxw-Aa>lTG(>h@?W!K47%h|_Z2$uiJ%>t*=h7RoKX469g38&M3aG)eS%R*zLw z?_Z_ibv{?TjfVmitRlHQ9&^saxgTe;h89ZH)d|1Tq$5}3oL_V5B*14_+rI(=~3uz;Y+0g0}Mq@01k{6wt0jn2Z8HSJ{Fo48<#0oum z2jVC~1(U?HQ}n<9m!LFq<4lw}tPTUHiqlgSHHmLXRn%35Hssu=zbAm8aoFEY2>H1T zumsOPu*P9OjG_q9IFoSQtkL)dlqI=wB}$gqGQhe@Vut~GD-f3fmXqvjtRarXBgxdT z%`gn`2t$y59CDRjh5;<3jVNYQ<4H7$ z)nfqF`zbIk16xdP4@-K)x|FRV-m?S=rDZaA2j#MY6lz|ooq6}ko7(i7#ma3>p{C=vUt}3)4 z=RUm)w4ia=cVLuXG*%B{9H6^d<6Pg1q6pF0j~x$BI}e{0&Ow=w8z-Y=i7f+kRT4W4 za7zW^GQcE~eULT8k+?3I8nzjR0j>kx!x|aD`k7(b4qnjE9<^Kt;B-v)M^`7@4(nxR z_eFA*UWNfIq>U(MQ!kO|0ISCUs`u^b);q5uZ{?vt1p`PfkH;{;MBJ`nEe2ciZEw#8 zTgpO=1yJ(d(=uT7;w!^!G8AtCh%<>5dh&OOO9&NA67NXSi?;xjhjZg*lx?gI1E`8m zrz&a^KbESfs|sz%xlg|#fS_^M7ZXB$E(7evvlDO}_DfI{AsRPe+@2#}MA?@c520j< zEdvYyq{I#bjHp0d1~>rdPpl!1#F^>j8@3sS0Zs)yl{GSe^)th=0KA}~J!-iQ!0DLo zkFHL*9oEat?w#Z+y$l0bNE=birtTuqDprpHRPQgqxSs)@k6w&b&hXM-(eEXD#TdFAODu*v|+FoX=nTL9ufVuhZ(6LA8ef=S}}DSBXlt5N3U z#P7MQvUEu4#>})kqx$#Js1lHkUaU!3|M6VW!OU0zyK0A6D#!O*NFSx(Fzqz z5?{g8`XvWa4NEBlEp(uaWOW!oRXm-ls7d^3s-mtcv?1p{y$`gYaoAtNC_k40CIec} z8i)M>6h(-}A?$ce+IjIy4k%M|V;4%6*fPMimBbDMEUG|U2ADyz%UMGliJOzDVVhwX zU_R(|tdRk%pBa|j-~|osQOk7zPRDeA2A8+PdYRchNUqY$Fo1=$5yfokFp2t6p2q;H z_iQjO1DwS}feHqYTpo{MfKJ?QPWi(xIUp|^kqx$#6&MQ!kbG!l2COoGGISF)Fo49l z#0nGpMZ_LL1(U>kQuHwFkD_eLjlC#4SRDpX6$?`pHHkN-D(b328*=W`9|$059QL~i zAwQP^4&gZqa2)olQ4}E>w_x0zBM+h+$&CZpahBLJz)(Dt*kOQi6^P3K{YZ8~I_6G^ zbCaoIn_(DWH0YVEkpZlq8J6YX1r6;{%XI)w$8>*mb;9kiUS@VzldJSH3}7K`L@}FM zOQH>|9s{V}=fSuP@Ei{XDi}a=c|3*z`uufkc+X@l23zuZqq4!4av{b7D0$833|M6V zWf)F|zyK0Eh!uMBUc{+{3MPrQ6g@D&4Jh++V-saDtHS`QVr;6SCh?3^MO{^BL(YBr zCIJME!#;%&@^cwr4W5Gl$6+6gq6pF0jd6L7T#d3WH*P}75?cn?T1o6Mz}^bPWq=JN zdx$l}k=T(=zG0hT7+@Cx2Bs#4fADDi%&<%XFKB3wTCM|dI;Q)hs}pXA^)j=28M#U? z!vGf2MijHD28m{~dJLd??*QX6z%m{RR4{<#@^}mbtjFz{lt272W8|05$_87?YZwa# zklgjI3|M6VW!O&CzyK0^i4}VCd&DE}Y=sIYiN~erfdNiK8JQbLp^RsB7(i9*NmbM& z{xVfjR~6cjbDySz7Bmj~VT|%~8K4W$Bdl@QA4O4wXdKQVOk$13%TQ+K#%ocs#FhbW zsU&t7U}Xj3GQez-UBepUNbF6fhHZvnfTf@}v&PQ=*3S&f0q}x`_Ne7L0H4j&;w!^&q84ufh#mNcp1c=vDxrc&Vl724 z-U3kO<;Et;VpfL%RK?g-MNQ%vsfxO)(1x7*^i2W?8i##~ISA*^H?F~R5a2lMgHaSA z8oMzr&ylN9*5$@cC@MI*S39t^lGtH@y%mVd02@g55Nn7du_K*)!#2Y(z%Bv|Oie7_ z0!$OluuK9XXlRdGt^;s7ru(C-6K;p~GP8Rbxk@j?02b0l6tk%YiDt8U44``N0OK;i zG9C&P-vY3e$72{^J&;8EBycWhAS^0IFh7s-hh+;N% zm_+?3&tm}9do~!C0nXx~Km`LxE|14BKqqcDr~Kh(0mz>pmkqX*r5Fnaki2(%2CQCu zW#}eqU;v4;i4`XH3y4by6-*NENYTTvKY;RZZrqHrjn!cQRk1KtQImLms-mtcv?1p{ z{Tl%Ujl=$BLdef$fPHvQ2ONjJfuab}xEbU29Qh*3zT9{SB};4>U?3nRb{Jq(1>!Qm z0YHCZ4RIvSOef#4%`gmbD(I=KkpZlq8I}d$1r6;{%XI)w$8>*mb;9kiUS@XhBvh+;N%7l~G}dJLd?e*wm2fG2qKw^cSyc2N(p@K=``6+r}fU8mF@dKy6^P3Kt4MY~YltIpU^@ARZH8fhCkSwaH8OzpGs7~5EP{sisO35Ur(?Q5x;o)@ zST8fX7m=&s$nT*@fLtG zlGR}VRq=GHq9*aDsfxO)(1x7*^ghsn#$kU6qx@V3m<(t=YaI3mP!u5=hp^)@Y3IdT z0Ls+d*oBfMwhVA>C9%T*iz*P80cMcwa@G(>;^t&(*k%|8m=Ag#Yh(cHXNF}rctJyZ z)N&nw(=pwj!R76+US@U=lB@JG3}7K`L@}E>Orn02=P`ilJsXV60B7+~p!gPmtvnvX z0G&W?W-SI=^0M=@!IrWDV*!+W=z}VPe0C*h8pbl6X&w9)|r< zlx?}O7i9;l!vLycVXC4g@y1j|T~%m9&VBj=0R)Z1eitF+=Q6+{JZAxp!+tf2B1GdB zjJtE>L6jr8aR58c5?clsiiZ+A3^1+&aT%Z=$xcYe+$nKxGBs>73afPw1L%Q0M+|E7?%N_f4>>|Lx z)WqUv0j7y&SSA4xG_*%8*8w;k)BVxa3Ae*~nc2OJT&0&`01Ig&irG|yM6+2v22j0s zfN>dM84m?27(jA)Jca?*CR6iXueg7L2=db7yI}9+c0&y9j zAIVNg$J{A#ZZb7&GYkWa20fECGJy3n!?GN_prJi#xembTnC_3RPPiS`%gpX-a+O|& z0W74AC}vY@Nwk60V*u6rJQ$Y&p5viF@ht#bc|3*z`uw-n@Se$947TL+re=dJ`MC_R2G2o&}Uv46us;15*==w*b>bGc1#U2pZa>mg@kVj_LmB>V(^2 zz0B-hMy}G!Fo1=$5yfn(L895L9s{V}JHWUMu#ATS#kT-#SP$eG)?%l{EP#@`rf0w^11Q6GG8AtCh`q!LJ^4N2k&9cQf=S|WDSBXl(@;j{#!)EaSseyY z6?;+@HHp7WRn%35Hssu=>7WIT!+sc}{9Fd;0`v%L9QH?16d@Xia|r*3wKsvUtE$@n zZ(1m!z>hH5FgAcdm~23WARuK>;8o<_B%}#uD05|yG8PeullYqpEv47Y)VRv{hcXmf>FGVj`0ldj7sJKb3qk+b#O;rHmJwP_e3b3y( zVw6S@02AeQp^YDYFF^Q;uEN2lGf!kz0l@DcRxm7A07jTD)ldO|Q=}Ev_ppcVGMZQwBd{3Q~MRIM;xG=D<0AuCBz=;a5Pst=%0oKr9YgwABz>|&Y z_lw~wK%1-w)#eqz`?uk+ z&^>BX6@YkuFPmfqcv%-Q%HIOS#q_ueQ0cB8-W}Cu2OGTagu=n5bCAf)5PWM-!LYpX z8DTq(ki7*6j+Itell=q_l~kaBU8bJB1*mgwksMOzVzr41Kosp3MHg^;i=vw-m{4Lr zT`vJl^TU3KB=AdCfLrC+B;)+BZ=?fBx=7phYj*jIqXrGXO_;PsM8 zvI5+r!R}WZbbwrLNbe!5$1t8wbWRt7_vvd)o{4GFSOpmJo3uN+~+U#J1-|8(K zY&!3V%nZSOpD!4eD*z)rqY<*V0Kuiw3TyHg!Ig*Cr2++9&D68E0CjdQlH=6bTWz8O z5XGw&MHlc{i=vw-m{4Lrb;=sk{IGu@62D{x=#$YRwfSLxOdS+*ayupAK(#q}xH^MH z@p|J1i??k7-yQ zmYf76R*e0_)(N}Ad%3eaT6-yaxeDM-Rzby0Y8?$UMs2DB5bpu9NmhV;brGZdEkImM zkE;L^Wpbg~>|leh?JFE?I`c(lhTyda3WnthzzEYdLiQFQI3%sGCf5nx-ftBEc(QSnuK+tqhJh0m;Ik!@WCa+b!8TPJbbv!f_4~ze z6=1xqPga{(0Pp7x%T2P!G%OEGP684u#{OaJgx%r2+}XWPV?{4l0ldj7sJKZzpn)D& zo2mfB`#0GnE5KX2h*4Gnz%f0p0yOFH?x{9A*x=e=;b7D05t$i+7fvh~mNz~l?5+_) z1ptndR#=k`!9hs{3V5`shi8EE)w#Gx&QRxSwTTKq6bD%pUBEpoif*D{LW%u!mjp1) z4|~5P@Jm*Jh4O5XaemmhQ3r*boG$XFqUC++JXj87F%8SZl9PbMim`v#I$?KsFL!p2*I3cZ zRRC|Y3My_=CuyK5YEu<}cyEwRvI1PIix_1U036ffD!?MSy=LQ2X#_VMSvc5q8bxLm z0DR(61;cU$V1!pRLZ|@17muj>g*6!=*>xoqDBy;s9xA}c)!Dm9?xW5jY7-TJDBiIs zx`2POD7uM)2_^Q^QL@G~KkVx$di;_V;6!;YQ=1?5r`16rCwG_J1hqMNygDZr$#c{x z4D2hwMWulg72wvANwNY=(O_Rz8+3p#8rAO?!&QKJvRD((as{r85jx89LD*z+R)(D{j0B1-mtjV2%4<2Jh0r;q>{4L-Q z>by}T-&E%VwTTKq6!R^LF5ndwMK@6}p~QaLK-QS%hy4MO_~m3d5u3?qn%ewapRW!I zIr)mnHEfdMfqxTq#umw))F}+?E5Kf*ffE&=uVj*}0NZJ>9n}UM;0&Ys{bINZP?hzN z+PngIKX+K}kv*nid0281kXSMH4_hbf4)5j8?h_g-dbtYVO;$n0O=^h-dPZ%k0ub+N zcF+?4Vn~B7Vw6S@04;L+jN0sAgO50_aIoo25}6r-7f&h}mNz~l9HrZ!Olh@!`$=mH*WQFIdp6H4r-M^Xe1^_7&ju(!hxdu(ot!Z0EdFUdYNb zGI~yJ&;jmaneX?D;VM8y*5lOX6~Ozs!*ZPLF%8SZl9PbMim`v#I$?KsFL!p&)>zTY zRRC|Y3My_==W3woYEu<}c<+`?@)_U`UBoD>0N|J&R{@sE?H@M<%EH#}^JZohFf4 z1puFTLcy@SaTsBRMhF!E_{taReql{UOLkpJ1q!&Ksb_Bi>g-)4_fh8%wTTKq6pc1y z7w|2kx`~1bCHB*?vc@z&>>DY1{E`*mWO+WNHb3kysDnaI?jyMgYIE{xA9mz1-Q|L}NuSR{^}qDyXpCWCf_{B1ZXJfVh|*R{P#v{Uj?2z`I4&e-@z5 z3q|rpb>2{$r~pJU-=gRO&ax=FiGm3w_R}a?W11iKABe;+C(DUgS4O9)&Cm6@>Y$L5 zFN$1Iv>dI;t!p#SeiookVPIbY#z}^O6BXdll1Z`xjL~45str28A*1^JVz>%0Ue+h8 z%`1TSbBE<7*<%`(hb1Qgi4|l2JZ&*{dM|f&?~`@(auvXvtb&T0)B_smakZ%mK)ipG zO|k;KrHdG46#yL5<0?Rt4)307vx5z;om@EBbb3T)hTw&h3x?&5&j`C~giry1@>7u&)5mmj+H$fcHx#$qMke276L%&;gFK z%=i1na1~&=1jgDTW>jVNqL-@x-eeV2 z+@wy@KvUGFDgg1`Ae&?bxKQaFMenM3K%rmG?uZ9eEzyXUQRK}y!xiH!d z7ud5FSQn7wh4VvT%rSAK7Af|ZZ93IsPpwbmG_|2P+$MNmq{FUz2z(pC0Nj5inUyC!yVC z;sc-7><+P6R)Hk5p)zohu3l+Y17+=Pb*V7z1ulT+G%&^?)6<6LqRLV=R9vyfv9Q+V z0DdEFOhVy&a|MSKMiEUhB#lul%0p{anGso`V_A z2M3Zf7$}Y*i;_4+9P=pD>Al=7TO^K6ZKjoMIAfEBB8{ih4EaETrKfCQGS&0jdYpG&|3Nk6AHd*lVbb%xyl zmRIP!BQi4t_f07nmbW`2JY(}Y47gNUq1^vPaOIhHrvR>I>fsKsjXFCQ$#LrJtv2Wl z7saaq_r&=|F;5=6WQ$R_!0yss`| zl$9WGOphx;6XkZHjUT=%D}2RSg@a9Jp2(~MfZzX8!LYpX8DY9q!?PlAinPL-Tq!tL zQh@^AVCta)Jf_Z)BDqwZ7t|&y08z}bD7t`Ou_(HUf(a$|)7uijG(YS&N&>%}EGJ^6 zJdcubey&ea2ZfwmD)N=0<$LO^ERt($#)W}>1sE$222NCfeM%jN`;0$%nEs{g(T&y-x0f?gAqUZu{Z&7p;1rti_ zr|TtvX@1xbkpzCp3UI4Dn`E3H_Knm*At$GZoL#iMS)JR9>jSMqL-@x-eeV2+@$(7&~a*06@YjzlTES$%+f`SvI+o>>2Vccf!v<6@xxny@LT5= z4mO>4MCNY+!hJ&p!*T^+glD80Dgbb){KK03MR4WRx>TTmtC@QC7NE|~MRJ@vd#g=U z0HS!+qUZuXYf*F)1rti_r%qX8njiKLMB%pVPIbY&M6I?r~p@$Op+DgI1P4!+MokmYE-{p3|9eW%KAaI`7?m`bBASx>@f|? z!;+JL#EP+h*g9c%crSN$M{6%dFING)$ttM0Nv)%S#;8qI0OCDBHpvRGuP$PgzXgbk z>2VccqD(GSn;mTM71Ig_o6bCunIZW7uM`Z+6@U?@YlQ4AKyZq*!kSzuI9F1E0^VTi z*;{}*ON!)DbzV@Lr~pJU$D-&0e#N5bCJH8$*iUau0Mq=i-zW+Eae*X>I_DP2A$2ZRo2URp(QZ+60k^j(x`~1bCHB+x62LS+ z?1xAKzhnitRh~^U&JX)W>Y$L5Q$)@#THdVA?L~5-I)#CK1z21fI8gy!FPS7Oz&#r5 zeziddIMy=X?-#>WfR`k&t}SBr7T^}q9hL)Sf@xSDmYf76R*e0_)(N}Ad%3fFxWeoQWsZCV?;=N2Z$qFz_7ct7;0>s7ixC*d9CeNwO4mS9$^9u)?&O0JA zLvY{pf?>G=Fv2q$A$toDTq>=wCVvrJ`SrR~pn$8HdiEBe&dx=0oH~1}O;iA)c-5ll z0zPX|bQ1*=O6;djS!0?X_76njm#hGNGFqfIKkSdGgF;Sjrz9MxHYX2PXRt_~s7_&E zUjfc34VullYqpE zv47Y)VRv{hcXmf>FGVj`0ldj7sJKb3qk+b#O;rHmJwP_e3b3y(VwArHh>PiQ6=0%F zE>xQxZ1A<;C>(4$^F?Nc;I%I(7?vvlBTUx_*;|0%khH>@Tqk(@H?0By?-o`6uBR zyaISXcUW$cJ*HuKSaK4OSTXj`(-vc=_i|_VK3PXER{^}qDyX z!LVEb7~vIE!{5yieDUJCUs#h7l3iC)fdX!5>Y)OBT%Em(RF=MKvnT3<}V^04G2AhBZX zAGS`|9p1~G-Ay!B^l}xzo2-J0o7CnSXgjs33P8MFvPo8enl56LRRC~IkE;Na<@OyL zKm5j=@EtP?2b<2lBC`qr-t3ZsVYvb@!fdIAX8_;~X@xbpQ}DrARuq7bimLzJ40YZp zl5eW>f!ag`Ad2}GMHlc2i=vw-m{4LrZ6Iq*^TYmtNc?iLoQTb2G)--OuFqEog`9jv zWfU2yA)aDhy z`?rZ!Olh@!`$=mH*WQFIdp6H4r-M^Xe1^_7&ju(!hxdu(ot!Y^lGSA*1Ki1|8r& zmid0a7_I_TWIax8UIDzHJ1ob^9@DTqEIA2CtQh-;trK>K_i|_VY>gGYTm|qZtDxd0 zb*=`Qt~ONxi1%*UB%cB9&_#^03ILAjaTQ>h-2P$XhrgR4yxzA92b)f_$gBc@&zW5? zELQ+VSRvI=0f28_UiS-Yvc6=;N-9vm51V?Z0H0FlkRo}gI{j)B6@VxjZOAU*yGC^r z1rti_r_*GOX@1x@SM>NLE5JGOd`WG7*#Dpo3ORY8$}C6nYcz;q3Eq1vDWeAB3YzZk9pERgk!YV!)<{oG+0qxHo!EDuXg0un36{$cBc z-Qm65+1){7MK4zYyvZu4xJm7-fp%A$ssO}$v}}?U;0RsBD60VAm>yRFrpfIV8-Ge8 z_@Oz4gH7irBC`qr-s#GMVYvb@!c7_>Q~=;yX@xa;Sa9hTRuq805S70L{9T<7isVXl z)=?aZ3P2S1S`=Nt?^zVxM8Sj-`)O-gW11iKB_i?5$#NoglF>}H`MJJa9TakMg~%~B z$?z7igF54ixA9mz1-RTwZ@8Gt^#h)VtO9^zdRzr)liTOjW(OO5%6AF}o6cDxGehw6R}~D)8=ny-XoOG!fQL#etjVc@ zLy`&<@N1?Xo&j!D=e8nwyE+f3O;iA)m}F6O0edZqZlYj9iT(7H1Tf7H`?-?9FIfSe zm*;pH=ZF1M>Y$L5H;H__X!&b(UM!NYt5X=*SAh3R11BoLX3~kVCEfyFkK_i|_V0*w{DTm|qZtDxd0 zb+HDTtu|Ewi1$(1B%c8u(nXB23ILAjaTVYtxvh0|{Sapd8@%x~g@a9}MPz0OK4)&h zuv`HcVGWHCDgbbWQJtGecC4hrG66qq>e*X>I)@a=L)Gb5o2URp(QHF@8Pw2W^AH6S zO6;dIC4gyu*td}ce#r_jB+uWe%@6w<>Y$L5hf3}^wK;jVIzvVBB6SJ_`wB3(G;pE< zJXkVGJ_AhGU>B+lI>0xL>i3J`D!>9+zo<5^0N&3XmN8miOvCc9SpkmFMU3*d0C6!rt^!Px$t`NLgAM-C zyu!hzvq)rS2;S_vf?>G=Fv3k5A$toDoGq=eCie+Gcx_!OP{2n;)qfVC&KpJYO?5s{ zo2URpaj!+u1-#ax=q3s#l-N%nk~OCJVSh{{emPlA#8?@9Rc(H*FIESIoP1N{8aB!7 zX94PrEs{H_QyAD+fPG2>Cn~^T$s}0;w$orcstr288AkQ{#c&m%D(fM&c?Ix(?y%e= zdrZUfu;e5lv106>KUj>N-pifcCuAMHTm|qZtDxd0wL}9wqc&9mh<7zRXoWMEJ|i5c5kdt3?k=rxVhR2y`F<1F+2elc7H zST2FFwusp`g4`my!!ki8n1@M4R(UspaWcLRKH&gR{>_q`a!k%Gl2JVhh>HAF%8SZl9PbMim`v#I$?KsFL!oF zYcEAFR{^}qDyX;ypk%$qKNqE@G5b0B}r?s{j+_cDmYdKFdego4CI$ zh{>_0QUji9l$$$VUoevnU<_y2h$ov@4={RllqWGw*5|3s3K)2LH8z(0*#EBMLCwSbpIt{J>Ea!uf6k!u`ou2;(@tSOLT8Y=E@Qj#G|!yk|Y z5=i2&(|NBNUQ9D_D7{w)UQ96j@yP!?ub#5W98DRuk(E=Hmu)IZBRp8J#9f4(|D%w5 z@So<}O>kKwo#i64%?94(dj-RCXpHdBxN^XsODnA0{|X|NSWJXoH=!=z!zR?}gHXO|%N^Hn~0m9iwo<;u0niCT<8{l_r8CDs6MYQG5{bO(mY0fk{eX zz@sgO!+;kVHCWCKFGQ(93Z~1ApWcq?=OpIF?eYsv}f4`okM3FvNCY@@- z)6u-4QADu1Il3Dh4w!~gHGgP(*^|qhEH6t#X~^t+pu&I|2VPI2c`>BJltxs}sDO`+ z(Kcvb5J;k2eOT75=2ZnwGHSpJxw7!N(tJX(4JizNFP}-pAl#upL?bcch{`T=PZxKt zMd6Zvqb@Bp6L$-^QBmEcHp?jZUU7Lw>HLowo>2j_r^1^OkUf>@l~=Y!S^u=!>_%>Q zft}O^4I|WPRGS%s-@2nAl2>w|&}n0T&aLSHcL zF5q)UbuwHOa?h0rO*O9?UOQTlwNDBj8mVIyOi|IQfQxR z+U_%r{bh~Uh{~xIFz(@O5naq~pUX8C;Ui^}ULC!FQK%asLQm{WtP4}Vcj)U%Br6l9 zeD4q`MKmW-X3OSeIjzXuW$QC=u%>oMoFb4xL#;sl3A1uVb6Z`)YeZ$%`0KvZ&tt+n@Wwro{3ah$$M43mn+$oJL{Ei z2enyM1pi*#$jX$N4Rl^D7=kDK(8}k`Iw}Syhkmu8Faj4E)%gd(!|tvNgeM~4fT;ZI z5?@tk@ej-n__V0p?$_$HE40KD7E|BYqUZwt*r;v-V~&Y)@dH_78aDRKp|-o~0p6)L zCtpN|7Ol4d7h*;Jl_GCYB-JM z$PyoWmJ<}tkN0v9FW)N#o_M_csJ6%<5ch+-r)?pB%Xs z@F|gN2A>wWCh%F2YXn~|E)TH05@hr`%`DsofYEQ(H(dF`?_~4q0y4L7zE0QBNF|OO zLYJ9P4S0)DT?a^LKyBjR8$&31bpU^7LY)X5WCW`6 zQd(rmu0ABVBvQ``z7Q#3n+4tv{FqTy;24`rKX6l{s=yd}KRM$lg&P`>S%p*q<4$$= zIF^&`nZ5RcN!jWlec^S#yf&;QVTA{b`9lQ8kU2S==Sh}{fj{a;d+lw*C}P(BvlZoQPv#zn7CY zN1B*qPx0XUbgiLMW3U;PDR<}dURCeq4%a*hF13}K(g^;}eRkVe&_HLN$n3rbe(L^$ zVR?p(u;!2IMxlHI*O6989q>aoX83l~$J9A4+Fd1hm4*ziz#A=!;CizzHHFXWEzVhOBnUR6I2w}h{iyz(zAc|W&d7=4f{M$})*5JMwW&u^;=SMYP~wsFUR}f} zdn5(N^te1ft#Myg8%jBFU)!AneS*hEs=Q%G=+Z7~^Y4jyKbL@)WIuXg{RC!57yQ%% zg`-R7S0Xb*@W=-XhULf@;nNx+oN?g3(h3I^_+K_=IH;4Pt4cP*=3z*}v=ZU$tIi8KC~X7+^5unN4yg!;XZJL45u?_plmU%Pp@B^+Dw z%Fnp>b9;|b$a0t&sl+=_B=FzNpEZj@o!-lx&Mt)=z1$i1Caa+0@-smL9icXL#)77vYXQ%STr>FFk!u3aj-2~CA9HP! zO{g|NhDrPc4X>Tds|#2&%I!V8HZ_wPknj^9-{Q54yt3y}@TX0wdWhgLkph0xr~%+* zMpc1iD?CdBNnF^0<-NslOgD=P@e57dozQu@^rYHsYk@zr$nFZuD|D(NGehu~A1N4? z17n28Er`Q_+e<6lK0Yfr=^?u<08bH>-xlVm)2bnZD{yCvBHVd)Rp;<%H(X%9vcS54 z?G{)cficI#<{K&NHO#9DY%!`Ep|>K%WPWCo?*8?|_4zZY#N7%_1jBpVf}z)?ieyfr zi)c=w%!G2XoSWZE?llXx3S@+EG8QXt#tGXJ$dJKmYb^l-2P+^kBu?3DT9tQgW1hAX zm<$9rl0d+LB0!=G6wwe$Vmvev3~9M{=mVeCdxy$X%q`0^%E@w8cTm12+Im-OZlAi7 zeNf43WaZ3vD&EiCwitzcgq=&2oQ^r^s%q3PgNTh z`Wp@2&m~}*q`#{+E6w0fJZhzRS%uDck(nX*{-PnBMFm6ft3|`|4kwgX6-u~!12>mn zsMvc7UK=UkgGL=z9xyl#H{8!`xGvy3MpYTEX4=E1@J{op0pol_K^QK8hs>`7xRs68 zO#oL#%1_SSn#pCHNe^&46Y4{|&!`^Ye53mOaPBbr;d&WvHxnB4!}T)UJ<$pnSF`3L zaG&75r6e+J_>jFvnv9Y7+?}}pDDUjS2OO1ZK5%!C-<86eHj(ORLA=A+?iY1Rv^rJr z;z)fCU9fq z8o_`5Ng=@DGTNb;g|Y?woyH6gVhxhLK~jObUl8@g;~J~Ax@mU-A2sdH-w3`LDTcei zhU=W`hI1v6;l5(S)qtciJpG`(wWbt4g$Bl{xQ{pPLQ`JhA!qs2{nf!QC(8-`jrV-V zJeO!z!SkJ|WzWUlbJ`GNe#XMD0t;bGbLIL9WtYTP^kjrE*iK)QeU!V9bkn~PdppUEk(Y1h&=qDhlwnr>e#GZb@X2qq<=KmmzA_oc&vWWMEOzo9_hVv#G=)tE_4|Gh(ps`GWVVN4*y zgfac?d5P?wjefxWEIor9Vx$sZ7+5KR-EFIeFAR7;U#eyQu8rn?;dHq(aldd{`Dwj& zt*JJYr*bB}iBoBk_r}AkcoV16BJXX7SMerJh46P2$Zlcaee090gzpP1XrObD$jlJj zRWwAg+%j}{asYl?Hi=@5_aSCd1MaHP!r|OckN^_r6`_Mnr~~*36B_hF?x6^w{Y|I` zxRnX@c_H_FhtMZYs28}t33YlQcZZoFflE!O3S7s8`n{05ov;qyi9*0A)a`}blk!9j zd!E{ClYpxu>gr)Py;PyoBQi4t|FmeRh^%3RYixpDz`f-cc2i9dsl*0DXtoK}fJ=;W znZoN5GwA?+)`lPO!@HljBXqF|^#J!Xp*}CS9v>hWIg`%`|b zUIYp;e$71x?xWcDw-~Fy3zeBLw|AzJrZ25tk2fdjTr;XpW{*waH^k+JbeKqHxVchL z2+u!J|5(GltTsGMoh{)xY7;M=yUMytrpSm8~Rc)C4j)G^X&FzTgZ_y6; zLD7!z?vF7qpSqF4+cBmXNcQtVn8s4s^N_ zx~UuxeA&NQer)iOl{Ht+r4x7f;GS-W50b+hbt$~!kx5Qi3fbQizBt8fr z4Gb4neU5u#d|Ctj-f~<8GFtF^erkEVoGkC*o0Q7?Eu~f97-c5kdY@P46iZhXNG5YT z(piY)CsQXW3599-%{)WXxl?U+e*}}4kbX2`7Tud1)|iIC{8zd6(S+G%VX=XQ9mm|5 zR$~1Rl+dwiM^w(O;P#AV%YCexaBK8#?lVHd<|u^SHZA-fsm6(6x+8vuOlGLf!em{h z*d}#hCfpjmn+x+Kjl#hTWe6`;B~azfh*#m{dM~%!GZpk6*NNukR%4nj-J&*2D|q2Lc1&hA&{-rhGXyU#8kUaOQoy@BWj6$7W$ZgwY68NjyY=?js|3BMG(;Db35t$j5cMW21 zTU2h>V6vHH65oWuWFs@F0S_|Dr53LyGwA^Sw^1&Sc&%k7J-|nda%B~-QD)K$yxS<3 zYJxw+Osc@!jq>x-rGKc+@(zA&U0a};4Rn@^%nZTr7Y*I2N%@}^gkLpX8Y7+5s>!6u zOlrW1M!B3Ulh-IS=>R@sRR8OOOe1mD5n9WUOM4pEVPAhd||1W>Nz_Zj?(cUgOQA1Gw0zVW+>Rne+gEW|TYYjJ2DY^a39?%B|T; zx-@FNdfu{BgFg@#Qa!VQ&OZx=1OEq2_n@KP5MWbA*3I2;_QU(6nC^xUl`Z^10^Rpnl(FXNvVS%PK z(D{(a%n*E7(XhN~jIg6r6RU>FXU)W|8YZ7IlNxZQQ7$KVjW?4H;2MfK)X?%Y5@i*k zJx!Uz%-2J ze+D^G{$Ev_Z6)xhH?lpqyh7(dk(pt6M8v*sLHX^aOE;NG*KjKH%%lcfY?MnlV}Hj? zI)Lp8Iq^GiczxTvdVqVIm)|9_o@HLWz>k<$XL()(|11_-m4d{_07vw5rY4Tja3DXG%q)=HeH&aHp@Hs|BbN)dZ|KZU6H8;LhNuG@33pR zBD8}E4SFH>xHnfbzHZE)@ zIx|IPhTu1ghUM+V2tTw5yCQ(e^JX$^oKKrc4Y5ERHM3yXtq&RAaNy@3L(7n=ex3B@}X}-je}?QXM%qqjsa7DI`~a3!LhOp zHiu3wX@y_OLbf1vXgQ_J<+kZ2^()!o059CM&SjrXHPBfkGBX6P*t}pk+z8uhgv9$e zLG+u6J4Kjun@J7$W23rvP#^@9SS^HV=G6hbT2l+B=mEhfII%*LY)ZxMN>??#x0cqSqOX1|K&@v^(z~#3jC^#*3D>0B@($(0+@!;W)3Zr z#OF6-M+9<`IzKFukE_E7xqEr<)Ff9JW6sdW}$#+;#%%2iqM>@yl&)Lz)g{B1#cL+Ht?pAYX{HRvK~`@=b&?m z$Z+Rq2VWYwHt^++dP>FviTGAE;9Z*uUAW(Ndp(tJ)Co?*(6r6yg52ZHp>9 z-lkIuKv3s&OrDIbfY%-CbrQB#tFs+ zchPSp&B1N3I#XUgTQ9>qo8?{GrITfj5s_JNVLV>a)s!pibx8 zBD1?Zcy{F4z*j`B75tsZwScdVTr+rXRITSfB zG41SzlyUoxmGJt_{3VLn-n?t`vEp zUW8b#a8$jJd%W01@ssgnDY{Zd*V$yNuL=G=Qsn~1dLAZ|dXb^Gj+QtqEnyNvVzsxF z#@2B-AX_$+UWDSdaiy2_!ZcYM9?~cUBVQX(x>&|*hJWv)vE{>!E!)ohrKn%qrO8}^ zB7X(1a{k6ELno-siVyhX+glM>)Ievv$jlJ@r=sC-BivzYao97m$1m|20dd`EUR~bH z9Y(y)H?JD-$kuvbVcoyshIh9Kgsw254&WB@4qIV2C*-yQLYJ9P4{%!(8t_8y3?amP z!XJGBjxwQcFXYO@R3!q_usNp+dy+TuUv2uL zro!n7|IqPH;5RJHPQtv~!t7)LejX`*g51v+m&!L0Qb394IE32Fs{`07@36pL$dwF) zb~K?L;Lax0?}c2+KxkVN>IJT8LY)Zh7pVgTkBHPHK~^O3-0^^hK`@+Zf1=LzM)QXl z5AopXkJ$y@bAfphUhwqi<6iIS&&QD^qwFWX<+xHm1^%MDP_YL6EhcBkOoQ0W1(Mj=NPFpA*-O&g*^oS9+y zKObl>`nUxxe_MZD6I*T*tODP#3HFsENZcB?mi4-}x{0ITE~C9nx(d9|r~yVpD)G6& zd9uD-ZT2g`<&<2bDY2EZTP*nNHd;TUk*36_-#g2ifI=pH#cI*Sw<>IT@XnEI0*{Ma zGx%eXYXP@Mt`)pjs^A7s%>_W9fz51I;Hi)lRkHR`(^r^CMMWjJX>2 zX0_pkJpA;9-eu-E;3IJFJf!Otn@SbR`8CIYXR>Zxn}UV z$Tfl6BG(9h@}q?S%iEWX{!uea)Dld7Y9?;+Fc~qft{;B51w7v zO&u=gznfnVkQ{~+U!H#A-b?_?O{f>hqJ?_rh1^@@=9=V=v*^;!)y-Ij# zfde1D3(FLK7D;Ea$nZ1Wdxu7C-8}aK5es)MSkw8vk*7A#{iiyGRQf-0`u|7Z3uEB# zOr`UX$gmTDf3xVqUkpq{=l^;rd*=e%Lsj4w%3!Ejetq0dn<|0xq7X0&4SFH>5%G^C zz~Y7~HE-w`#lq_5fBc(%X^-kRTy~w5L>G-@cf~9e&G2- z`}=|C4_)I2_V2Uh9g_bML8ADKW}cIDh$`F(zcDn5ULnR;r_y2X=l*nl68FigQ|T-b z8E(qJH;Z;`uEK7A)fJBG7498sv!Ba@5eN?*<-M7B@+H7I8`tFRz96Zk(nX*>;Ga{8iElQ_Xan==0ls2XpBHkEy$Jn3 z^Xdh@U{t3U>MUioa=5Gq%&Q9gl~Ds;$UO#=zSGRB3XDSCUdSbTngqTUg@BhDH8|cS z+m-$EWgWe$czs6Ri5-tMrs16XgE>?FmzvID<-}rgxtVkgXL5y^)c&(c$A32I`OhZ3 zWs`Y|;d+bZu;EPRn~9qdDz}=6n~^thGx8>GM&88DXn|sQQf*dgz&GqxpIrW_9i1CR zW`^atBleU{?6BcXR+x!P4=T&eqy}u!;E4xbyk0Y}4&XfVa&Opp{n5O7fZsN+?%^i! zvU&9aXPK8PA_TvUnN)!n8Rcr&0$qAiZN5Ol*Y93fAUZdQOfHZdpHiE+Juu!1Gw}s=y14 z@(ZL(cdE_u4t^kVP2dM3*9?9raxLJ;BG(H3Y2@0#%OckfzF`mB7&9B_+$b^x*be?~ zy$>P+G#%PU*h|n$c5gQ@d1`eq<(om+I0h zW>N$0-CiFtykPm!Gt=1q%pio->w-kv&5%~2>sTCdVqL^`>20c#BY?v z*b8KR!g=!nxUJfv7%|OX+#VD83tN)#;`VH5Ho0XvF~=&NGc2y^48fZt^&7#zMv9?+ zt5FgU7=yAtDtZCunO8p(A$^I5$#JsYRc-e9#bLrxX|SAy@U^P)bhlI}Y?biA#r*us z=n%v_vU2W_d-i7$Nm>>MIL6WKXXJ|8p^Bq3rUn>=2E34a7;P?1D7@8%2{D^7mgYgU zNK!}+OBgfn@^!c5pR_n@2;Fbm?#ah{b$BoLFyy_e-pjqWRX$N);}Nu>bc6q27IZ*8<)t za?Rk6M6LgmzH6XJO<#n6_A)rLXMX1e$ zI)HeE$~MP+LV@2w=GOx(_{|&E??m(K1(MQmc6{LO?0i=Ox0p~BINI_*>(;4A?Nubp zi*lYsu2mZfSNOHl@b8*Hyk?{Hm3_Ii!JhRip~7AvIM1jm@E)VwgWRJU<0ooEu?a!Y zV^ZOcT%Jm^lm`QOsMqV7L@>D*lOZ%Bg6&Qv37U^mfayjwC58N*=y!p3R=tnhI zg~{+*F)Sbvhr9cHUA-$>0U0Cl%w5&CI7@BzYzjVh?|Oprw>vtMMP`QJSBr+_q%y)y z8X`y*0{~^_KDg8xlw)4S% zi(C`noGp+QDDnr_SYXM|7r(%nZTbh+G@^g2=Uk zFN$0X_*;={2G5LK6L?nS8o`f=%L6R$EHb*TW|k-kn0&-cTuH!WgqhTU%sy<-azKe^ z0EG55p$=f2M)=CraMQpze0>X8@cYIp`1Rt)5`|lu58NI2PbuJoE$}LkH4GKd3-y<0 za=QfXP@8?zVWIF(O?fk@?c#J%r5DHRsWpZ@I?b9;;(=_?~u4sw|;{Z(yNGr&)buUm(s@lu7(lOi+2 z;l$py@!Tg6j5kIyiQlZmq{&Qbz^YMw!;QU`nREajGRo~I#u{ZNJ-~a6a@!fN1~cge z-f5J(xe)weW>N*-Y?PmuF8xAnmUr+!Bi90689Bc`y7r9PL^)fgOTRV~SI#hb*-UD{ zca0j>>+fdL0gPU~mC}EVwcNaVfOv(3l$aHOcwyTW3~No8=n3OXOO> zyGG9Cx}s}qs7>S=lhJ14vWCfeW>N$0WYn-;t!B~zj9xC+jJ36S^#JioFE;0nz z4xS#lHt;ti*9yKcaxLJCBi9U`5xFMt%*Zu@pE{rrV0rhF(PK5U#E!${U^8)h2a^-b zqz2qa5rrp{>jgiqfC6=(ATcJMU(mk9v^#*zI^3ztlTF;by-*KAEJt`S@j|YWd!b&0 zSg-J4;)UFeE7hiv1Bb!5?&WZJW%%m4eq8RRuEj z@U8g~%HCFL1I5hPYeAkF-W{#Vp^s1m1QnKH{?HVSjZ{vaA(6r=&L1kveW0Z-$SoLR zmIb*5LzBJSg4Cw)?|Wa@7^F7;>J`altem8?zQzjk`14dc8(LDTKvJ07(IJVsA{`EL zZdp#kJqnQA=3l6u%k2AY+=1a7pO-*TpErcxgg6O*Q0|ilo?*!FmmA8i;j4`0As;9? zhEKGD`3V zvoyOod#|eZa?^cDg72x#Zpz@(K2?u4aBbkzBj+A~(E7lFbT0##yk#ctc7(|~8Y#>k zc#Ki*Q5&z-&7=c(vQh3)8?Oz`qz8DsQSKJvz1)-4G@t>}zHGj#XGg)M2 z2>w>lu)J{?;Tnt6B^#56&BP@ell#r223%oO-w6tYfD-oE zy2FHefD=rp+Y7l>Lg-c#>ILp=LY)Ym8mZF-r$?%s{874ucPMGTqBnRW{|--<-?u2M zz^aY!_U$vWX8i0oO9`7rO8vHJd9+}2=vWQ2r48qb#7tefUv0KY!3Q5?yLnLqovO&p z5d5v8VL73U@Q4NBb~7e_HWRm-F?q#IYQV1^SRXPxKb)vQ2q^K`iqLOOr~|m4yu&l< ze>tJS;pX)}Ce#Dm!G!v}kXvygddh@)fg?<)8=+Gpb-Lj6NR@-UM7bd`VK@8VNO;H_ z`7e8n@J7>yh=6o)J{Kb^eT8_je|PkN&{&gdR*u+)QQS zF11-*0N;9WeLsfV;j{)i3q)py;DtrQ@*ZY{A6m|R0nw#r%)}QEU0P-)HQ*K+J@FG= z#(vVgI)L-dYv49PrjghJ2>sZEdVtrMP@fm_)j|RfnNTnAG85|bLT)dOk@cqLRRzv4 zs^1H_y+r!9Hm@o$3UzxScUxa1f!EY#I|RJ@XKX7iXrR+BGBX6>U zU1s98KPEpk6IV$wdB99+z=w<))+?HHlqZF7H0k-zChq-gnPPa#VsVqiAYGn1aEXm-8nZbXPOZ{ zB**XpH1J8II)5*SRN{dfpWcH)PRrB9bAOAG_M}u z(?<1qAy-fl+S9yxfy<2Q_Cl_po*`>YLqWZF=v|TIIw#A^Ho4@Py(+#@a!kEGlIK3z zhJph8u~GfRLkNj-+M-RhzuIhPf_MF#ZMbO-blOGcH*w(uiiYJWGQy`dLgGgwm>g#& zZqH#d(M)Q<8;x=cgV*QHqyzX7#T*{~K3bYa;&~9EZWHPOe%OTiypY>-2-Qre7r2QD zb$TIp*SSUl*O^cixRD98yaqbQM8kIQv5{*7Pl{YC`1r`RfKQBEGx&>TqF1)-4gQv={zbj z%OLo%$hCnVk6bJGr;%#`KN-1Z@RGbD&NZb%34-1NDr4$Z(7j5U3S`pHyiG)JR3X zmD)h{2tFFA$x7HcYJ=5qY5TH;-UDQVhaySnY^gvII*S;n|52Q*Q=+<(sUO&Iy+F=g z_@JBN*y({{IQCYc{BW)Y|5W*X)>fhVh9JL`AMU4{DEm9uDpXmJ&qWG2U{vR+g6A7m zy+ZKTNIfU`r%3VBv#qt*A&6}S4>roS!U)?6)hz|vBL)1dQ5|yyZ#Am=s^H%u#di9v zZL8{U1wV)sJ8d)DR#i3~JFc+#PKZ{g3eJqwEWtY?^&`P&A_e@7QJui|jjDbixP|TQ z>R7>@Bjry(^+H{ZyVMGBWZzM_qTUrv^f)1STPy+h}CPycb<{W5x3ZTJ~z z_}wacl*RC=gWvXp-<#ktc>d(}b$PM~v#`m>po+-3%ACUEBmmsR_d)gm)q$hK*iM7?tq~XH@ue>8Zb| z!YH3NyVH_(^K|_>wb?^1_}0iZf$xZ1Gx&#*YXRRAxmNJKk!u4#7`b-vo0@fa0-Dx9 z=Px3&M>OzXBi9E0d*oWdD;D1D}30#>_-@sX7z?+E611#@!GP+zdOFW`s z@|u~b%ttLuUNw^%uwPMz2amNC2mvLYMG(5#ggSssF_hc)r5S}>xkc!YCe#CDJ;Iju zLOM!*YVVj(FL1U^t=|i|irgUU)y=C4e9fpnFXSGe+GR~TlQ%umeuPO^fma(fz-UM% zR^$>{RxmIvnLo!^Sg3Iq6ek!u70A#$zY zS0dK}{!`?d!LLQG3A{XVjo`KQNogJ+o%Ka#0fIM(TpM_!$hCsUM6LzAapao8n?|k) zyjkQL!8?h|1B^SOj#%>jl=?*F!sHk;ag_^`kDEyi$nu0b@w&1{LIPEeF>!-L`wOPs z0gN+C{HX}<*MlF+8Hyk=k-$Ltm|R`-+P!G+rb&ili8hNJD6j2K>S>zi6q|1q$kq%6 z25mM&puD!LC(ER{QbC3);J>KwXSG*&N_-#e z%d)=6yxarn-Wr3AoRv!OuIAPc+)GsW0~I6Y5&@?zyd3ysx?9mlBtgDXe}%hEXRotx zF2Q>rUO$52T)tGHGhSr&=mGxkqG9;}GeWmUNE~2HrkII4z?e)nlN#_^quf0duM^Cq z1Gs}?4txHSf=nY&!%gB?6Y2qe%!K;AkXsWXI>Ll{fh&yaL}-F(y9X9#{ay2_0-MdN z51}|Um!n0pepGF?u;8~N*9Kk@xyEvu(0W`piM7V$2{UnPjmgiBB^$h3l8x5mvPmQxlPAo? zr45sxnMn=!mQllcyDHMg8@yZO z+Q7R<&LtbIHDr@WHYTIZ#H9_B^~|IO+{LJ2y>>Q}4j^8sWHVT+x%B{}n@=`wTbWxg zu;AvBJznMqsLhfMJ~MJ{;3<)F$wuoy*(8#U$$y!NOB*INGpPYjF=|+^lgy+8h*v7v z3^vK!dVtZ*CmXjT&8-($aC6C?CiAbU&5{lNVdUDt_e9Pm8?EWGNhBMS3(Ul&4U<`B zQUl&$)UaN+nMnr_uT-)bY`(em0Hd2vHg4CMTQ9KS=90Zg=8vk)k_~=4a&6!hk#osL z>v7p6l8wm|X5!L@$D;64L&4tP2fW#*9@*jt_6Hpek5#;o`=kY- zArtBavSi^iSTE#0Aek+JPg!7;Zoc1CVS`-F(qENx8sO0~J4ZN&Er&VNT2>#lU1;aXw z|IY}|PONi@osP-h&7=#sk%kLP@?k+nORN$?e>R~Sa1#^i_ChXm2>r=~I)GzMsLuubNXAylt zBge`5Z>-_R5W_@?)ER}zfRRs>EY|s>mdYxSK!ex0x;igf0i(7nJ}iASamz&1n532o z8)lOw0E}N^X5MW|#k;v>TBa%fRc&@~!Pg#L---DhiOzhHnIZV0qG5UWGQ!)IOnsv! z8e%d+I^nl(f!{Q$^EyFBOC$lIf7nG3$?yXFin0}=OZPf)cM}t+qm<+ zn{4)18y>%bUo)zo=@U}oxdoxWvBOtZ6d zS!p7v^R$I*3V!Z9VRMQ_H*ZxP!+0xkj+Q&r%7#P2h?wzpFM&4tT4EIvKbY@OF`NL87%% zS|Lc_hvXIt9dKKtToFZRq?vR9*Egyg7=_%2jb5mR&=w}t2aH1cr6|oKG;Ol}l-jKL zbPM;HQr8y*PmI(Vf@ei4%GS}Iy0o|2QI%7}?|f`Fm;NE<+5zk|s!A9==6y==-)t`Z z7+wt+V{sFmCYAHlX8QsBwa7JrzZtn^@P(0U0bdlkR`4Z}YXe^qxpwf0BkOqzH}823 zbWRc(0&E9=F>-C-$&qUXpBlLq@ad6j2A>(ZCh)Y#HG)qXRS2-Wx@7cP%`BV>VDt6=YknD;jCS$UfQ@zxR76k{ltiRd}~)a3s+$e z*45Em?Y^`T?gU+1E4voH^n7V!xCnWcF0PqftmT)W{!zR%m|ucgOL@<|`twUr+Z8X> zy`b8Pm+D?n`{`0XyKw2;^BO_|U_Yg+jaiW4>ZOe^pLJJYI2EgBK9@GpWydt;{{(q< z1!k6y4(bYj3F>;g6c(*MC#dfgFV!;vb)PPc&XzjcCSgV378l*JYhj0odOG0-_pDtD z^OYprZ|qte4(=^oD{KI$_xvTOk$Qy4*XzucQ|nWJmj932Ny5ZH#yW%x|B~OOIv>EV2`(}N?d*w_Q|5`U_K($v$~X@ zM^;1J@}gUPfuUAcohiD(QJ{)@29EmlucZU_t5W^f(gAy6am=jjgv7zkv}h`z_oO@%j5+&F{)OcUs*f+e){c0bG`f^^6%M*9l$6*c(F#h zPHkO|-YphK2k=g#4g)@ClzXf_T&X)s?a0c|)VXvf8F}iE%V9awAv+}fqqZJ3;Af2L z?h!;i@h8$xQyu(<+WMsFT`D#_FaobMssl*94OZn)>vBHY4x;^pMLP(L(YhRdOzGu` zD@+K;4h=i{ovBDnH}x8@u=D*Nu4OkMHoEeceKyjVXd+O&R^FKtUG z->L$?XMqn=*w;~I3!dfdovZP7)T2-MxQypR#tjtO?BMV*Cy)sTE9ObQ5+A)W9NP6g zi#%F&wk%g?SpJc979{@(NZwQm`XaCzq3! znjDhks2C(qGH4gC{a+@xu4cd+URaU^q6n#Qww|;q>sE=CCpE=@ctu08cUWiP1fE%Rc-b$7x?tZHG$8JTr>Er$hCmK6uDOL z*^z4leJ0QZ744I*3=!GJVr4oyS< zz5DF<^S(Q4tx3Z5`{NJRv!C_$RdziS@IlVi!G}2420qle8u&2hs^G6WR{>9Tt_(in z!-W9DB}qkJ)Xe011n7EmE}b!y-pG~m9@*T01-)6zq1Qe`O}wE`re&4vvu6HEf4v2jz4tG(WQI$OJz zd|&{3f%O(|`eC-dB!sEAN^ zYgci`!7VDPtC%6&#YMHeXi%3Y)Zo5neflI&mMPrT)~98W zKDgV=o#^EWK>Z=^f_hB5TEk#Pfqljltte107Q6Db1ND}66^jD*p*5L+n!$x-s8QNg zRD;{l`n)J`n_8b21#T~MCwlo(fEpinLG@afSj@Gc4lQ=&a{+3ab`|4?JFBRc&jqN9 z5^8W)TA!y4_kHX0wBdeb?rbhV-4%C1J)~Vr&IQ=VO;P;i^9$;!Vpm=is5i8$SQNMw z*NWx>)T-K5RD)a7`n)J`>sp@|1@07cCwlol3w4gExkaQ2(|rv6pK>tu%`IS{GE=x~%k2>uT2$R}qj~Y3GusE7+Z^ zS4-S_wFTQ%^qDUYr~~6Js6({ND+x2jdZ;AKsYP>nO`yIXcR~Hox~yDKzl^(}=3AFl z66#LvTB4GWf7Q+$3cy zI^r&<{jJMt33XW91vSOG%oo(OVpkp^)cM-AWF=v*u-;-NVQ(s$%_|9YbKC`Whj#g# zg!!HIh_}}2hIyiBE}xT7!}WIk*Db}ca{A$QfzB`XQb zgPqp<{%qfEt!~(ZVxLf7jk}<}W?hya)T!2GNkE+wcR^ifU1kvKJH@Vi3PD|~UB&Y( z+|5O`{B#3#M?wwmF6)!|*QH!nGCy?9tVH5L>$TFc9`c?QM;te&7Pv zXMJK<`{3qiUn`G%YCwG_?t=P}b&17X3+h_yk~x}pLETvF%C{@1d$eoG!oWUcy~V=7 zzE(7wdxTn1pBERhgQ{4Ur2w_2by*5f>leH76ri@%t|e1|-PL-FDZq}mUR~I&k%!&i zdW*WSJQH8iFYH08Tc`jO&$e4#c`o@3^Z&19Y0j~Bcs~GSVQab z#{6$H24eHQm_OHSHGyuvcOxCpowUu(akQ5WcL(ruMvVs^YgETC^(pwh+UDjsdQjP6 zr3t{l7}W$0*GIe}^;d;2YAfd6pE1DEViOz>9HEb7MHje=QR9I;6Ben3+WVBYdB7aK zV7e24uNu_^u5WXB!opyBt2t#~a6#a!Mm2%!+d7yK4t|GDyouVjzJ%gnvXRHdA3}sv zcUY-|S-hp{$-6V)UPg5^6k05t|q&_Jwx-p>eKaJ5xK? zqFh}{=s+$SQdiA@LD#N4Oe2zlg%*Af_JiV2ICsP z*COgALE;xFE>32G*?84)8XpsFLAQRiJaNVAaVYuIEnK=wlS1XP;8-?v(%Ov zuU)%(YEz*7zZqkq296MJHKUX@bBC)IXNsuOLV zHm4}`lWKGcaJq`{B3`~R;$n=OSE2UMAgQ!ajQuuxc_>s^&BN6;6Rfz(C@L0Kw43hf*Y!~}JNOJQu zDZYh}`t#6(B!!x*c@kw|t=(n&X*6(51`HKrFJHr zoy5%`g-;C}vmuon_nGJyllveE{Fh$X{ZiZPpY8%Lu#3KqaKhfEU0gC|H#AC}V%K)P zyhq|LMkXwGEnGT?B9OZnk>aj}2ev|m5p1e%b~AS!yXx)FWV;RO;EDD{`aD=J0D(N4 zmh<1t{sE47EG>ee$2HXx2^LEpW?JkUt>KVgu`f)t*cbZ$?+;X9uGl0IAOS32fkSus zc_>UYXBkyI)QkzvJ&|YuNBAUML!sXuMQ5^oRfO+Y#1LiW9jZ{gFy(!vP!yDRJ3{d~gq4!o zNB9KigHBxBv51P3lXo=uE%t>I_rPG`$Zr3FzRxY}457EgX_{|>Qi<>q8H>e$Les^* zFxVCKmh2w8HS6IEGrky;$oTZ?2-_li_y%e(n+rX}$%jRv$R|1P*xg$XpR({-N_M?U z7Ps1MR1a(HNSnt!y!qsXrPybz*w2LGO%yMXgyN+N??QwMpOgs|mgYu%^6)!tvvURb zK6@$=?VCGlCAl@0Eg$&bR^*QR)d0^lL<;zID{=?9?5%Hu65BNH3bq%j?f;vht29X2 zzT`0sM?bXC+kx*dA&66++vMsTk(=DT0(a3gb3u;AS@OL=W?C-D(?Yh8oMm0|qAq8{ z5j|HUzEIoN-pc_GCz5v&;nNSfi(pWB-XXga@MJ4ibCyD^i&PjD&%Y>)3^I_%TcQQr zzzWoCQ|M2+BHP!gd%lR3eLXCzP}Fq1Zc{=r^Uk%I7hRH2IOaidoB6ng#@H46@Wu!h z`@(T7_Jwu(z2?Rzy;3Ah?`w&)lM^2Yi4;zF(=8N`w50;PFJ?-mPBnakoGJmaEGDoY=Cfy^_{Nz}jD^ApU-k&Kxjx8Y38zjI{4gVxFOHe3 z#kg@t?TdYUp@ZLIUzqQnnlJuk#ht9{k;S<5bx8n;(*!>r!FDkYJ_Y}vxrOn6U+*z~ zt8Hp)Ja=;6=y?a9a9uyU8KU&ZmNMZnqvDr)nb%{ z3I|Cj#)~`7e23#`C4b5mdR$XIGML3x5e{jwk5VzP#lCQOi+!R0ebxVA+R96C-0~X+ zq4>eVs5Ntaq4s%FlkaHFA6z9s|6>Eh6!=a&~d#|7O$T$F9Z_fPq_s?1hgyoq*XnHj@ ziO7;aG@hirM{2urNj^pFZhf{7)nDwA56t9SulJxd&z4r+wX#xcYg^6?wv=FYh?QW;Vz>UWVnnEQ)L@&TX3cfy8w70%SQ5FgZK#jacn>c(Q1{GLV(Lfu*H%C(>#Dt6_y z_=i%jXuD$R!ipH<@Lx)ov%h}Vax-D0JnoaUEtm3-aW5>xoUc7AYmbf72?j05ib)+cdf-zSSnxSP$S_Q~HRRO+sT8r*%>r=MDiNw|ls z&)vcOL;G6uG|vF)rML^~m10+3NT`2VmsJbuee1IFum90}s5jSE=P8GErG1`$PML{I zDsXt1(ko{ZJ6{S=Q>;t8=Uq@o7rXLwpw7{*)w0bb8>-g)g}u;vwd$?jpD25awwe@& z3zYVxV4k!d79Y$j+EbYDQ2)}d;zY}f>i-XSV%ed1-=k%8;|zqz*Y7THGY4 zelf}IZA99EpEs&GLE#}rMK?mB@jL`-h)uci0e;GCjR(5$?d{L8|$CZqKM3>VjrfNSH-^2$2scbMjO`-PW>lp&@%eO zeTP$=4LQGw6p)xCCC&+ilDbvoA}^IDv0$>83a27$7yH6AuMJ8mf7JeJ&4rv;(H!G4 z@eh44vZ;u<&Ov=?QH|HFpR-7EACETe?EQF{-veriYke7wy>-%_<&&3~K4NpOgHxez z|Is3TF0$R;p*Fb$mObeNu={HVa@vsz5r+`%Nt^>eq-Oxp0&Z+n=R^%5VVn7%SW@JI zq2lO}6c&qpgpKK9UpN-0YmgUeE5#{+udoE7GGD1(++NAqlbL;kjaP%~p68tcGL(t5 zf2_8-`w|gsV^F9&)aFtQ5hR9+b-*I(#lEmcm0HfMrET_50zAsO3i!j$Rl%d3tARi6 zTpM^x=j!0?oErn4vvH~QpFEV9J&c13l*}GVfG>2e4!+pAHt@HdtAW4cTort|a~1Gh z=gQ!DqYDA%(Em?GzgWvTc_;xSZrRDMP_vjyY;G9Z*bFs+=NQ%Vo*MedCZ*PY@|5gU z4etVN6A#X?eUKSy2NIE7495SkLaf(;-WN0w`SO(HJf=y&HVd-}JlevXMC8{wRSN#w zvz8MRwO#VT4D1x`UDf`v1bI3Gd5m`2U5E6+9cz7h&m?_tRK0b-Dfg(-1vN^OZ*|FC zm~=sXtk{)nL5(YRJaT}-PTE0{;wUt9-+OhCrHvO|5$@EtdJF3&z33- z($8W~P``@1p#IysRjzwg&T319&q0)s^w`z-IP#+`?dAy-Mg5C zd$edWj|A$axC`n{>k^B(7SvnTB{j~wphoCQEtC;z1MOP!@dNDU)?0l10J}%gZ0-^2 zvvC*Hf!1XyKuxqRO9AT3#jbozp-$1RB~yT9w_5TEAne?vE-X9TlDe?$!MapS!GC+| zCdKX}8$8pR*|>bCfthNEy-Kb3UxIad%cX9Kyv z1Qt#VK>kgR%7IN|qnQ(WriZy|I8PyJ) zXjF5GLaZn5kVD&f?6LXrnE#xa?*O{_=nnb2Iz;oemGJ=HVN^TtQKOpADSXYS_EPX) zXgd!*Hs68yd(75&pquXqte4(XY0jlG;W?vmjuPs8Ek{)sqfnut=hYBXNv@WsY&txb zV*Hs<#lCP_75hRTRXs^@c9#`Iu&-#BP;8uRg4sPfm@Q7kLgOsnR03?%|7ikSYm{7N zNF5(g3b!(M2=>IGtIcqcez9s?Kwz=hhw1+`5jG=FWD=V_rc;kb7`Rl}oJY_vhW$Y% z3H#FxTD|O64E#&yD&Su^R|VhVTn+qN=i0z`I9CVXV_z4&&e@C9_*G z@XOBC!Gq4VfnRm527cYSD)?W{Rludq^I)>VfdBQeLV)4QprZF{W^yYAByPFy`nS5m zRN__)LyOH&6S&o;xmS5)^j(ebC)&!X4ET4Wx`9tCmHmr_*}n=mNi#WKThRu7)2QyD zQY0KXhX8NWyyRb9*-U*sW`??!ZKwl7Z&`}HK!#5C0m|2~6nlWDY(Z^$f$OwE^#I2i z)eC&Xs2-r#pcm)^7H#S;>p-n%p-%$#DK)%wY?%?e-(sHxoTRa5Vnn*5hLv$sw3Gys zx&m5RUHKg8xLrLhG>;uXCPezrnqZ5N^V2Ejm^?X4OcW?*!x3LAj*dXLx3aYZKW|jW z{t6E@DmwXxw(|&L^WB*Ll-X(m-F)}c+ROCI&2cosCVDS$6{DKKPZ|}UL3OM*)>hsW z_6>0GaU&DI{0qlU9``bgFW3mT16NQ}BDI!6tS7GhLfd)LvH2$EUo(&0KsVn!Rz1$r zHaEx7g%(;9c$rbXz&{ujeK2#J4$%*7AbXC}crLNHdVtL0R;9X9eSt(^_NiNF^%R65s+xUB=Cs&oDDtXZ0ICs!=3hKo!ED3+xojf9I&R6 z-`2zys@{0M8CR%x4W<2OJhQ%B9xAbs?B= zJgL+=nuXjW0*O&(!Fg&HQ;CUzp|NJD2_!gqL=qdCG*m=r%hr99y8nx|LVZG$8n10) zgWpjjIocxX0p6)pcACfds$1oe$aw&Gx>A|gZxlaX;Kr!)L$ytv=J!%pJ6Y`A6amk& zpFgj?i%qHnNTwo%pPMY{=!O_~*p@T$Z~F%@kFA`=9HW}E;dp#X6W&ML5v3V@0~~zG z$i&?N8LX~Ca`pzUZB#pOOQSl*D8zbVa6{X9?6LVK=GQm#JwP`fUC!?x&7E%M&dD~N z+JP4u)iI#($3{gL2chje^w@k4=1((QO`w~PuIg@8>3^D57ZdI$HoLlj%#+rr9-=k6 z&}!Aiv>$YeY4c5+h0#X-J@uW~LU1Ts2$P6eDHOJul8KGxX|+AdlIbBox7|YuH(A_0 zq_Bm>-7{6;CEBJgG#**5T<}M|ByjwFu=G~Zv?f@3UBH8sYTY%)4!hphT-{+ei7l|4 zqp^A?&M&r|N(S3Y87nqDPGho%eODv7r%-aRTZ;Czk8AFQ8MPe#7pjv7%-QrZ_!;La z;E!&T?|yRLUQptorlg#=E8vZtD}y&LDu=r$k-Vv1uwWi0Lfi$Iq#ht=umIJ;)-8OI?v)u*U+NcH)A0tY~_6>x| z(D_}}%WiRRR7_CAs%eYr*m!Ky`8Ip_i?TY9_L&I+;FZ8zG}NXVc8P~ zOC0<_$!t#mU+r8Se2sH$;A@?$fvAPy`ojR)RGt#c_e7snYC{cNsQnan8Py3Sb&>k1_C91%QRM%z zyTjc!@JORNfgda=%1Onu5sz_L%#1~g1D>-(-X|-1QHg^~mCOXeKPU=@1A~V#>Oq!1@Ho|z z3&#r-Vk&VhiJ|SyP!o8f8S07+MFWGOrWtAno^OVFff&qw_6qhWqm>^UKM3P!$uCUi zM;jD+TUt;Zz+W5H69*7oCt(P$^7nFpeU@E!Y$&>PiVYnQj_o9k>|l$q0i0#NCIRs& z&me%5MAkuQo2>72+rY<+in5=lZhx+AHci2=J68cOcCHLw;gfkRqFV{0!v1emPi8X^ zlQMPR&|WMhW^+ZItmV$i;I*BrfJZr31#jS74g8NC^N?lH&K}0W^GZraHSi10RlzSg zR{;+?R|eO1DmdX_Vt< zl9L&hsH;#I{fs6qioh=!)jcIDOBBIO^*o^Milt*qBS**gglg$HZL<{!KF_%dc(!w8 z@EqqV;JMC~!T;r41$>QjW$-=DRlxT;H)IZ{sEo`mAYseAIbVB;P$=NVu6T(;;u9(0 z&s_>gbcty>Ri(dST1_HwcTL=3P(G&(rxJ!1sG$+1mERxWz{?Wf+bvXu7qrcW7JSZ_ z+>LN`@NDPG;A@?$hk;@LP0MgRaGI(~UCvdAr9`PPG|IG^z?nvMW9TxYnn1T5offgZ zfoZh^Pd2J6w%s1v?g|S~QUe&2_n*LV|F3LQ2$>i_xBY*t330ojI^LElymBqa9JIS&8@TCQ4cz5i6+FSY3itr$%HT7U zYdH?*9;M$)OO;)pD5MZF(biEDgeX*KJuw}z-7wotAcd0Wr`RS1p<>(7_45R^`Bk&s z05ULgO@ZxwHGZLD+e1!A3}&~iv(?Wv=CA8*g)4k2FKS}ZVBt8^8Xpe8>Z(24taOGV z+iMru$Ys}9GCI@-e9qWVZ?fv0WTvBQB~0%zw8-V!Rkj*N0p6%0A2h&4s`sy8^^rkBCrcCG@x+_@@vu5&f;RnE16NAH>!xwV!!*h0x{_W-v!R|9|Cxhi-o z=PKZ>ohyU)RW7-RslYWFu`FUBHf2?XrV{TiFf?F>n!t~005Z#CL(v5ShVHT&)(2iv z{E8WmCekyJNCRg(nEz-J-=SJKlRF2XN8ODfDk*zGxL}BECH0jpeDM0lZ94$Qdg`m3 zhRRPhz3FA}#=H3}Ds#}LWOnic-@1E2FpL%t=cxx7b>J#$O9pEbg_uh0QW!eM3^jr4 zo1vcAP)``qEH&_`??Kf&Et10+)v)%wMG^!K0rxqd81n;Yy3^lN;AU07kNoT5I zu#42wx@M~LN`*glD%7fJ7hdJtxZ9>jwil?>ZQ5pI3jV!w74To3tAaQEbnaVp>)_3t ztAaOot^%$*R|ZdZt`0uPxhnWz=PKYsoQv*3$@OoVgG|G)Xox&wB26G!$mPQp>V!ZN zR}>h!*9^4-NnPeQhKN2f<=0YQU$DgHbcaztrA}ToXVK2HdSFJTV`ycniX3B}Rc84xN{Zo-<&Ih|E*jrKn@nW;27|m&eg$hJJ$w&*SQ+_edns+VSAKX|49)l zhMg-9SG>2jtpIZs|35>1yp}$(A`tnOi8O)KN**;+4vVfdDGVU-KpNW@n(cNVVTU%SX#g=On<;RvO9Anb*m74==?zS)1Gv3W(e{bf z!77ryQltXJkvhB6cctpdyR2LLiPDE9x^N|wLcvSHf4@?bJ_RPl<2@-*Dur5d;V`#< z0L9&O!sIMRd)SN`4=@@O#R&ZoW7u?U|d% z1i?Ef*Ag7c!!)&)I4uywR1;|eUofikeT8^S>@FCZVOs6LYmMrP4MpoAHq?QkOUzJD zY$!T&#D=2X{Zuu0h8b=E-!Q5>_7&~hb5t5pd0_H|iVn>>Hf(dg5RMRfkDIsX4wa-4 zN$owMOyWj67KxHzobI<^8bF*UcGO5 z7?H^)(gY3|)p?^ryd|bFhK@3=cHmV;b;X9FX&f8sz|fD(PyNe2K&u$ z1H-pC6_)knKubR37>0$*Os(fWg%3ORq(ZM_wD?MUrh3Z>?bc5P`QP2iV}>YSkvZ;2fRU!OFscHmcy z>WU2wSr)YAx1+$XnW663P_(1O5k+$+7C8W4$C|I6*jF@9cU0+pOsj#`=}v{?vuB`{ z{he+sOfa=c-&W|=k1m22smQaY6+J78MWXKlVv*=ZCKl-kvx-Hcx({lYt9~X|&Za*2 zXys(;FD!9zLO~EbzbF_6frqtKC^7XBshCI;ILWBaV-(^oG4(ODk!iI94>77MHWW?$ z*iZ+CrkJ7b*iba}H&yAnX*GaH8P$WKQ;li>J=sRXr&Z-N`9jG9S!Ff+Kv=MsXMDH1GyD*xzOoc6}wv7Tr9eQTAf7@me^1ShQ^qo?$}VYSYkube)YNrTQb89;BH3s;H%xJ2G9!B6cuW&BJBFnUrT2G;w_6c*}wOFAS zB-$4S)xg@OH3;?Nul- zZxE@QNE0~2sLnYG@s^l37~0LW+JUDT)fF3x>Kz;Ez|h%ds5>?kO~BYtG;j7+gI_Yk z4d8i3_2BDrqZ&XjNHlHt8QS&OwjHfI452L#b;s2{msf5(EquluqnPo@ zII2(SGcK(8nHu1Y+RBTOdHn-uctN985-Q5~w6RvSA5nAM4KE#6LhY9}Gol;YgjzXP zYlO*BjEk9}t>id*P}#)(iy|Va=iRZ)C!@DGu}pn{<-!_SUfZ$Wj*-91G$rDecfc!Z zRdJX2SOd$esAxV1IC8~k4xorgDwS9!4ts$*epuUV#|6Kwoa|W(N*t8Fm=AR(2)=i} zoNEb&GXW3JsI_d{Qn-TJ5^5+d7ij|VmReJ>vv&Oc+x&OO{@X+2iHDxC?T*+`2Zp9v zZe76Rj2ge4R*YN{+a_`2L(-kFi&{WEf`~8{fGr zbFe_kOb|R@w-1>Jb5{<7z{6weAu;NR{L4g|z#pnRxkmk^LcAp|TQRh;X|)5tWmH#e zDB3k+Lme2xz1+ero;Fmn2+IR{U_oy%(tBT{TU~clnTIunI~&yio@`Y2pA~MULy(xj zQ`9zTOW@Z}$02$xavwEvtc|BghAA#mb67}iLAb)Y>~BAtK7jeJsrj4@?O&uBk(mrL ziW4l!=!tKrCAMd}M-+25^mwIuyjfeR#mwpW{H*yL5B#E+XJ%BMgt1v>49&zTVSyUG zUE6GZf$wsz0>0b1D)@fqYTyT)YXd*xTpfIzKDd@Oys*T<@k&ZCb?`Lj+Q26`R|B8q zTortZa~1GR=gQ!3Dc1@n94;#Lq-K%WP-2nj;eIUAjzKb&%aj=!2mvLoTw+5V7@|C~ z@5Y9rJtsCa0Yj^47_z#76)Vvx{R13Ody$JBMpwGA!vk>0;K<^O4MmF&t=DZt8o;$I z@}A8U?ru~AIK`+Q+?``o1L%Vg-4GDLn>MrOd|?H(fSQ~k^;*DZ)YdA~&^GmlYiDcU ze3O}81|OwYXfjb3l{uKIWVVFCXXylN3A+Cr;o%nbATt`cf!dN0{hGq#ojPCPZfaem z_Ed;Np@wqzBXidT?q{~UaOZ}i8B5%cn4xyyK4z#hHl%$XS8S*QLzB!s7CklJ93dxB>KELN^uEjJS_|6i7qjV4$mt5WR&C#D!2VL1FQ2dw; zXjJr2OKi`ZJ`gJAZ0PZ^z@wb$=1oWSqvlhG)3woiRrN)0est!Wt@`t|&89#2D816p zHuN$FQxS|{q}N9L{x z+|O)x;m!?3Lq^<>n4xyyK4z#hHl)?|GQ@^DFf_>w^~8qab)u1=o%oPsh8jFq+w?lo z&Wp9xb)rXewUzx0`1%rpt7)kimt6g@U}$pItB|7y)e;tE^@NIv`<+;8|5z(}#q)UV zD-0(VS*8`4_o0bilf8V(_@YT-M!SkFO1Kvf8EZfDrl{wf|NeOTacsJ+j;7>c(2HwND8h9_~ zs^HH!R{=MiD}#4FFdyPpfZ=GfWn7_|$vOdUq_*Un&V3Yq-6kej+%TakK0Ar2EC zgW~R&=B^1uOQzeRX+wqipy{;(DX&}?6DFn-m&ycywmh~4q9p-5w;TaXK+o&cO#oL| zox0;Xb;oszua7iOd<-ic0A%XO&VoB4Pi!`b5Qc1IavRK`4eE&HY*_SfXcp*-`A{+8 zG1L;rG9Gs)1{8nN1^vrZvK;rVOd%vjBv}-%O)K>v}(!B)f zN%w}v>uTZC+R8%-Af-(_Vk4wmZ6F%Qr~wKkhHy_g5)V9nr3MOy!Uz#zSY&?;>nT)N zO_^o0r-!ES;-(syVicAVBlVZ%G<*60iqtk|LywmPRmo?>hodp3RLr2IPN&expVi4? zZ6)`a(^*a|aH$dSr#j`h z(akDWK5Mb)1a&J7?V4;NQMj>46mBdMg&T`R;Udy)u|(lc)vlQ)5``O!MB&CFQMj>4 z6fPpCSS(Svv$g9o6N$o&MWS$Hktp0)BnlUiODvWs+_~EIJ8iT38gTo>eA39}`+_nD z6O_yZ!H*XOeWK+E55Kn{#sl|MJ-J-(P>89-6+DJ+Geb?_r_4}yY$&>%!q9VOs2%um zGt?Oyik?WshB`2`of+zi4e2GeR|=7fRZBLz#2>s^q`gmSn+*Z@Dd#HSe>hhKzu;UA z{7>iFz^^%12fyRo81Qekx-v%wOB^gvQUV+UzRkHh_zvgV!2jc14Sbh#Rq)-;Rlp0K zD}&e4$3LwAIapiCEI{xm=jz~foofSEovVS@cdiQF(76hDBj?KCamuv<^eK@ehJ2W2 zCZ`r4MU)$%d$gCZg$k`FZkMpVScg@vCx8r^+{R*?G6@yij#dV?SGTy^fef@fbHp~K z6)Lu^7Q8aC?P%kRMJC{$`6E~NxM#$Kirq)IOV4V#FepzCf&a1X{WaOct!xc~eAX#1 zMUIH5rXtx(=}i@X+|+t5R(QEnp{4)Qu3JoGc?LVwlHDu&)xFN^UA4_wj>al06B@`u z$OXgDu8l)mmdNrMl1st(SNEYe5lZ~(KFH91bsr`<iaa4jHV$@MJ-+S4NJ z*-xR*7)>Y(H~(*2wfP;lefZ?O>wIk!LH28Kv$U1{Y+gSH9#d}|Q*RuTdd{6@|EB~` zsP*ha0x*eZf4&b~QwtKst$iF2MY%NRzxys)Vf63;Jd-~gC^1hZmycY@xojOe+nItX*W7{3E?H+7@(rov{wxj79+m3EQpU{+^veX*D4b4|4?mdsr zyfXg(H`TyZs+E27@^i(%D{!unBTSP{qWmO{Nb0-4P$qE;8;e9qFmy*+>J1>y6GK=! zI8S90ZL>KH9&oM>p6gsWEC;qep)xY9foB^P#f{cj6KMkH7!^%xwDvQRcHntNMJKym zwCi*enG`0`qg@A?NCW6v(QKWo(!bF*D=3(QaxST;l5v7u-HFhm&g&=$D58S04*4Ou$0<@F};JqxlkHWW>y$?En* zZL=`|FIGaTE-*t);OERxcWfvgB{guq8EOZP zHA9`Tp{Tw@bd?$E0Dj2~^~8pvMHd^IfFTN!ymNg8$L6guOyPD$^~M22SAMVkzcRE) z-M*=9Hch}^|BB701!WGVDwzp_uPX|M^9c`|sgRu3fD?@B3{53gJ%&b_RueeIsP5QM zG@mfEs%fL#%7Eup|#u?QBQjx^P z-lD+vaQP>aFz)HbvqIGnNnL7U8kt0i5W%C|6arl<+EA$8dY&oJwW2CcR_TMa&88Ok zFy|`ZDb7{FM>KHwv?$zL zN>pH$D$3>p#J7ymOWKR6#IRxLR5R2BK5A4a@NuQ&AMNcQ;D7?i)`i3OT-BhZBdWvn zV0&#v6}V$jj)Pv810GzIRga?VgDx#R&d3bLk1zzs!HcCI@9zP8!q17Gc21$?b@ zRq)T9tASVeYGJ%NSW!uFQUi~4t_m(YR{>X?D}!e!*P6{7^toUS-0xf!e5!L5@GR%b z;7=W1$RwP)ly9DvAn_0bkvmMJ2_#>c$t$a8q7&*gr3fG~-?9B0v)vA)zA_oHO*Mpy zZAViK+xMC64j^M9xBA$of~YiUS{0YU2^6_vMwXsu*g(f10URcW-cAKJ%i(KieUwQ{T$ zWmeNvH`X>=^59*ZtAKZPuB_0SnxZYVmH7`uU1pn!2hv;xW#{(rzp+Z}p=}l=n8_ks z1w6SR2gYh92R^PKr_h?Qiu8y1rNnBEMWWG+MWT_5MLO_FK2kRQ0LzM#sN@$k;-j=p zJiNvVB79z|8rIBIbh)-!!NI!^%gNS8%fVPBGePi#qM#bGW~QQFXe%czU|scONqt%& zrV^_RLob=34zxPWQ1s#@)|wD&QGT^tD!rxXJ@eH7e#(6H05K@zKqUK{R0HU#Md#y( z)y*T?%3E0Z6CWJ?&4L&Y+|tZ<;vCPpE=TS(T8}t=RLu`B?J)p;(zxSFp@U3LyRDh5 z@yyaTn;nLS=VCIA7L(G5y5LaR{@W5t_rR?R|79_t`5G# zxf=MtovVWHbglwk=v*26XXonRzd2U}Kj~Z*{FHMQ@N>?U!LK;S$;Rdwwc9~u61zPj zTboD|I7X=vrQ@bVi(muo#B}z}G?>!KZhn`Fk*QEmD@EXmJ%6r>+@h^a3$$Kz%FLSR;oeFIqtX*F*kp_?nAlHQ$V!VVRZqGJa%!jGXBej+5x#)F8 zX+$1TP1|fM0l%zVYtP|eupkKTULiNq5)3oM!>+2Bm@9~U(nOlTu@*yOuAsMt>9qqF z8P)wyg+wXE8!ED!8bCMH^{)1gGD8iZ zClc+%2dY-D+XfzDR0ohqp|_n1tNnHDI!;@em-;7Y zT(Ycble7F)>I%5SN)nw@5^7b{yll}bV6;AMmK#9Viq8F$Rr&;Nv$X?0V#PccSrQ9N z986U*69k`A6!iRagokfiT+yRVL}r^v6L`5&$$b{R(@d`&c#~1xz<-*lQ{pWfQ|Fmp zw0cg~E)vOB4|sxBK$gmp3Moor^?XuA8m84aMd2Avg@ydF`oGyiYyj(~)rI?$oC*z5 zh}YZ@@L<#G#*pU`Z8@*2fj6~HErQucq7-v{GjrSC;ZiX5gE8Nbh#}z*9)LRPIFn7_@gRcbcsB!jJc~#dPB`Kx-!*( zSW5g_lWMJ3s0`I&oU;JHpRwT?Pqmo6nHZiZ!}7)6`>I++UH+ZEaIt+?10>^BO0WKk z>sKPdYq4)AJ0v7)4fPK;iEq$=m`74?3|(nOiWb)QwCj&1GM=PQuxUR2429ozD$L`m zz>u7#PKYfvVoOm-VP%Jxb{QZODw=U&1fkw4r4ir!PQLge{bFBeXQ3wfwzktxf4mfE`7wS|c^fMbkm-(BGWPW38efaK9%I6nOO$M36X zHiqD2rgJs$Th3L%?>kokudqrUQEKiG+YPEGa|ejJ+>ilBsHMbB;&o~O zZCRUT@GWM%3;3E*oxsILMYAqp=ysL9Q(Jk`UI#D5QbjXZvrrT8md;hcTR9i4J8Z3| zGEx;F>WND+L~3T42lzg3>e{!R$xJVUcXF-*-r2b-co*kt;9Z?-1MlWs9lVEgW5D++ zC)?7zVI1675Cq@vTpj!e=i0yzI9CHd=v)>2kaHFABInBBhdz`SuoWN&k18p1e+>Aq z&eg$>JJ$wYq;pm9Q_fYuPdirzPg$c7pbvJA81m0)W-^(8u9x_+QLNWQk7CJ{ z#lvcq@PrDZN?d=$wxhl6$W`<3kJVPL2!K0i31t8HyuyZ24IstNo|$RyXswe_TPvgl zLWPCRYwG_g@S|me8fr?g!3h`JCF}ocC6O9;sCkibSOoa4KUm~P;`ofS>cU=tGfx5U_ zcb^;-Vp$x;(vnOBvp5yZ;wYGw`cQ>=x=S5!p!VX6lP}s2Pw=)v@n?m zFy3X1W-T?8AL=R>!NEQb3Ug^zD!y4CD8B0pr2-e?oBbcfH#-&I><<**w+r1 z&vvd1e$u%b`0vhDz)v|B-8&HP_f#fvM|FyJA(B0m2je19iQlQ(pUiSJWY=idO+$+e zYS$~;X7vQW?py`D*tsfrhe|$IM7Ite<6ITIvvU>jSm(;%JDsb8?{TgQzSp@5c%gG; z@L!#)hqIde>nbDT^C^Ynom#e56RjT8YTl>tA*Vt^CGGlrUSNhM#d$;p!R;8+Y5?6(H-_$U zDd4L{ML`Z|aQAtTVU4Z}M6yYV_(ZeZiGLrE&d}1nD*YvGQ`ZflOyU|L7Kzd#u0D&e z0rZk}-mAS{pGFum$NHULDO(<3{l+TeDqwxfnQ>L{1!_dN8u$|Ds^CkVtAOV^R|fym zxthYL(acJ+DuK9>iUO}tJ(0RnA;uGR#|Z|r;oDlt?afj&77MiNc5Sl~g70#!0>0b1 zD){%#)xdvrt_}R4b9L}voErn?Yu{`O9LB)|Iv1$hk6j4SieR3Q!ZUW`UwL?5phYK-ZhIsLvD>#ClEih*`GLM>QNwC3aQ}J)wr> z#VPPMi>Vv9z8aLb6u_}Ybp!bnSfqdyS1vf?poW}hq$Gc>0(grB)qAf(B56G;4AT`Pp#cj8TO1{x2xNZ$S?Ua<;$ePN>K|#Tm|9s{U}lzV zr7#Y9kv?@J^Od z16ZizS^bF-J*z)VAu~ui($V&JVLY{RPDr9 zYd6L2!@LkF7#>Q*lZf&}n_t-_VjRcOmW9Mq%(au%Dn>kkj^EFmBwZLCh zxmJMTY-di^b#BNVI*^f@ovP`6Nytb0Kv#LKORInwbokDIg;;v!8=66?}qm)Dq-?DUn$OGdEg- z957`wK`W|&`nUpfK05^Boncr2IL%Fq?sD{eC6c!VSfKej@Z*=#xCw7UP{~| ziTJ9*7=Q`0CCI_jVr~fz6*a#L%c2Hz&X;R!3d~tnC<5NT5F41eBVs`E${c`6PsD%^ z7py(1n7Jcj!0qiMS|0$9F>Y!J%%&i!LuFnmY@A>=Sh-~)^E+)X?Im-5GLoePCSB14 zl5INkUNR@!ObkrAVhu=UnHbo!>>Vmg=9+!pDS~i^S!xH9?QfkTThb_6fICq~YFO!D z(s^2`qBtD6h&ZrA2d^m3x4U0X^udvOcDok-cZY6`%x5{jvGo6F0XJAO6jP)^Lz`$%IBB6 z+?C;PWs5z!Q;M}3u~t;N*VO;oIu`i@6h<8b>BlPQ7Ou&{^!}yh5y{d6<5JSYA3v{3 z+=s?mjaVy6FPu+9efb#9jiKbGYWhTm}5Pb5S9P&3`@- zeP|VnG?B(b;u`FBb@~@=6U)2A*R|Sabs4639p@_GuQ*o&ALd*IJk_}}_+;m5;8UEd zfM+-t)rxpGQW=>vz%7*;QJOkA+KSK{X?jf{Ab#-7 zM3N|2MT1+j5F~gr=PKY?&eg#DI7*6L0nc{1GWc@mYTzrJtAMX`E-D!DZmlv>FyJmq zB@2e$x~A6z60%h1TMsH0LCnGu2YcDi1EyEN>foE58v|Zjzpu|8^bO}=lz((T2E2}Qb?|!5wShnE zTn)T|b5-z0&Q-uQ=gQ!I{i3H8;7}F4T{Dw841COfx6;Y)R&Lc1k-7NewR2OCX)Dx$ z>gafF6Ek+My7_~-YXUvYIkTeoqr~#6#nkwOe&E~GRwh62G^08%Rrr0Q8o(bK70rL# zy|3iq6%~S-cXDjXK}zSRo*02?dOwN|q_A{H6; z*?l`I$?|QHL`jI5_NR68-P?A3(I!x$Ol=y3$5>}F;KDw5eIGHiuVsSZx(-YB3u!P> zO32suaZn5yOq4A_4vHaz3wy`)(Ly><_v~Y|&BFV(;;Xbxj1Fp~EJ57b&rltqL)Ie0 zacxth1$^MaXp@)oH zw*BoB_O~Aha~NLQK4E|R$o>I37W1@CJZ$15P`tDQf6Pu>J<}DQ?G*4bqZ(m;b?w?! zTdDG3-vCDkn_?4q$`XR4{i2-(n?TNnxy8`$4r&IoE0y|6+iu!l+9b4S9ifF!2Nud$ z7#W&K>^^%2%Bz_2=yI$cRqY~8mlY%@fcWNQrk-j~(>A*+1)t$u1$>rsRq!n5YT)yo zYXi@At`5G;xiR1a*Uif)H+_S{Ihd%V1ULrV?OYu^$+D6qmuY-kvK1!i!M{hqhM}P%G{#9gtoZXVV)lQ~S~(NFAyjOU9st_>hjNqWFS=@&$U zc8{8PLfe%}^1~TMJB3D{gJzjr<0wlFIVU@yEhE{H64|=w3X2WGMD6L z8d!+wnkc4iHI|({CSXDDxkxXu=m+}}9tTs4KK?b&jq3h?Jd7t4zHQWmZwIB3=O%;M zJPPacW8Dx5GFz?ePQ;jT;6kPgXK=t~mD&za{E5ZZI4y`Ru^Ws~>6Kgyh*Ozw^OmR9 zi&M2;oT_#F1EMdklh^A|ZLLXKk~vn*%0@M>Z-Aq-EUhN+&Lspny2k{^0|_v<7*4eTRrrA^ zMps^hc7%zve?QR5Za}es-^3+gY^f1zMgE`GnOQvNfe+H{Sw2hpwZabFY=sJU)x^h3 z7-#>M8?4jM(wjE6Z6`si|OAexHo2p4Fm+5H9hBIhdLOPs5MFLka4zS6li z@LcEW;A@>51Kxk5yh?I^v1m946O_#EFTe*lR|ijYt_|GdTn*goTortfa~1Fu=gQ!p zZ&C;_98oHIk7g$K7r@6h%!TC&^I$c5*hh>4p01Sqsg%hBM{S(%*rUQq6G>1)~ z2P*S243uaHMOMgjeQ#@A_-&CmFPM8Sx=Toy8|?d{=29>>*q1ODUw^!;nmr$-KwZZw zJkh8I@M)vE&eFm8skVs|=lWXAF;>*ZLWR#e71ryI8WE9Y8`!8?F13xe8F#rN$V*5j8i8qhxK_{HL90I@S<{yt)h(|s8y0kTu3qa zTkG_ZX|rGk6Zvc{oag~xG^!g&S>==lEXrI# zkt#Q85CuQ?rTpAC>J?d5=*NIe0T{#j{ahTC)wU)PHKlK{n)tK1n}0atZGF^h_Q;SN*m3(FAt$ky15fzW!+hIVsy zSr)Td8rsXa&|1bV+tv;VTRS9d#n;vwX~o9-QUtQ4$jTUuR^7&W0;$`HqN2~4i#7s|Cf z1};(qE48|2l=SrKmi7&7tl~CChDH)o{d0l(Y6bPt<=8+p>lbK5x7+1}{IMj(e{ik> ze%QGx_fqHj&1)f7?X!n*u)30R)m{gG$hkIf#km@IE$6D>QO;Gs zRp-j!O_gf}!@*`QSO;(JTpPH}xf=N6&Q-x%IadL1?OgOal@WSMt0#8`z&Djje$$EG zpG>a_q%QIbdmZ&mbcwgxeWUXsx>Vb2>v&%=BaygVMhy@0#pc+Wdh>kejLnVv|0k>I zg5J1<-ZJyA*$;$dp{2C^-nA$UAnH>j0Z*3acq1wt7EpUcXvKP+o z5A}}HPL{>6(*8h-lbsa$B8PP?^LB-41>VKE3V2WFs^GnxtAY1+t_?iSxjOin&GJ-a z9nBlY!Lv$ciyHi#b8X<~ovVRgbgl}1*|`dM(77`B9pzfVaPY1R*1_*N*9ImtS*#FZ6vYG&O>@z#RhHX0r1+!%^3$~a1#T0ohC_f zWlsX4EyFmoe*-nUyS5?+Jl3e_UXq{~?DRGjN~Owz3_)ez{ezd1UP5gwYDZh#-QkpZ zFBp1>dr`c-R&-gdRaoHQk2+TYZ|+}S;fe&`B3jVTl74Q`2%HZRaYX!r>@h(^gPjjvfe1dZ|@JY^9!KXM^0nczQ z+Flu<9khC~y#hb0RC0SoZ&TB20;!AarH86#qDySA52}HGYMV{%FR2t2;K(ldbM^Lv3})GU4QE1c=-8?uQ~$sbwC^#Jk@K8tkvM zm8}T)N2O%)hfBJ2Tacp!IXB*-XKIyC(^f2%!QXJM0=~exD)>U@YT%2UYXe{ETpj$O zkJ*}@J&c1jl+4yNxZ+$JcrE8@;8D(1!RtC#0oR->gSS#n*7WRQ9Bl1^b?`RMwSnu- z)xg_1R|W6jTm}3|=b|;u2+h~($(jbJfR14Cmmx>Ke^#S%-?LE11Qx-nfKbw5*AAu11`#xM3P7b0)F-ycoDkuarvgU$30(UW-Z`{MZHS;Ga0x2A=0! z4Sa)hRq)T9tAOV_R|emvTq_t3Zg;^t_zvgV!2jc14Sbh#Rq)-;RlxT+R}S|hrpsYE zl(Llqrz@4*O40kG={122lI)w8t7l3j)Gw4G;Kc5V?TKc)9k{M)%WE)fGlYp3+a;B* zYMZUjlN6tBmZJAIsNo@7u!QZkjQ??%?`gb+Aj5Ja!orX%Cm`PPtrmaG6A=UwLV8c0 zr$JyY%Nb*6rgkax=3CJlz)@DJXtzOYDJd^dgMW9!K*lApk1tf8kDBlCVJ%~k=#l;+ z)qY`UE3rsqWl*)>9@a~1F`=gQ!#ovVR= z=v)On&$%-A7UycyQz$fB=GY}CC@YHZEku^ASICDK0|xS zOsH#=B4D9@trT%4=E;#Ng0>9hlu#|>!t@dWglY+IXnjk)N(I^mI3NW1B6`Zm(QO0R zyGw=P!%)uCe(cDb@-{FwCP$+=oM4&?um<|;ED__k8X z8IRt>rq={g0+~uXsAn=0YJyS(EYwj-5ocl!Zm1$?XR|w0OU?L10HIpK8(N=MudNx+ z0UW;__1I^#o=i5Z{PtjC(sjGw7-oT_a$k>{7gfZi3_}0spP~(?<~`60!c(> z#5(Gl@Pyh-DWXcun`hJ}+A24bYnaB;TVQv_$rTBU;_7#SMNWt*3M6%DS5U19OH4=+Ni-8ozXjUUiN5_9ypZpLS zE<#|R4Uug`cDIVPhlw?`>j-VLwFv(DCvs_7cY`GkrYe~Uf=?_84&~t@3t~L*N~My^ zgvgFFy(X|*{mCQ=LrBbWLYZq~nMNqHNM+v8 zc4TR__vOEV%C@=WdfLiHyDZggR>o@47LJ+W3bI`Xyn=HT@JQ#X;8mTgf%Q*dl*C>e zcwLv{=c6V4&OMVG1Acy3E+~_0?l2BsP%;w)zvx^Y{IYXx;6dkV;8&fig87#U#YYAF zhRa2hRr4vWsX!(xa6MH^{NNrfeQRBkZk2^Dm6yJ#}KH?|8R;ufj!65hU#>?IH|uKf9j;VoLnwx=z)@ z+faXJn}4M2Hx|MqT+{01P-#acWSIibF{&47+U3*2z#KEw0Frqkxi6~pS6mBty-|~Z zw;I&|G8Ty+6wgp;7l|g)|1D^=mSR};i`IXmwUrfH2CqCK?`z!$aIlJ!nV`n)jT)(I zE6W)8FQcOCH9|mCmNBrT&Jx$Iu~s`;ul#?ky?vlvScV_bI#|po;}Z6C%K*9FMs6u?6ucty*+E~z0bM#o}<3ZgSCa=-R#PDE2UQ1EM)VV}#`126`b;fHHVg14QAGHGip-Nbzd?J` z($*7yD#|*EH;=L|;;ty`CLS7PJ;Yl_SugSSQ8tqJPTWVst!)qw?t(Df+K3lMSugP~ zqpXMc-YDxP{#BH95ig3ePU0m|)&`>GxSpx{5=J4#t@9sUgmH5F}S~tJE zd>fqp8mkza-?up;h)n053#Lis@KK(`J7^T+3HO00JUko#e7LHaE435XLN6}+z6i(m z93ABm{lrh`5R4^UABl(XiRpY$W%qs-hN%VFf1*C`#+?f9gmm(lUuPwcgh!xHW?>DV zfnI`|-hPj{D=fE^E3LMOSoqaeYdm)1EUeCpts8JE4zsysKMxh|qkMOK zI(*!E*28-irVh=>Ah&CecIHWyKW`7zE;F~YP}a+^hG!(=tD>xv`06O@BEBZdx{0ri zvL52=qO6zr$5A$txLCiHj@&5cuf>C-Aq+Q4;#!pT5+567J;e1W>n3hQSr_qyDC;Di z6lER6OYus#86Xe-4Z;v0@e@(jOZ@Lq)>wSY;L(^eFZ+)P9l01JIe})DBY(E zz`9f;%m~>tl7}^9UYvdl2KXIqxc&^~@wX9+JR=Y?td8)xF;}d!+fZ}CITxj59mDg3oTH+GklQ7Nn%=hr=a;Hp zMi|GUF&8!dm)M6DZ19mf8Rmg)6;j_U+9upyMFrs~h$O68eJoM@O5=?1C8gF0KU7gh zI7TC_bM!a^(&LZg?2D?E5q?thX%OzFqKt4q6*UN}5QY9o|8Y0|KVJ}Nhe&Fyi2l62Y_Um-m547e@9Cg?za#18C&3O%U1!~=oRpPAR*)T@yHCjB| zLa6iKb^#nfyYiBCyzXsr8Q>jJltx39)!ZSh;nA~UkgV{Ned77*Y;@|(TFNZT{7m6Z z805uHe)NHMi1t$**&Ta3c9iThf}>;~KvvA zQPxBJQIz!(ueDJt_3(s8{MC(HGI`QkRpG%z2!kT=2~pNdJSob0i1`gx!tEwLIqG#0 ze+ety`E?S)5^s*Vq&)@w7qyV~ zV`?_TIo8d2z}W#RpT#Pd5<*tOc{`k}zd768;pPj`9pQ2nHPRZ9_5Y{4??!iotoNYG zA?8u*AgG_9w2ZJ%MdJv$DbCX>ovo(2>!9^E!0K3`gSVn@!uKFryE1ihKTYkZrp6KO zq@pU}ZV)x8=_b%6?MtJs2Ym(_E~3P=>HK<>wUk+wxrk1Nvs1Ckgmn;qJIXqVPmi)L z;xnVHoA~S~>mi;RWxd4TkFt@(w{Oy_l3cOouEm2pAq-b6;=7`(m-y}|>mj};%DRc~ zi?S}_`=hLrcu|yf5WfUjGr)95Szmq267eTZ_q$9!nn=j7~n-} zJR@Yl&W|gv!ifDrM=c|K7b1B&m}}aTeHM4n{8bdcj_|(`P`EA<@46Yg`ixHEy`s!M z*ucj7np~BT^mQtyE$r*((MT9ur!qyP>DG_J*-0gr#1DR|rQLkkelQIuBdB%GRtb?wS&J^kc8Q5L?0gSX1UHJOw5JyK&LN==C#XQD z$V{%(gp5&keI(4%l+y^Wz`*-z$m|}!GwYPiTTwVKpni!{i=)b(_>z+SCpGMI}_~MD-`UMo(G8EuhtU`Ph@U19X7r%h8 z8CKES29Up-Afgcvjfq-c2Amp2{7B?exedfVH#YEyV@6{=m&}OUBCm%C%TW+B8c`!0NMP)x zc8Rl3CSfE0*2LMqfk(_;mOb(k=e{vpQ6nr$QLyWdFUg2ww392utcsW;97usUS?vnM zXzAu}EfB|agUUbYlQU&s(J}Z+TBx!*Lzsq2l+SSxFLKUN#&U#rT8LSivaNO#{IT9w zVwDl(TR=dGFh zam#xFtNiY&d@0XdbR9RjB~5EXZt1p}syvX~SwpK*oi63cl>TmpU*}=fvNFpu+qVzy z)pe|rh3Qm5?4WT}m$W_oMddSypCQCOzIncvElpYxcaz z@lM}PZ$1Ki3Tt@jO?=zdt?cE2ZfS)F^C1k1#Gm-Pl44phdiax;qDuIEb#Pj(pT-_3 z3*W`Tdg?(m+j)3FJ>-Npf-koc+GnsLYG>#_)zE0dOVm)EhMtZhh8bUM2*v{A&)mTpWv5M-cDd!%>%*uDdVevH03;a(9osY2RY%fYN%liwas~s8X8Twff}k?L*|;yh{%>-izKY7 zp>fubX;!-Zgt9WiT~uV;^AsIOH!tivANm(#Z3-*Nzw?m!hge1F-aeY+7BqzkQQjzM zNlLp(__yt5KMXCvDo@gcFQ}*>WXG&s>FevizPlB}omiczx)UZIP*dhfh^GG(RS4rvKC{NGX5L;Plx^%B1mWh06A{`*Sv zpN#UN3J>;yPy!rDyl<5C67LsfJ;eVIW!=OFL|GRxzw%CebP{J#uY)*;tQjB=j*f~W ziEB~TOMGmU^$^#itedzIWnIJ*qO6nnD^aGe)LZqdRFp@2@XN1g8UVt8iv$W{E zCbfog8e);_SXO~-d9^*iub)RTsH2$`*Z)LTJ0X6#sSq9+D@AQx zN8lWF)~XysNM~|0%=(o8Fde*$)m@eFQWcqoC5vD0C_gJEj4ac`b7Am$tfAG2|2fJ! ziEoUuF5;V_tef~}QPx9zdzAGOKeSydY3Zs}6&^ecp#;-Qyd=tch<_Vp-NcVZSr_rY zMp-BE<56a=AS}v41T5W5$Z(w2CF>q#<%A5_x$|#=I6j9}dh1%ie~F^BMV7(G+gPQQ zoi9cBJPh<)16w^(ivG+8DD&h8^IcZx~ zPS!TcY8yCJ<;^|9A<8muw_Lt`E9TW0rpjs}W`Rb+TKG#qUd(O2cn2@OHJ0?0T>duPVNAN+(qeg=K~UYVe=rYVXuiFgRJ>L$%CaOMdEMn zpxv`5ZIlW<90j6WUvxagSx?c^AwT4V*SVN;}=r?Mpu!c<4`6Wkt zB7fP@H2t)evN5bA9msugR6h@5{^rh{A+{fCj2Yog5XtVP0aW-+Y40n|6eLw~@6<1X zPdoA_6ee3ordf5*ES4L3SQDrsz#4@q7<$_4a29!IM^G$XG zs3lca6X9f}{4K0ux{1$>vQFZ^L|HHK+fmj@{7#g0nBl>>v$4wX5R&eVfnk}KL1~au zOUB4}+)MdXm|mg*WrY6$k#{GldsMs^%BpBvQFajqO2pW9er*D z9jP55=}zq|%hWCnQfkK-8IN1LjS)yq1Ih@$5%Vx<%)+_LqD}|#RZ-SSd`*;f5&tC0 zx`}@pWj)0I6J@=`A4gdaF~3Ge_OV;>XIr6*tZNYPI#Je1JUGhCo-x+bmw2xz8%ezL&aEir8E5|5JlF+7X}yuepNq0y;@&9hA>KX8 zx`{s@WnIK0qO6m6RFriPKe0zCz;+eg7MV$Z6Vi`74DAWCH03-Wacj0zLpdRXlXtzX zq4Dh^I!FzTCS=v5pRFM?gp7!6`EeW}^N_3J%&BPrhalNWSVhTxc-mL3(#&aO@T*3I zx$5j4cWF5&`CN5&>XWf<0kdHonPS(jE$tvX0C+-4Mtl-v@&rV-U))6n0x^<=RF|If`h8)f`ushnxaAIG`pu}ayxuvOb)bzTE=>fhRlo3dL! zx0^d1J~$=y_5)Ow8!Q*jbbm#}?d{vG#TU`XfV*IdMYI6$;V5e7?r)pngApWy4KD?V zJCLyWCp=PXutz_!lOM|>Ov0%s@Sw_`{lp(@(Z>=V^f?xJct0_xL9DS_>}(Mc@)N(q zYaZf%?$x3az6#Nx%J6>T!(;6U$3Y}-iY}ifZ;JBxZB-q|ZJM~dIwAZSL?IU9_oEu& zhS*Iy;W2kxZvBL%jVI((I}ZqlV^@EORl1sA_jq>{5k94&I^lZQXStlspSCd$#%Moe zV*&f3XgD^L-^e09&w=RYy3g4<*xy63iq^LPr$iB9-1%|Ka4p+Xw=~-d@Zc!A8SsrL zS`Rz6rS4ot$jz1t5^|p<@mW(jlRu19!q^96zlM#yO*c4u81N5KM994tpM>1@rcVz2 zK04_cA$viZo{+sD)(B%;j7={QtC`qXX`L}z$y}OBX{83roH^BD+9!w9r#z>mJm*o|vc_AMc}%jbhGm)GbxK+C_Qa}b z{VA~}O&ccVhKqN~46-%zYq0lc7kNLXsWT$ zlCFsx;v7iqsn3DUh-D3APkFa2l$D1+>QqWHy%gH#*->J7i_CCKCO>cF(LCd z%Ev@EWJk${oGLjXTNM+sRWTv+>6*_w&5#*CJGCj~b)C;2ZgP&fnNuBeGp9P{Y#CB> zQijx+h*DEdv8xL8|T_Rl=hxa7t@7$5m;Y?jZZw#({mZ5%GgQ;qeOG_~83 zuE}D^_WxP|M%SmWGse4|$y5y(4qWN`~blxE|+kH?qBrQ*mRyv@Vyhqj~)B29xx zxuFaxGsxD=8}9Jz(*$3#mtvKTOg;KrmU0IgD|w~uNz=Fb*VYx0X=;q4OpJ6}$!bKO)$nx@LG{ysXg z)rma1EIuX?GVqpSIsq+dJ169ZdZtXTkr`a%4eScaw}r@)sHBlok1c5$YswvHtmLic zqCyphHLxqlJGk_dC1J}xI>|V@%XdM@9cVJ-@!bu#!3~zNv_)WL9cyKg?;pb7rwJ7T) zJ~qmFi0e_-P27mGF5(GM)=4}m$~uTQ9#IM~?PgYV!_T+Q$d3RB8MnNC`wGm`l!)3H z+C>fJgk;G>>C@BN@wQUlXu=ZDekMMnq?6FO2O!L&usVO0UWFT~NK7lIrRg<=X`l}C zX^&}BB=dCm9#lE4yPb9FbjZhG@3H-4`6^hIXi`NQuk%cOP?D$o_}j9~Z_8TNK(@XQ zzGh&Rx0MN5X1S8l{VAG9M#znJ?$R`Lxz?_?|61T5tG{t;!DxpXAGa;wjw&)gv0MZL zi?PbS-`lqg2hXT=^9}%mVF1EgI_y@vJE8Af6p%-GI8#K3hnlJP#*Ha{?#pSg6YHJ`#RU zMTIqF(whtIJF$lJh>u#UC6xQt(h3jwWrIPHc;7)KMHtd$F3{^(B^$zh;6ScUG*wek zmGF5L6@)KpWT#K)9}Mq9u!=RpEj2LF(})1(V*N8#sUhKE>auPF%NZEcc4nv(2FR9Q zXFepQ1sPoHvcBcj9G>~L=XJm%Uu-v6-BJvV+n~qLgPuAEcn52VmpL(HDQ`AxKZ8{+ z@^)(sYZ%uMNh--NqU-l}#zcAT&}2q|_GPSLSc%tNyQL;my{f_ke(_&WBp$L(Nl{G$ z(8KlcAX82FUn(lrgRccxWi$zYsbR=yK97UfqaNX#WxaM$d{&M26W(@#w2 z7G;eloUfuW!w^fahS|3A--s;2XehIWOba~#15}kKgN1!`%uJfXv?%w0$+Tt0|1l*7 zS<1T;Jn}{7NbY6H){#??J%b2(sjYL8=tbucCkAuiSaWEFo&Qga@g7g=9v@>a!ML#&EP>OnYq9IJN?VZ0weXKlGWm}|&;I7y{&4Iz%@ zF`e9v@!uV*^AuuL%=5)4u*bBwVpWWl31FYv-ilQ*R;Gh}S$iv1#aNjO_K&fKOD6Hl zQPxSkD#|*DhqQiZUJ-65F~4&$*dyMfq(}VuC^PpI#`Xw&%AOJaD@1EorcCJn6As>v zdW0X9^%%$Y@agmbUEZpuaza)>-U}eyRt@EZ%*%PlfZt3>cF7PkkcT!#&R|89P9&3$ ztm?;zH?qvDgMWw03Qf(tr(snFV8x93Q*f{xt9&xH*;*T~$&xDTqR8&mU~QbFQn(rr z$6A=HfmJbCjDqF;v4%z_J}AmMiTO>xqSr-yWYp^><~M|fx6dj(SYA>j=68SwgTyaI zy-wnlQPx4c$$G7xX*x;UhnXD*qKpCIlPVhfN5C`IZSkdBuLHaJi>WrPe?ZhDM28ZvjV1`LoTcX-ng=Ofx5Y1A3vGb$=x zfhBH%bJJzok0~o7jE3sgka_!x+Z9Hq2w0YuD6&k6{yy;~7nuqRa+79eLL_Z>sDuaM zwECUsmOZeB3n%f?@^y&^{C3QsNc=yzW4ISEdN@-1vP$@Xit79#PMUIRM?+syR!;b^ zipE((=FPdiq0KPlmbWwUu{bARgRnF4Y_;B?^;e=ONAh36DsL1267cybO6&elFhGN{ zH;2Cu87&4GtvRE>fv+5v8_S zv^UjIMi>n>Xowk0J=27pcj8l)_w!hO$!N4}My7&yWqYuOE+^h1|G7UQfP z`&BWMp>%GvLwJ#jOo+Wm=wMCEJjGiTvxgbjC$_g@Rg9I%V4vUKid8XIW`TWEdn;DO zSeXy@!|knD6=P*7*neto#i|%9tH54w!&av{(`Z$Ul}@mCY;VP?7%RPCAKc!GRWVjF zuup7n#i|%9lfgc}y%nostjq%YXIR6fl=$B^(rfP03J;!!u=xlAy!XZ>#q@eY55J0W z5%3b#F{?DF?~pCdVTOd7flo1rlN7FCFiHIqtGP# zV+{>Nd~lR?5`QVmx`+>pvTov|qO6BFi?UwgJjzBApZ|$gy7E-9s=|Yr5K4d}i7$w< zUg8U*tcUpGDC;J^B+9yoFO9NJ;>)6}gLn~S%>a4u>!>)A_<<h*v_^43Gz}M8%QBe~z+V;@6_AhxosutebdMlywom8D*Ws??qV$ z@yJa|TbwpGd-5jqhTK~Sxuf!0h?_AN=0%H;WfiU8;Vcso(OVF$j~IkV$grH3S`6wo z4Js!jOD;Qe`r0K!W%(5+!jII{cqVhTCNrAwCKZimGOVopDF~BUPm>u<$fTS<7_rHW zVNj)Ht}w~mjfn5l$TLFDpWF`^CL2mbjD^;nyT;S7`DV0D$oZ5PIo6Q*f%#i7Fc^gk ze{IfhL)`~5*(#ger697|!i!*HmM6RjCg!C?F00%>4zGw<1&&L&_+pb{70e6qQBdI* z$2!kdR>eF&On{Zi+7;#r+Nu~U)4`tA-ilQ*R_1~|zr7W!Vyr9zdue+sR>fFZ4)%tf ztuYOEJL0DylWXRzwRrGMNs;)l&Ggze3%^hhhn^m`0#Wvl@RuqoQd7=2(9qt>$_ejR zQQaCcPfRyMo2t_Kv-&1~vK8_#ur`H@aIns%Oi(ljBiop@2xGo2bSQk3%+2Yex$|I7 zu0w<+WA=(U)*73u#!5{4^}Sz50@Ja|J2e%A&oqK4`FFbYH_!7o&!p{?c77j&DAi)V znk$ude&0hVOerV(Qk>iNABa+R>ic~okzmQk_dOrq_k4Wcs~~j5ts-PnqQw?s4B;A@{7Y?1eP(*~nW+ht_F$%0pP8mU41>(@ zP8u6(D>Rt*?%HA`N1jwCr8)hW!Q$7|@);e({K{OpNG+}K;A@|1)g)Zah<6&&k~I~R zlF0`Ewj|TA%0-Is%djO^q|}sik)ok1)KE@1K@HWdA#;)1657{bm0g_Q$FHQ0xj1iC z^~3djk-5-g+TMK^%xyK&Y#|_xIlJ=*uo!80;g<&Ly%EMGNu^{z7{ae;(&V)=4}f%DRa6 zjA^mDbH6LN#(3Psy2iHNN2BE29o z?)XKLqY1gu@{*9YSy~ZU+vd{NiERG|t6bWq_k9}qUZT|$;T#;?SQe(CeiiALm@}2h z@-=UQDf1M|oaHg?QjvPk3WK_y(?h&PNh!r!ZCJgdt3$YUZQs}zh8ABy6L z8jH_Th$tF~^3@v0?LR*nAo`AMfFZ1@>RtTd^v}N~Nn6-5{=V8z){T$~uYHjj}G{4Wg`@ zc*7{`A>K5~dWp}+7r4s}XZl(^mdV4&o0W zYX-xE52Io)@kdeCL;P`+brY|JFS3?Ix`@|~vQFZTDC;0T1hQr@X$x`Njz>x5R!qo3 z$o-3XX*iYoIM>y^Y$=3Aq{ayAHI?l8DIK9-GGf3)s9tD=lxTK7@l!G-DBd z8VB2#g{isSV6McRt4yXS7a@CPe;3RTG|7zcT!bU4XNQIy8z{A$J*Y*h2SsO&ZS zLol~ce_5LHW!Ct7HC`%~eXqf({X(zya)Gq9x7WfSBaokBm3uuL??u)zFvSh}MCG*qb>m1&bwE<8@ZySo*c?HY@-n192CGbO2l3b_>m<&jtc&>m zDC;I(6lGn+4@6lf@scR(Ag*uKic(_fZWr&j!IO)@gMj}UMSH`-m#~UfJ3|wcl@l@t z8FSiZNX{=leIEuc#JW!9^i|V4cyzg%uM=LQqUyDPKUR@>=ynWU1#KFVclW!9S7T{2 z6k{u_9hK8pPVZQEyY2ha+N7G2l4zz{FCvUrHTY~OO!sKr%}h_gspGJQh9mxJlywqM zin1=^lcTJgcyg5W5Z^wu6|?y0CB7rddWe4!W!=PgMp+l}!YJz`zB|e~i05q`{iao8 z{?{U2X-&evgDH8XM@zp}QI+rw6%{k!=W?uKi;y8WZ*|!Saz?;JEG=6++D>g1ggdFIx-;NzDk{=Y9YZ@pn})(* z5wFJ5g`i!IwWD%co54yoB_(IDj)9%yvC3`a3c$Hhl;*P%r`R8@a`EUbB^HDqsHplO zp*E5!mt*K%uxKchi+HW}mU5h`V{KoqRMRilXJBV%b(xI=JSmFOa`7wOZ-+{_4iVoK zWu3$eqpXYg?kMXfzAwsph>!ZamiKTSBF>_$hj?_9brX+`vMyqN!@1b&B%ToUI*4aP zSugQm;5Z zW#-Pry`HRTf&gq8HI+wTKA~^c+-pyf>x0jM7 zzAMT)iSLfGF5-Klteg1WDC;4lXqpXMcjVS9T{_iO3 zB7Q5%I*I=hWgW!F|9z$TuUWwsjQ?}X*Fk2|s7&ndqll(H>TWSb>mOkCD6C$GV>_em zoR9^Tw;8RwoSvDLXxRvI2E+VBE9TWgJxHNfQ^^Rq1M`_j+3W+W zqoQ>}hAS_S=k%qfn!+^Hx07Op-=Se?Zczsf+KNdxQcH(I`)I6T(ysu%PA!ca)ZHR( zjMcj!Ov1V7%{*ZL4i8ZClDB z>m}}rvJT?jDCdAd|jbnwG7?3XDg@(xm)CWb&N8C9AHioRE>o?4|)Y<9sO$ z{74Hnxr6w5HE#Be-ya@TOwarg?ANfie}gF1^xq&l9d^#usIu1q--)8MzHj1`Qx%rk zsR~)_68T$XLe{kP>2^Gdw#;C>j|eu4olzlecgSRi2}hLF(qvLo@}#k_kG4pZ*Y_ze zgUxS5ci#k@7Da1j<(6gcYSUrxifDK?;Ehp~`m(ItvW&0!F!-BjcroDbqbT)dS-E8y zU&~?eFVXPZfa?xxxp3yxvU1BZzB*wrekFT#7(^r5`?9RuvW%}$Ft|U~<`o$DOHtNI zd{~rq5g!p{-Nd7#tcQ3^l=TuH6J;Zbuivp%P`O~vU5f`lflvY*N&L@I)=PY2l=TqL zi?VLwpGH|1@hwr-NjyKwI*51aDFw)b&p{XhB;Ga3dWm<7vL50+qO6;Ec$9S!?-^yC z#G|6DgE)h%SwS9*j*26R$3$5#aUNwo#6^^K6W5}wi}=_m>m>eilywmQ0kUR*>4dT; z>*x&`EW&@nZpb6{t$-g!kwUe8_JsbCIAAJ5WfZ65l!yrVrNnDjggOo9?}~cuVx6wB z=7cdv`DosDyzeM)G$FfNemuk+*-s*3I^01KF&#Q|ey+rlep}s*A>@Eb-&Tm}SMD&& zr}2P|9u$cgO;eEvY`CCE%ov-BJYc{Nou@7I2EGs(7ea?r&YGyZW2Y8sP=e5h9p1VCS-KM55r>(g6vY$XXa)9 zD<}g^N*!nRZHlaEvnixu7Pl!(E5K}jhxkQlR^Kx?_^T$B6PDc0>SH=3x3l^jxAr&Y z4u$0-)k8+e@`YYlh12xv)o67+^Bjs5c*K8%Og@u+Uf;U%Yf}_#rLpLKeI9jfw_ok5 z`8Z5THRtxNzyV7rf8j>RyqbDWHRpQOw7-hMO&q5gjK2-=K^0|$bn3j3_9$w@lE}lY z)QCrsILBym(5=}EsdcmOoLbpmxtZ-idIi~atBLU z8$+9Aa7|OGWO_|@oP0NhtF5uhUjufeW*G6WpyPh*+dVi*rSP$D#M~z5!|<1b_C8j5 zVE6!V@Ml_qcn{}>m0Om1VD5y$&ql*L0S=F%G<(a+Ez9^C1%m}vx#o2czY1CNT?HPz zR#GJ1^|P&Dnu=*$^l%x7QYOMbsi>Zsa=OhL%4z6DHB?wb=G`nBI$c?#37=QdSZm0< zM?pgjL%zoQlPJY7tRhMaax;8T)%!K4eydYU?eGil8K~GXugiN8#y@H})dK;GC`v6` zR&H6Ql_tR8x1-@HfagU~>dUfn%QC)Z!QhQpL)#J`wo}VTvm1GkK^PQ?_uaXqn8rm9 zuf)&^N5Yoe7!C)lsi;c$yow6K7a>}^5H8 z&P=zhuEr`i zBf^y`Dn114fGN=;yjVpA;brPjrgZ@h7Dqk8-AHJi zfb1~ADb1Ve1(sKHcxI~SgGYXtR#vwZLt{J&!qB~`zTU==IWd@4J`lDq(0$14))>|d zXj4^+$=zd+PJP(`kySjJq~^Sam}7mVO<`5cR9h93)HQH)6IO4k8Sj(OaUY$R;v|*A zR1?SYn5kX`+Pa^M!%BQLWX)ma!Q7G}@l$2Rv`+M}CzNDf31_LO_z~bd6;%mo-PtDE z{=Bks!r3aSTidy{ZC*be3j<__AxvrBV2-ss<4$jbROgHdUpV+&u>A)RzqqS;fgP6j|opI@0<| zldviSuws(h8;%af>fNCk?`hC+@6eNRl1kwYO&k*)on|o$v>UO8VI>~3drP}{?dCxZ z!k|dJ?;a(^b{>8Y4>Hw+`@xpX>sY`EDykAbucCtRMTlCjrr~`ERQZ>nEF zJxaXHiNUn;Y}kGVYj`znw#KlQL7S>lOzs|obn43nh^%5Ld_|U-$CcJs+O1Y)09H&= zufoy0SiPxcyxVDydatH&l1j~2(zeBV+{p|ufSEI-Y|3^i28(7Z3 zn6}e(55fT1VS-bdH`VuAUd`c|slE$5^24;Ux}_Kz<54Sy?oIV2HipcJ!L;&$uzdkm znQFT=hBX7)RFz_K_ZXy8Up7Ew6^|yVNgJd)KF9i6v&sOhn53?OqnogLQ_XmvgpPZ! zUW$`cYQCC=ESASi^(v^WJG_-&7*^ujA#2{Nd9bjgNPOLh7P+aI)`=cQfGG1y*sr4E z4!}h!suJ!C>#|LR`>R8l>WMg*67>kbTh?nA*dFRKC;YjJ>Nc>PfiZ2T>rR6Kvcm+Y zG;gX;w!E6dGgEycc;ts^Wpzt2G&Vs&7`ivr``H*WCkE5XZ-cj&?n7p`#;}G%o2pVw z?jD15>dOX*tm5n>HD{_1vA)u#uqwrxRZLQc!O_uJy{Trr=R(Jw>ghO1r7+dRu{>s~ z=Yn>p`j>AX2LAG%t%RDx%7Y0IhVdspVq{4%Edo9K6;@@c2@ioS*?>CWBo$Q&Usq8< z_$EZnd+-o=ABI(|5q?$!bDqE%*gG0nPI$DstlPkH2FA3Vt{VyiWQPe(Y2H*1vAmkY ztEENm1dseMt%q1!jr$>E=-yNhvb@ZR!L;%X;Jt!X?$vf{3~L3nsVc?f?lDNGzHETV zDt5+RM^=$m={@Uf%_;-1Vv_m*j@Cm=-c&Q*J+w!iC-7dJq*9n_;+UwJ>I}3K)c+YB z#2fC_%DFk!Jm`clOf~UGWyQ2k^l&pg$W#-)r=nsbnCQYPT7=iBs35#v9m=#W#=)|v zNBD=bUc10<)F^Vo_0&||29`50rtNgyqp(SKnBbJ=P4!~St2w+{n#&^a$Pd%X>Xu?? zd=rT=bZ@F}w=rZ+45pQkyZOVDxe)yr{`N@1#rV|mO}SH957Yjdn&Sc&g}tU1*@SX5FZ zzIE@G5%&q49*zM~=9O@sii&#ym#C;pcm%A=HW7|dhceYua4;k45&od8mu?^fE7WC9 zc$bRmHn5z5F>R;o&V~W9!vv=^Z>pzQUd`c|sh$iT`C(e;SzC>5P!NXhP4#h>mpL(* zRvrx7U($WZ?A938fzYO^6qCEhAf5WM0V1ooC`rwk>YDYHHicC&Q*BjDQeTFn6R~g(D&a>eDhLOuLz(JfI2evqwv+G+WxaNRt*iEP!V^_gw}IshjA=Vvw=)co z9VR%Xc~d>i@@fvxOm#PS*iQ&^P&STRX$2uE9B^`@Hf9snJ8sz>1@mBLgL z$3)FkPXO(7^?ybO@m60f4J!}2Aq-PZyzzb|#k5ZJa5p^2R1?VGiDncW)0x&hi$m11)D7^G8QHb7(* zuO+ED_v(e#SK1U-#Z0wTF-hG6M-OB5rke4-1s!*)SK%a;!c-H-@|daa#6a$VH4H29 z@5;lImisL?k)5A$1%2X3BR8jFL;L|Fq5{`p)*(So{)S=v~XW(FV)Fb>+ zS+A1(V_+w!%bf6G71eEEIRm>-9hyu2MKC~inBbJ=P4x`Rt2sO~)ziTvKTPXFYpby< zLcR~HH`S+FUgpGLT6qX;9;0Q+?A91o71~sl5$5hONN1{#GpyoTh$73(<0Qi}8)sDp zV8tZ$bvQZ$t2fn*_b1SCr+O|IAU^9KD$Re*VdcSe2*daj zpR#{Ru?6G*^zdmAWvU4$si-&`aHfi?gd4-UOf})A>QJV7I1UbsdW46T^C~4E5Lw0HFcev49=oisv?;900IZm#wt}M_ zv3gU@c*jD=o$3rHsT8J~I3{YQdNOF|ssA%Nh<7@mm2-2ddC&`Cm}=s!|FNW))`=b- zfd`pt!Y8G9_o=8Ld;p^66Zi@oydCuj-!JR63+y2cEGOJiP1S8+IRj(b zPS?E#n`DOxPHEm$uduwD!!uL896a*Fw00qhQVfktAYR6k*3$eb8VD^G##yR~?k z-5SGM0Bx#DF}ZsT(y1>SAhL=Nlhm9i@Fmt)+7wpBOtn=pN&OCvp2F%)HRD}dhsXKJ zR)r_AhN&hYmd8wWH`GTc?TilMXK6Bg7#R8vCIj4BeaRb1W}&Vlb^d47N|weaP(A7}i8+Q&oz|-D8kW zec1q!Ros%K=1ldO)>qmTR>e%URWV7O1xM#&^`@Hf-T@u=UOgWtsT8J~IF`pu^-|DY zR{v*o5ceHa8de@mhp_ns4t&bNCB?K(^sqIQWvU5JR#8E-GgVY2+!)qnn+P{mhv5kv z2M0zy!b8h??E>3UUFL*msiX=2k6qSR z+7wo009H&=TfxzeSiPxcyknu`PIZQpRBAqfLlzS?Q#~0f=V1-QO1usJq^S7>&Vz0U zgCg<9Us}yn)5Cr6AX81a5p2o4(o~O%s)V)ExOE-{#{Za$V z3Aa*HbsJdDz?invb<1Is>@dM8&710HEU)J9%vAq7c;ts^Wpzt2H2RPjL-(foAsa*H z#9&%^B5dER#mklfazEsUUFj~|02QiAF{#ZUo%*r?BCB`}h9b+%<3j5z4acesz=}!g z9yod!t2fn*_buqSQ@si&sT8J~I3{YQx)TGr1J*FC#1EB+l?O{pip2LFx|*q`ha5zi zYQj5IR6Gp$w2G>P<6vE8pYS+!7~TWN!R)9<_@lC3x`7OAw7Sd*A5c-<29`50rtNgy z`7l6snBbJ=P4#rkt2sO~)n|c6ewbEPw-iHTR}_Syds98m#*jHNm{uMF+sEiWWOi!| zs|szZN-?>64AQAD8z8cZYm?NRC-6zuSK1U-#Z0wTF-d(Lj?Td9O*P~F33S}4o{N)I z3R6uS%VVZ`5oo_x|MDI<@L7kIhLs1?A#6T@1D|quNinSxJ$xFJ^q(b9iQ|mxD)snAR>NQHr5) z31kf2o9ZWQ44D&yY2_)feYX}bTLH-ZaGt;yK!vJOOlmVor@m}}$SOXBp~y1xxWxKO zo5HFLz=|ow@8IYutlm^J-nDgjy!XI)5^I=h5@Moes=J{+LTU0IIPkMro3E>Ru)L&5 z{Mb>enQD6Y4u~?M|#M zN=0=WSkAzhw$pXj!T{M}f>W9|)w3^P>p(o$CmZl1-65_%n8p|QQZcXGccy@blu@F zKz5kml;%zKftFWucxI|cfk%Fr)-$cG#v70^bZ@G6x4g`W0a~%D%OLr*?n7p`#;}G! zo2pVw?jD15>dOX*tfCG>k!9ww$NGxlw~$pCfEAO}PH?m*R&S~q@A1%ar+NZTQYlO| zaZD6SF_-C(T!}RdEAjqgT6)c4<-s8k21Vjs#+DSL?G~bkhu}e`ns8Uxl6gGp(1C!}*7ubUuSWfswby>H8Ei*a&+aS!N!ew!YG)uqp$vVv>3mj{bzzn`*|pnf9pj1m200R0>l~923Qc z#awzpI|yqSR^m6+pZpdI58f*&5_KnQFopR8+hLSb-_gBAlwCg76%5 zDARfa4i-c`!e5s4V$vD;oQQZcXGccy@bln{=Kz4J25e#YGRNrEGwbY&P z%v9e59{FKfS=~|$jiYP~jkGR(HipcJ!L;&l*!~Vyc@b^5#;~S9o2pVw?j8%})Rzqq zS;f6cYR(h*OzSI6-l~|Xwkjs63*qQ0tlm^J-Up%M-m4ekB$dKc6UXwHsa_7+JL+FP zNf!8;YAfgFu<~Fogkk)NFGl9>RMW#=Aj-TF&QwuxE#N#ARS9>2b=fAu-PNH?brlB_ zqaNYc%6jPrGO!WqGAF!BMRglk&cK+q({*(iAUjNOO7o_=YI!w>XQny>kNhyzGp()0 z$B;4fpqJKVf6L397)&d#0Pi!p51HK>!x{!{s!B1rdkoU4FB>4Tij!d|vLJ~0jkLbv z$riFI1F&L}+8d;Uv3gU@cu#|lJJpkMl1gE!iDRN>s%L?AqxzT67zG}4bSu;5u=1b= zVVG*-ebHs^RMW%nVO6G@a6j0Rc^wNlK}A)<=T%e?z6eqC34AcT55X$d2)ES0oF{Mw z_M`@u6CSB9>o%~QfiZ2T>o$V{vcm+YG;gXmvb>tZLt3#V*8`9IFs-a^DTc;v&|~P{ zRKI|Flz5pFgK6d2u>A~HxmVk*F|1|Krm7T^yT>4%`mzBcs~C!+L>BUhRa$9%#gi>$ zRR&r_Nem&-i?z~3R6uS6E#yk3bX=i7*^sB)Sol~9Lr;-dKGBv9^1+)3@h>NkTr*u2MbGz#MgbfMRuo}9!7vD z^GevSqT&v~MJlQi?hEU(O@#ZaLz(J{IG7Uk2)|p_OE-{#?V&Dn!k??CZUf617}IvT z?lc%6J4|p&^QQV_%d0s&Gu0=8M}C-AR<{&GV-pmFp?g!kpN%1NVlb`zHh6pKK4f-l z3~M;FsVc?f?lDNGzHETVD$Y()bEf(b>nm*vt74|ws+gn>gQKIddQ;7K&xMXV)zfj3 zN@1#rV|mO}&jsyH^)H_>3Va;?ro1_EUHqm8m8?3bteejt88q zqAKC*Dk=!ygsAxhJ_O!}VHInHpVh#eCvXP#k_MI&Hq>R^29`50rtNgyb}&G8nBbJ= zP4!UAt2sO~)mwl^ewbEPw-iI;e&{iDZ>rxwJxaXHiNUn;4Y2(RR(S%qTVq%&piNaN zCU=iPI`w4(L{_mg_Byi6JicdrrBiKH24KY`^#L5MhnT#nX1sf7k2+7_y*NpwFxA8{ zQ8U#UXeX$D`HWHEP4PxbbEM;hCv^ z7(DXBw6eOT7#iP1Vhr7z>V6wT=EPuHxeD7?YwHHI}C+EkTda`zadQ(rbfWEIaP zsX6!RdDd6j6jsGdwN)`m{S1!o!s<;m<9!J_?!9_BPEsjMHE}GDnd-{;R$iN94Z})& z4`j`$=E0(pBJr&gT1MO_aC$ffM44B@c`7RI1ze({D&Y~ZF55&nMjgsjPr<>As7LsN zvR)pW|#u?-5s z(7mZX&hj!R2Gh!eVf#y1A)RzqqS;a+3YR**GtglM)&#DxO zM#ZX_q`nMCCt~%cn(tCrf|22n|2a_QT z<4-*9xRPQE#{cPIeGp};32Q1UP6j+vMODI&R8$ZSQin3t!*DPht86FX7s`4uq!wae z>#F^n@I)2WZD2VAW7yqd!^Q{4?7`C(dX)>h+j$QZge)tg#g z=EPuHc>#ET#VS*6x5lvEhBj5DnA|-E>C~4E5Lv}O*z3qL^Vk8E$ikCyYz?b204pY` z4dG}Dtlm^J-UFcHPW339q*9n_;+UwJ>ItBouKwjSMuE3FzLjlrs(H{2VVG*-jlWt_ zj1_7jdbk@_WvU4~U`yt3C}59@s)V4Tir13VoO|^`>nm*vt74|ws+gqifuo19dQ;7K z--3=i)vIulN@1#rV|mO}cVZxSz#4{?_;=-D<-yXDBJq6_Tg7po!0F*65M`EMwcrgfpU)z}pUVd&mepK5uT6N72xA+UXn?n7p`#;~f;rm7T^ zyT>4%`mzBctGG5v&6(;+)>qmTR>e%URWV6@9gfbx>Pl~9Lr;- zdJ$;9SO4<&)xc++P#RVqOouRxKk+FimK4)E(Zi=fl&L11q@v<%z?mwl5^fCZvQ31W zszaIT;W#)j>Jc7V)@v8o=ISyhJVQlw8(7Z3n6}e(`@jI%VS-bdH`T)}ujcT~RQG~M zewfxt)>h*M$QZge)!SNL=EPuHc`po<5YYb~Jw5ck^@{; zq=DsxJF2O=4J>D1Oxx+Y*I<+EFu^I!o9Y#oS95q~s+WUDewfxSBvFc?aS3D$-J9ws zYz&zbgK6a{uzj}{FSA=?SPP&{RVgNSk3l;1WdlT3@nMphbFW@veWgudRm@ad6_eEO z;OHr=-c&Q*wRL!$pKMil5^I=h5@LDGRChytgwo{itAU@z+I+C)!Sa$K@nc_G%~aFF zcR-Y>CS0PT;yJ*TDykBm0_(C(gr}-QxmVA|!3|N5@TXFP2kd`d-i8(7Z3 zn6}e(*TMkVVS-bdH`TK(ujcT~RL=sB{4lLctgXg=Cd0#ILD-UKt81B`?Q%^1_rgfr+Pk|^?O?aA$it_>I zsHjS~HLS}v5pJsvWvUOvK{e_T9$VIH7uYA&WlngGit0A7oPjZIr|S-e0kXpcr!;S> zM_FFY;hCx48$9yEw6eOT7#eRtkD+^0y|s-Yb7C;9ybQcg>po<5YYb}$w5ck^teB*Bf}=gLdQ;7KkB5#s)e~@%N@1#rW1?oNr-OE- z`j@}21|D%rX;^tM3c@he#JhZ>q?p!;9)1I>GS!6J!IsSHo`8p_s7m;liVDIfAZk8= zzm0>zh(fFpZlHn5^tTJ_*BV$(xVyTn+rV-L#Xu??^dm8b?oIV$HipcJ!L;%W*nUKdm)Wf`ti{l#suYvE#~_{hvH>Ei*a&+a zS!N!ew!YG)uqp$vVv>3mj{bzzn`*|pnf9pj1m200R0>l~91}HD-3!`5Si`Uqzo7o) zuRD0Kyrf9{*f&=*)$}kGM44*BM^#kN)Jhdq2~UA_nO4G6)nRx7$H5IzkMO5uy>tT^ z*mu=sPWY^f>Nc>PfiZ2T>*m4$*dOX*tm2j=HEEZ0$IrCB)~qrBD<-M4 z;OKm;-c&Q*JD}sF+qPh(%XJAa* z>AF!cKz5kml;%zKUY1vLcxI|U4<7kpT3Ovv42>6{$I!j0-oeI@IWc5uJr{$wzV1V2 zx5lsrLz}8nOzs|obn43nh^*pp7>X=2k6qSRnuJvufEAO}R&cZF1f#cwYs7LtIvR=E?k5`vD;eV*8ZUf617}IvT?k6xnc9`Il=1ujrmREClW~#3O zkNhyLtZpfW#(pRWL-(fon>L2biNUn;FxWmx_aU=eV^|ZRO;sr-caK3j^<@J@R&h&` zn)d{5eWgutrcp6dZBJc7W z)@v8oX6iC0{Gp2KHn5z5F>R;o#=-#EVS-bdH`Rw*Ud`c|sXiDy^24;Ux}_KzZ$OWs zdsDrQjUjVlFs-}{yie;sWOi!|YY4QdD#hgPF-WJrY=For>M#^pMY`iX*4LU<24KY` zwG$leiPf8G#(O+;+^L>`lT->*O&k+7Q#~EDE7iZe2M)ad8Lga~w_qL|0%4eH;$6-x zDW-LzhlgNQrkZe9*phiY05DTgmGChY6@*Vf)Orsb2ZIrXSR>p(1C!}*7ubUuSWfsw zby>H8suE6xb(vPebJU?s>kT+q5cLRuS=LK8kb#}2E_1>^si9dX9o>h_ zZjE70fi_j8nA|-E>C~4E5Lv~&NovwAX_aPLUu#wwfEAO}g>ZBgR&S~q?}N~B@70TN zl1gE!iDP-pR4)hZ9rZ7tF$#RmcUw6(hm{9&Aq?YBeDPT&#k5ZJuos9j)r2!uR9p)< zPeoP2U0_|diEwvyC{tac@-im|)5-r(FKb{q;lb*%ZUf617}IvTZX*~VJ4|p&^QL;R<<%UXnd%C7B!bgN&hjQ~f6DQHnitVlb^d8@8XpDv#TCYYb}{w5ck^I6Yhq12WZwD^*l{2-pErqD6SIiVDKZ)S*o40vs%kdW63%>!lmW zz^>FNa>932RJVcU42)?zU3V`GkR2vCrFm1m!18Ji&rJ1v@W>C-%IcP4XpBce7`ivr z*V`B}CkE5X2g3FRx(}J%8pE0aZK_H!xqA%KsV^HKvWiEO)SM^qIo4O&6jsGdwN)`m zT?0oqVfCh(@jeM1_g=jeC#e*snmCrnO!X?z*6oYKO8hg(n#0P2`6Wf->!!EJ?o`vm zmq3(xC7h$8;ugS#DykCh3+u8?g!`*Qnd*r+m=g5}zgyO;B>x!LLFzImyh%lM8(7Z3 zn6}e(C&K{QVS-bdH`Nm@ujcT~R8Ihp{4lL^tgXf-C64AQAD8z8cZvy;@EsXoN|swDrcikWJwVv;%xj*iCaO*P{^7dq}# zPsd3rg{dZv=sLPORY;Onlt;TFT8~<-sHfgCg+}=adv%F#b;uFHdjDWU2{| zf-TvA;{hkDs7m;{iVDIvA!4}te#Sj8IQXCZ3p#gJNvfxV=G<%A7&S+{}Z42)?z zUAG+!kR2vCrFm05)beT$&rJ0e;E^AumDMf9(6}FZ4BeaRH&BlfFLPort$YJ)zk*e! z+HQ?ut$;RFrI_432Iwy&e-e7GV}PJ^_4b-RT+R4lhg-rv>syerke5Yp*`w6 zf%oDhmBLgL$3)FkXP}*+{^c`9fj`FDoN69a&TSQ<`2-I9I#%~yO%K<>noKp}Ybq*e zwgaZZ6FA_-Dk=ysQ-|RR90!Y|9^r4xdg%r-upem@IpGH?s@uSF2FA3Vu3H2HWQPe( zY2H-dZFx0^XQuiW;E^AumDMf9&=`+`Fm!LKZ?rLFP7J1%4}|RtbRROiHHI|<+EkTd za`zadQ(rbfWEGDlsX6!RIo4O&6jsGdwN)`mT?0oqVfCh(@jeM1_g=jeC#lqY0*5S? z$4vDqsI2?_IIP6|kTr*u2Xjk`#23$Kk=?1LhkZemc_q9^MFmaGQ&E+07g(2VBHUda z%2ZczFfr;8eyyyRZXg5ug1XEJuTfFm29`50rtNgy1Q;MYOmIr`rurDmt2sO~)j4?N zhiPSXOEEM)h8{!rrurZoL*~R_T6qO{pV58D?A938FlbX%ipkw$kWPKs0FhOk3`3D+ z=5eI;l{SS{8Gsd&)ZTD(FjjA>8SiP(ai@ARPEsjMHE~STO!X|#ZdCvB9ysur^Gd_Y zgBpb4UQN93`6b1)PW145Se2QUllP7J1%XT$b0Si^hZW@`*<8MLV?#pLcWNTd<*!#W5D9aw`R@#|&9 zv`+MJ9z4iY6TYFMVr`h{#41{Z7ptfsyi6U2CvY4rj(UW@E$g)l>|Bi^C;T53)ooxo z17q4w*F6XWWQPe(Y2H-dYk4(?XQuit@W>C-%IcP4XpBce7`ivrm)ICGCkE5X2g3FR zx(}J%8pE0aZK_H!xqA%KsV^HKvWiEO)VwEf>nm-FIYz}ywN)`mT>~pOVfCh(@jeM1 z_g=jeC#lqY0*5S?$4vDqsH}TIE59(T#J5A%e6Z%h!jdBKbw6m4{U>mE7y+WpD`CHi ziaP)osi;c0FRaTp5$>-JWvVCQU`o^@{BBt<-9VD1Oxx+Y(_nz? zFu^I!o9dG-ujcT~RG$bQ`C(dF-BJvVO;8Yq?oIW6HipcJ!L;(*;O(XRklC#EiI6Fzrnd(EVue2$wikWJwVv;%xj*iCaO*P{^7dq}#Psd3rg{dZv zu_zwcZ1V_hDGY8sTR(Fy{%JfxV=G<%A7&S+{}Z42)?zUAG+!kR2vCrFm05)beT$ z&rJ0e;E^AumDMf9(6}FZ4BeaRH&BlfFLPort$YJ)zk)Tq2X3~;uvS2us!~ku9)oo1 z%La(7VrTe@EHjVqSzl?lT9pAkyZ9@?YM6L>F9QYlO|aZJ=qbq3lA z>R;Xi2j2AJ(y;QN3&Jqf#2=Lv(>l?^m9Q#PP57~jip^l68>?s$UZAGN%i!!uL;FnHvLX=Qaw zF*Lr3#2C6a)%`Yx%!$FYauv3(*5YM$YYb~Pw5ck^YZzAIdmw8*So2^} zNs;*0OIk+UCvbW=21J=x!g(qx?gd<;qAKALurAv~I7S`HR8PUdjHpNWgR)+_fefrr zmpS2GDyrMSat6k}5hi9gGGI->NX`N?nHMT)P7`ivr$5~$H z#9&%^Fl>KG_aU=eV^{}5o2pVw?jD15>dOX*tm2|1HD{`8)>qmTR>e%URWV6@8IDfG z>PC-%IcP4Xgm%*hVD)E$EZh%mpL(HX+0Of_Fu8e z6S&c24KY`wILjBfz_L8#(MyC z+^HUglT->*O&k*)on|osw9~PMVI|)7vX*x97R-Yl2!kT=#+R2A+j+PV9%QNsH-Rmg z!|ecjRa7OsRYe8i?GQCzS1-fCil|5U=dxbAz<#2E<%EByrs_7ZoPjZIr|VvVO|ruT zr!;S>pR>H0!!uL;6nNx^X=QawF*N#+7(@4_`ZgOw=EPuHc_M7zti{Xh))>|e(59*s zle@K-_H7^^qcjQ1_*xKq6fC#e*snmCqc zbecsc266|iVOWWuC=V+So-Qd8-*-jJtosB`4+V%a)r9w}sCW`^xr(ZU<6vF3iSRge zDEI0aIG7#v2!B-8YnS?1b(s_XtBUG2u$+N0ZKvxlhXJy~1gA7_sxPvPgmD+7wpBOtn=pNqrrT&cNzTHRJsWblj<)i<49eQ%xMpW2Sl$Xuntg@;zI?=gck* zD-UKu7{;IYlq*Y$X`Sd{T@Yoe3BReL;#|O4DykB04C}H@gqx~End;#AC}8fb1~ADb1VeeJro$@XS>22_E@jT3Ovv42>6{$I!j0 z-o(a`IWd@4UJTy)x(}J%8p9e4ZK_H!xqA%KsV^HKvWmlDD6)!l$GfbrHLDE3ib-lK zINA}bH`R=HEOgwd&Tx`SVXBE^qGqZmgLaErFm1m!t!bk&rJ1l@W>C-+Jz)aF*GiLjG=o|{e+Dnb7C;9JO#Gz z*5YM$YYb}vw5ck^y?J0gt8MrT2M;EUZ9m!fg%(W_5uYWQm9ZMqEMCsVJT~i5m_pN1{I3h z>UZv(=Q;27yyw#D&+qqr{iE-5p8LGZS>8K$Cet(_^w)_Qwu7VTYQi%eRh|WSwWFE{ zH$`-_O@v#xgr=+a$Dm1cgoh?|X@za$QkDoWc2q?dR$^hCcHq=aL;!ln9qhY>x_W=@ z*BtOuU0nx1`p4aRrjFI}CvYr1)YUs_zbH_HI?Bu7_a(Ovk=`2RwIS4$n#JVqvB<#s z@&Jfexq?9ArTVc(=kgDQmaIb*HL2~9XgAnUSF_%upb2($D=tzLcQvt0RCRSb+^%x@ z&F>fm-e+E-Svi;hGVW^PvDYPp{yH(kB> zRq&&K+^t;QM68xh)Wy<6UHzP{Aqv!>j`D?w{iIvGNNu(# zJQ8~?UaB8o)w%pbp(X1OMNR4rBzg}v)YYu_3+{{tp1^asNKxF?#4=IU)kU}+02?5eKBp63#pZoL_Ug`y*T zAgS{=kcFM@swffu(NPs$Sc!#k+JRH|a|EDwwS!R%-9lZxK>IcO>5QN1>RaJQ|F~PZ zx``TEzN2es@z-UBt|1E4ppNn`hqxfS0%G z+hq>sgN)mscy^avoZ$OvX4nRfre6s!cU1XCz-~u15spQ4^BPRJlS^p2x(S1$MMro% zsOoEa#{0vktA_MEo10Y`I6P1_h z$6B2$%SwFeT7hYfW#>pdNsU{|-{B1Lgm6U*|buI_-_EiS+L_tn4$T%W1_ zsWvMI5y-f!iO1cL5LzgIGs8<=mNQ*V_;tiG{W>17*-=e|D;!lOd<#^yTZbU~hOj0Y z;l{48z!NwNd)^gRBHYKNtmwi@ER53*oVsBMK<~JNeYa3oXS82)z)yAcKQIUS$KA@+ zP1MkGH{!GOP*<-&tb|__s6id&D-ruOSku*dYn0bAs3|pz$=zd-f%WA95U=tW?6r8Q zeq5<@*)u7BYsor9QIq-u60L#_bv5hV(w))36L6GGw@H`-+hzE(5CY&e>(CS2~QGDCgps3yYch;F)-@I03=egel}q38%7Na|$k zEMj4ox|AitKRBwQ3oEfOPCIbwx)FfhaR>Wup{~A7`!xsrR9D{wKl;br%GFKO(DEHD z2ulxj^;NotC{Tks%DW);S#BR9y*0|~45%qJi^<(%k%9H)0T8e9VsC2ZxcEChQ|DT{ zWF4ZYNnM6Sb74bW&3b6DW8rAJn(%B#l^N=4M>P>{is)vW2)A$vO;_)aL6hhR4^8UQ3j2ynSt7j5 zQ59WSiG^|6fm0VD0KMZ5_T55VeW3Ph4*03A-Vc8CkGqwto2a4XPtdXSP*?A&Yls3h zsH40LeqVC?5b3Q^UK>J9saZ_!9*YdDFAsotl`9A&UaB8!bT0o;XvsQ6QIpypiFShx zbv5ff3YuV7x8fp2aaR+|L{(R}!|f`U-+TrRyyGp2X62v=GVW^PO>a#I{dHo7XAsqN zHQ^YwT@~ce8^E{!bd^bZ=qoDvFHf@p46ok_LM8EM7X^RRndi&SQw`rICcGq zN$k#q9eQ}sq;6Gg&prwmI$AAR7Dq7Vqu(i;M8>>0KMZ5 z_T55VeTnvK4*03Az5ssokGqwto2a2>M=S_S4|VlaT|*S8K^^4{5&ICg50Ty)<<$f= zrDid?dn_`rzB~ZpRi5WfE${?>tj^^h3N2AxttDzwCm_)@*icuq-W#C_zE*ePB1Lgm z6U*|buI_=`%Pzn99ysuMw)s)=wE zqML0Z+`uI?UA+qi`-_h7TS;A7VS`-C65%ER53*oVtAxfZlNj`);AG-c$QE z2mDl5?*>2m$KA@+P1Mlx8|YYisH@l4HAI0L)KPv6e#6~9M0#tK*AS>FHH*pJW08UN z<#RbAZ%w~JhU z^F46j&F)AvD+dLTaaR+MT96R>>%$Yls3hsH1!|V&CQ#FVb71yl#e?QnQ%cJr)^QUmgJQDu3WjE$~{s zNaykog_fwU))FKqz!8`!v6i62QeD+fIZ zA@QAeenwX_!(=#`t|naIs4_!6;ix9UgAv_q6Jdi(XkM$&z~DmB5nh_q`5VZ>4s|I@ zgbzBZq6;grFityg>e>;2-f;)}ZlSI|OZznk{8U$e4}SEIyOpb(sG(&uEC@>vb@eg2 zhA2>jI?6*3`#`r3k=`2RwLjF9n#JVqvB<#s@&Jfed6qY|Kvy^GT>hcZ64ljOq9%11 z5>0^(bv5h#F*L!h?!ZNg;;tr^mX>g#J1) z!zYMpx|;BC#4;OjI$*n_nh4i8s!TZ0B{W^VH3qxDnrMXINb1rG`->~AM0m1GSyhw_0yU_k zyb!Vf25UYV(_5pwK8Bi7vzXjH78zJy9suzw?}@z@FV&A(NW`n+9||p5hbU@NBamn` zY^bYQ@4nCkySfe+DT=$ASSG5vx)p9yU4HYsP=L4SPBbeAH6Y`zCLZ;(gwS6nX1E1W zO;;1Hk65M;w*)LYs)_JUN0kZh0cD@SF?dIGgzqPHX@%YB3M&zQ*@dd;!b&WR(+-@v z-ytTw;|})SLS4OF`!xsrR9C+OKl;br%GFKO&@uxPW9gx;zFXH21!{==^*kD}Z*z+m zEd%6!1U>`584^m(Vp7c_1MABJAYSDU5J_LI8Tl z9qhY>y81%x*BtOuUEK~p`p4b6NXKf~5evf7LtTBE_KN~FME-hih}eg?eTekdD6b}{ zDK(49-D8n~_2mH&ukt)^YUa536&|Z|tzEJXQPiYPK%!}|p{{1VH$oHa>MmTQDDG-v zS)O`-7Cmr#+2uE%fdilNbE~d4D+g^Ld~ZUSmSGeeO;;0E993qhGac1LxW-Xs z!htSf`~;4{F0kgOxPyJSP*-oK{h9-Q zs;jqxAN}KQ$iVvY z0Ekz4PvjCW)sI<7#7jScE6F-UQIi^hD5GIRUCny;g(le5b+|}T^$8rDOjLDsDFUQYSSC8c z*OR)m!ftm}ln6(-P!(NRiG^|6fm7FunDmZ2*mnzc^^@AKIpC+d`q%KIf84EH-9!y7 zr(j|%J=E3r=^COy4eBU2A@)z);zfFEl-HF|Q)(8IyT>8}>&pWmUghQ9)B>;7^K~x& zP-uzjYAsQdx&w*shYfW#>-`-x!LIJZMT+9CCYI$nH1 zh7S?dbT#3gh-Ef_p_(1lM7Y9HWx}^W*?+Tz>>I+GXoMTP!c6z4752U>tVDQ_OIgu{ zl~@?39XNG41fX}^!Mmw2guT&Z*U3#ldR5JgSu4@k5M zHq_OucT0Ch1K$`e;37qFR};%bRae)+tqdDCEAb~TpZPai9AqA_3lR&6KLi(it!9P` z5Y2Qo;qM$(UImy%D5D~r?Wi*0k6c32tqU=DOmu`#C3XG=vaoYq6(zzyJF21!E3q(6 zJ8y1HBYH3$5f{khx$Kl;br%GFKO&~i8ygr$eNdbX}13e=#E^8Sc@ ziQ9)rZ;kT05Nb-zVsiIbWMF-H0K}`j)SFu1wfbtE%U?(>QC+PiYEsuC(XFtdu4cV2 zLKA$g?!`rl;;tr^P>X z3!th`;2&c!1XY-5gzLG&0#D#9>~&XIiEs~>vZ4zsu`o_MaOyG$K<~JNeYa3o{|&Vy z{F(!Ps;mDBKl;br%GFK8YUxB>EIriK@9G+&Kn?0BUx?UGy2Xq1)+n#Xpr+I;CU=iT z2G*AcK)lK$vDf0I`tenr%RjAJvJO$yq~1WH_h3U^&3eD!&S>BXJco-E#a&G-6IESZ zgxdkIakCQt#^p0l;2iWNgv8H0@)=#t4AbFgx|(pQqsk1m(os!>Cn37&R>IRbeks-f;)}ZlSLJiS}y__^GbG0)F(5 zyOpb(sG(&qEC@>vb@fHMhA2>jI?7uk_Hk|>BE2=r>u9JcHH*pJW08UNT1^eb7+FE)!n#AQQXzUvOKD*d*Sx3%Wr;jFz}2X zJLl?yH3#h=6Zzih4MEuYzjxy)r2QHs?1O`9o0lQ3en9r5pLiTny%gjgZ)KE z_^qT)O0tNBZR}E(2!G(HiY~0g!Z_`~sjEW(ddD5?yM?-XckS04@KasAGyLcucPm#n zQA5jbpkwKwuHHu15Cv*bNBJ@M4R`wx>8(*-L!hSAEGBo4MF!TF2SB{a6A?(fR6pi* zu8j9bOV%NZn$#vpv@L9?t66U&G{LTpaFL?8tBGZzs;k@Jc9F|({>>Kf_K(^rSG$^n zBFMO_i8uXKLg+11h8dnjRMXXjdBigPx&vUXqnZdGa#We{QBc(<@OLoySagJcPwLVN zd)yUPBHY%6s_4Q>ER53*oVpJXliqO$`);AGen&QrrDid?dn_`rzB~ZpRbK5)E$~|XgwEw33N2Ax zttDzwPb1MQu%WJIy@Ooy1fIY%ya*e2H63JmR96=u-_^O9-{sywJ<6X)U;nR+) z=)y`YjMEOBx()=Ocih3gTd1oq(SFSVKh@P2z>ofMw{mq8HMH!A1!3u-uAZuEhypdJ zqr4$vAL8~Q(p#gvnxLlCEGBo4MF!TF2SB{a^Sr4Ay82k1%RdxaqPkj3)TB;8qG_<9 zu4cVALKA$g?!rZi;;tr^nLEg#J1)!zehK zt|qKFs?1PlI;x3qjibth16@MX)mvk*3#{2r!fzyXX@#xl;+F_da8yMXR$^hCcHq<% z5rE!t2m5ZJuHH`jH3$4uS8oMB`p4bM)lJmU@*H$5J=E13=^COy4eBT_gx}v_<8O?r ztx;YdLrtk!Ozs|w46H8?fOwVnL@x1C{g{PByej^o(2{kCq9!#0iAKYQx|;Rw3r(=A z>u`~xxT}d}qN=M~;WpLfH{TcqUVllVSvkmojJuk6O;YHu6Eob2sHUq4S39cAPWup{{;X z`!xsrR9F8Re)NyKm8+Ymq2&}zjHQRV`aWGl6sSQRy81Zn*BtOuUHx77 z(Le51u5O}+mi4h9EIriKWnDuQs6id&kKwnw+lNSRjq=(BYD&#wa`#wdV10Q2#H)O^ zH?=@lPtdviL!l+AtF=T;>L4VV1RLsV)_XoQ!LDw{MT+9CCYI$gvJpqkr73T-`(s zEq6o5(nDSS3DzUw7X@liNBK&`ehoJM#;DpF<+TiIO3h+&_gG|LeR%-Ht2_p|#7p(# zN}bC;6k4(lQPiaVfJCccLtV{!w{&MT@M&2A7b%Lnnph^Py1EW-hr!0pN}O4mY*q}i zu(6Q%<9`!+3&jkVAcN^@!j+CH4?u_8LW{H7=p))`u{7Ms$QPBz0b085VYt ztD;1>%25?vSc!#k+JRHIm;q3L>7F{@E#~~17HYo<0lx{pUpM^dZ(>h$t1C53YyxKK zvCfYb@cZ@{?e}fpkFn=KYr0x*jcS0|P*Z9ale@FP5uxKMP2mnL=o26F0Um$F27 zzoRO;uo4U7v;(K^Yy_aU>7EI`Tg>@2ouU1j1AgkYx($BxH?dph>sT$DVL@1Wtn+I) zQu{@L8q`r9g4hSTeTekdD6jpYrqnDZcaKE|)|UrByvnn@sRg>aS?BU6uO+IhwM0$o zFeI7+8|rG-`(tQ=UEP6;6vbUlEX$+1x*KkfyZq+&mI0seoSk#ESvhC}8Mi<2H_@eA9riBj@9xUIF=sj>J7AC6sRHc*K;BK z{stR=2ClY7d3_8urDid?dn_`rzB~ZpRo)Z1#7p&K783E&pMfjMIz&;E8i6RIVMATb zdiRAU*wuBoNKxF?#4=IU)va)w>hhby+(Gk9%)TI@6pDV0HINF7(=)y`YjMEOBy5)#T z@3@0~w@_Cv(|*kXKh@Q}@S}g+tyxSWQA5iNa4bF4)sN~LqCgFizn(`U_HAzQBE2=r z>t?7aHH*pJW08UNM=S_S4|Vn7x`rrFgF4C^BK9F}A0oXq%Bu-#O3h+&_gG|L zeR%-Ht31z}TA-_s)w%pbp(U!TwM0$o1SFaU8|rG-dm}W#uI|D`isG&&mgP}h-2=Br zUH)lV;$I7wBYsl2Jn>V)6^Ne}u7>zI;flmZFLO79z6=M)fHb$5BJmXAYKV^$u0VW( zaCzdBgv$}1EL@iO7I5b7)R*DlRuLA7=L=Uue7kT3;swIxiSH6FM|`(%S>g>}POOZ7 z__%Gq!IGL=<->^aIIN-4y!xG2iI7V*ATyb(!GaCt*JLO^_AW13W;a;#{7pF3QDp{V zv4Pgx8|v}SDJ^W*SOhM7<-YUB0@=5;qq{K1GkJ#keObCGIJKq@cpW!zX+Tu zOEEXiI^Qwoe7}vnj7Bpd;HN%@Sqi_GWUUDIa;wntN5DTjDk79sP#>?nfS7N=4$KtQ zkC86Mwaa0%nbTLcXNPXN)moK0oaT3g^D7Y!L%so-!$$%#l%duhsGfoT=wdhOK#gk;)WASYZ#MtFb(l#r z{XTUCRCEFA!w7B`y$56tAspzIq(ui(car0g_B5A1B3$FB$vTiav>PJrw_W<^T(u+W z5vvQ5>tW-wMEoz|a>UtJ?N-D(;^D&Oh}RP?>u(-&-i`R?7!oqgfK20HL|{Zi`GInf zY?DYv{0O*e$8gY-5E9?{nhhI%*9C{ku%=YP1&%5+)Dwln=-|u_yqrZvW z!qrXG(6Sj8gr&ziKh`n2hA2>j>VqMOeW2TiNNu(#Jjq(Mb)2sfRp7>@4C7n`5A0{t08_$ zxEyh>a9QF$;c~>k6;9ofm~$aCf%gZLJme%LLRMmKQBUJ4lLqcu{gC{{1#0ql$(Ibw zoROh?Rk~Z|bsK09Y+x3=G*Tp^05=fIucv@ZyTis?LVUY$1>!q|%Mw2!Tp=y~p3n@) zlnD2Alv*WLB@%U(w4{OR>s{coUt*y9z6JFm&MP9k#ZeUoy2nuwA*Ua>AM`@~0&Kjx z#IFdKBYsV|Jnq0BI$*J~hLc&u zZ(OaD30cb!v#dk69K#_uF7QzV%i#9KAGHv3E6pL8cMc2TO2tBA7FQLrJSLBY#9WxF zkONtzVyC~$RiVFh)3LwY+gLQ?3Xs>rR!I)_a=YJhC?F>{Ak)HWaK{bh7i=iMG(-7? zeGhxu0c+NKFKiNXgpLB;xbCL(mD7F4D(NS@2#GFr2_r%VGkwyC8YZ~|6A`kkKsQ~6 zm~&y{eY^|!ewVM@tR|>Gt9#m8b2FDur?X#p463BR$W_vxHD6I=3lI1G0-uh&7b?OJ z?TgkSj`^U_kMakk`V`jmwt6OJ#e3pb7ktLw9TzEzpYe&Msnv5?E8M2S#*aM2j|x{H zenPk`@t=e%`151@>Cl)50K$hIRrwVlBL$w#bf6Lg{lWz*>p<#_*F{iY>bxStdmYuN z1E~)^`k@}$XD1ym9dTK>0&%NwS>lU@D`dPsj6Vt*vvha5rP~AQy_{D>IKfekI*?kr4yf;djhBviz29c4f6T`~JsA#iAY&o%ZAqc^k-wRt zyU%jwjSS&xN0mn)1VaVhZ!plK&a0mAaYr>ch=Aq*>(~`riu}#kk4PZGRgP-Zfz*07 zA?>LyeI!{dT84m(W&W6@#)aOmzxOR`zOv@7AFIExFYeJ!qpJ>30EL~OSnAo+rs6D zR|=OUZh0qBpug%|(a~?&E2j4eXCan(y&=33RP}w+I~Z(-_(n&#qf2StH>IWgqDxsK zlv3yDHxVp#l=G`69QHd_Utv`nxT-3f0kS&t?kcUOAue`Abh!o_YT zOw70AoVEKM67aiP`R#x_EX_Qx^qs@GCo=Xa&)wj80<8IgU*9=HIr@b=4~<+1rZA6c zOt7kub!2e=KX?u89KAG6~9 zaj*wvPSrmm-Uk;c8fZks(h2G=K}qELN8O}aap`2(_!))x9^s0_i-gM(zbagjxKFq& z@q5A*{RL+o)1Wc08H9H^s{C`n=Y)C^G;j+Yi!sUop3YR7RlW;lWe&N)+)^=RG3DzvT zzcV-E((^7%UFX}t61q-3s`!5EWd_YTYe+S^@=sp#^Dp)CoA>8T{|8(Oyk%!)AGxZh zWr;r)E=T+~;qt^i@7Y?+h82i^EnJ@XG2wE=j|-<3owW^s#wgd8Pwl!UJ)S+ zF%JoZ%pX{qUIcg#Ha?HUN3FC|uKswRgJVI)LgG=sPYC@jV}?&$sZE53B9&f%uM%OCqsk04)lnrvRvuVl9s39d>TrRMNSiIo7?3$qdmXuUuX+YJ+~uk; z(3y^^CzN@pr?MP2X9L)H7NqyOLzN5=pT@*qty zMdB|BS3|ssa0TK`h07CfCR~oVAY9hr4-&=ryTFzDDrR9?k&t=JhrWKOz~}A^w6zOV zBIIPvVb+1vs|5q?733VIbLqaV9d{L;s0Im|s-<1gUogj7o4Dd0bwnBZiQs-5Gw+rRR=K69C z*30}fOAWbtlomgY&*0!lkVyw}Q*bT*@fR@{s2YTWn*u?&Y+S3V0|x_fdXrgME-k0T zJxprJo#|HO$o){m#6T_NIO%vo#GF_%Jm>XjAl82~1)jP1hZDrVccs_!9PWd3v5c70 zic2QGIH6;yT+!Hf5wpN;s z2Z#NZrpw_BZ2((+IRM^BxE%3l;qt^^7Op`23^?KQCO4_*LPu z#DhMxn^R4~!C;VPCI#Xl!sUsF36~?z36~|_6kIh42VWLpfp|0F^2D19mm{tbE=znU zxM~ufI9l$}L0AIwLPW?_G_M~o;40^As4SwgLPKo2@^^CF@nVG}f_4HS%Z-B(1;f%qW zVvfr}+ltszVdG9A-sF!~Wwx(3!$ARLEF>QFr-YD^X_?^{Zn2sOvxsG0-p2seII4;8 zPDhmq?*V21xiJRsh>r06q>csAvatJHVI{)RE>uMqR$^hCcHlkva>S&!S^5dSTgPI<8Blc}>@uFpb+>gL} zvzsBI)GQ{|EHbdZJOJWV{s4i*OFbYi(z!TkS}j?JC~7GlM4~0Ip|8lY-oHQ-{PB4| zE>aZ#_?%cKsy_SUYz6|v%}P8-xE%3N;qt_13s)eXE?l1YJmGT07YpYeH9_6!sUst7p_2jgK#y(HwsrI zzE!wd;;#*`q`CF>WjNRwq}k0{;$4I*67MEl4e{>66^Qo`E>Ap8xEyhva9QGM;Hm{; z0?u$n?*(JFn2@>68_I7Y1VaVhW9mR924cbHNmK_?_cT9{`7C~hdJ3gzfh)U#*|-$u zrv*BjdQqjh8lLnd7rgG&KV<|K&v5QQCR1@qL8& z65(>hmkF0A?hvj(e1&i|#8(PeB%UW+Epf|0yOieS^=CLZ9Aw-Y#I3>=iH{VnhWNX} z6^M@!E>C={a5>^O;j+YY!Bq>ywmZWWy#q5dtwG4#rVoxo2!;x@h7MF>AbObx%&TX( z%mCU?y?SVg`uP)6Eq2o=zY4g*Q4wJyrejXHo|!t~jJ?PODia>+0=4Kss_7YMp$l|4 zAp@EvT0DaTiGH|_uA1CmrMMB9Q6g1zq>8^KpU14e>1J1+1<0*5E9ZMLFr@)W7oyw% z8@DgkF47<^xc?FvJ%?S1pi(OU1dC_%h*&#IuE~A--I=0`VN-^29$DE=PQY zaOy#dyZabc+1yG9xen&O!D%sb;8vmol^BTmgSQgxr*0)$qHZNCQ2x7a8WG{Y998Bt zxX^*q&YJ)1yefo=Kn`MM&2uDUbHf7fHyHcxF81Ms3|PIDa4^6{Kb+CIrJ*kzAS$!O zw-REB)W}FYfPqgrx{&vV#JM4!FI0^{B=^Lh*t^SOX}naW{3H%P22@o3?4 z#G440C*DlB0`Zo@)ez4EXKup184fxVLgFsr3dA=EmnXhSxE%4#!l?to$!-meIUs}# z8Q4VaRiYP@2JXMwOWjAcL~T41?d~d$2pb*Mcmm*5M@58JI!b+b%GmosVvfhe?*QMN z;QV9NjcYGEjrni7fVag54e=IdIVggR?+e5)V{@v)vX-vIdZ$0(VZ9 z`ZpJ-MEG0#9gd7?e+DYK*y_2g8|ohC6%p?1<)Qunu0pq?RKgyJ>X-=PPf7Kxhr(@LVE^kFAHaeF!~OI(AG!%R;UiHpKz zi9Z&u=vT@3IcQ9q5N_tE@^*lGJF1D0!2_$!*cspHO4ety2wc)f3dlIbFd@v@g=@fxEyh2q}#c!EC&NX8t0tD!es{n z5BoP~uM}2t3$mIgV!|bkQco}Ry4Fe5Gf)pMy$c(g8DBaubGk2a8n=d-NrYUk_#Z}0>*R=wXMSaxA8a|Qep9L^T;(XWE;;N*ZDOa0 zAC{f!%W&{WLg<&kVD-?Lo*}%}QR)>=do|Fj+j*(B(q8K4np$#*U-98caHUHW5#Hgb zMncX#@RP_MsDB9?w-E93!sUp6BV3-iZj>$0+_8JI9PAC!1k4lfBV3Mnf^b>l>B)fp z)LHRBh|C?9kTK2ebYEO$sK6a|DkNvRK&owq;nJq-F8PekVziAA&9q4Gv`&sBM~RTd zTcN)Y3`h0g%vAHM6n)i~k!%~80@vmYp?t4)TsjvvJ{H8636~>&8=N@;U0Dv^NeGGG z6)sEshoo~_B|n77Y&Rienm0CnsK9%jegqi$Ih!J$67dw_3dC)~Wr;5qu0Y%&T$cD2 z;R^nQSjQKkF%u$eaa1GW365$y5%4TWHO>TNzR#F8bC_2q!lPWE3S*xy6l2eGREbd1 zsrM6lcJ)kms0&nP>@G*u6H07#!|jIp9@uyXiSHFIM|_`fdEy6!D-bUhu7QtPS>E*sppdn0t|+YA2`uC^6Jp;k4=fXsC(V{+FdJ8swH6wmcxE%2Z!sUrS60Sh}v2ZoSp9ohZ zUL#yBad`t`{DN>b#4ia~ zAbwf6Jn^f-<%s))%MzdWg~S%8oyifOw^z)|Eg^T_ypVD;xYmZc37I%ML%jxy2?MXd zY=LcDx)LD|i+O2dx?>U0Ji!uj$Bc>h;(x$IEN*I{I?%5_f{kA$h_4bZM|_QN zdE&=~D-b^=T%LHTa5>^vh07AZDO`d0E#dOSZwr?r?iVggy!#jJdYZWv{FUYWAB3N| zB@wc0bK(elMfE)33ZV#DpK&ByDXQNC{!=JI7HS;R>io6yDiLxi%{_ueNxBl#t%pT0 zw>Lsbr|u-aSLk`U4iQ^qd9%d#fUCYvaj+;MByQT!&a^7@C(R7Uz|m|D;cbqp{2Y*> z%sQk6I>C9B2&Xw}GT}^6aSJi{BMFZ9Z;m@XGaB<43mf`ao6|YPrK~6HbX1ucWjg9G zd>?8SYM%TFf8+u+5?YxGyI6?6I?tl0)%(vC|rVdL8f@$JIpi0=?i zeWb`(^Pu^RbU$&Dl3!6bF8wT#iMUs|9P#tQsfv1V?P*xEY=m?-j~|4L6nH<@hXB7# zBqRP%xE%4Hg;PZ_)=Fs1G7-|<6h-)vsQi`6Y-E`Mu<_mzj}R_LysK~p;@yPH5!VW* zif7IZpb6|TCBvMgM92(*XFhsu?7Zp;zw9V=Sn6c>ctv36~>2OSnAoO5qB` zzZWh~{JwBG;(p<>#KqCJN>fWAt==EP&#W;a)0j2>1FkYsVALw2y^(SS4PzYI7O(m*eAF^r4GPh&a0#YsiVLmSw{S_PJD<9 zUS9#cLMT6Y=z)Op1$qHW0x75 zl?UD{urgp*yM z$vTkw++_&@yd!}K2f08k40NWWBEpLuRj(IZed@u}aG>*Qk11~eZU8?+6##b-$`5o8ydH6Z>iYrz32T09 z-?7)@H~M`Ukr>&0pKDu{L_NLgBqcw|z|5|lI(YOIFZdAi`ZsjToIi^?$o7VfH;=ep zxE%2$;qt^c30EYZFI=AZHsNx_-NI#we<55kt=_|+F?&VGkmhCMc3fqoz$?k6kaW60 zk@iyeh#sh)hmDtuc=#Awtl^5pIpMOzy9uYJ$N0-!RZWCz9Mw1q5f~|O2WFt(IZ$Ea(Fo0ZzfI$YWZ zHr_|#{e;UAA1GX&_#oj5#NQFFhBy+gNIXfnTH;5(Z0BljTiwGs=mBX8tR?=Xa7E%@ z30Fh>m~aK+CBo&2pA;@f{FHE6;%zre6zJ~@SM*WL%rqt;>oJc-eyG5c((wpD@6e-m z>!vpSO|SzqU+>%r74HdaUi1m~0%g8^F{3lWpdm^6v)LYjJDq4w$21oIWw#E7^gV0g z*$8XOAiM#T`7*(bP94mUez1+>6i-l ze@N!^nxR|f?*sh;Y&J7B^YyLFr7g(3DOYCLEB-3+Xt6N!)6ikq?>wy8QbHaDqw)id z#*8+1GgEgV?g(?5D4aZV1_DpS9C)k-XU-0MdzC64I`czL#+IU6{G#c_*yb}gLN18; zN^SQvcYFt6O)Se1m&dphGN~2%c}tj+tVG0W|CqjhTqNdLw~e6}D++v#hY|_Aj`Q#x z!+TT2qJ0fEe)S=qBwUWTBAj|+W~^Fh%zc-T?xukV4-=KY1+BPr)VfRJU$jK|lytW? zTsl2n(t%62!^SH@d}6_tYF-z6G8~))G8XzXVX$Aid`GNZa%7t19v7vF@K_`dym~SB zEzYY%*y6m@6V-E2zv#Ro!lRs5BO&K*j=NuU8`P)5#>+$eif}pN<-)0DWULv`n3F|F zcXP4`-w;*C`_qR@Z^6bz5x*^5j`&^S^2C1-u0Z^Oa5cmq30EW@uz9BX$K&cfx1cNH#2yqj=Y;yuAt3$(S$-(1nZ7c6JC zhLE|<3x^*n@LIw^t6ZQGAs4{xF(Kz~eve|MQ%6^y3_Oj6e*xAUSi*srUZ904P}4gg zIUbVBBru@_K3vwzqF10^EndFlTex&oy5uxmV#4ZIdO2Wr^M4o(==6iX0Z)no6QhJB ze#XCvp>!pp+80)i7{I&&6Ebc16`0|u9+a8h=T9J0hh5tzabSt}6D~)*zi@ft3BnbK z8-=SOUM*aac#Uv1#QzkoK%CjqmTnq3@33&WfxubevcwaGEBXt-`AKUDb5O-@>(^rH{X@zM?A952db`n49vPOjOII7H)V_eEIAydciuJ*%amJxc# z#hE{sD1!KHa9Q)E67xft;&yJX8zHh>`ET=IjvUv(4$GY0XFku`N;R1;925)vKi4A4 z?XZDY(g>y3!^SUK#0|pbhz}7iPuwkBf%s>_<%#bRE=T-x;nXWLYiotZ9795eG^fj- z^iQF_1vdT5eZDgM<-QXT=LfEe=vF`$A6VBONPY<$uPgCm!sUpc6fRG^&sKJB#ap#D@x(BR)(xwNISuE0}}XCqkBEK7?Zxj2QT^vKIm9 zZN7Wj-=5`&(=~g}Sgbg33|dj}k%_{Ij}98D-u5-Tn+If z!WD>rDO{fTSHk6ppAarf{G@P2;-`eGAzmt6f%sYB^2E!8%MrgUoZ5J<@ib`6#uIWG z%+CB8S6Sg_Y!zad^?i}}7~u-UQ-sSCA17RnxJ|e$@lxT6#4iX}AbwG}Jn=H& za>OqSr;Z8NX)rYAm=JO*=1d%qtIQlYCJeM1ixxli0WzSuWte~c!%-eF?Y{|PhD-mA z^t3 z@xs**A1GXb_#ol(#0LwPBaVd25>Kp26v#mX$hbh_M&XLYO~Ta>PZF*`e28#);=_c? z5m$uE65q64qQJB*pMj+}$BU2)Yi=uqT$>@8b9z3R+Q}g^jL6KHKV#^~m-ZW&8Ka)R zdAJzf97#fMm+Aj+V}-duhVthTxJ&5R^^DDJH=WGbTxdh-*p;=fPy=K0_?XX67@Her zC>>kGy1P`V=ZQOLDKeVU`{314tj<483VW>eQcw9M`#A5g~J$*PJbpekWM7 z>4Z{|I!*T@rUX*w;$cq`I?dEP1+96`H>a5+39gPR9Yvx_keWZJbvl(;e)ndct!Z)kt_5DDySi_J2+7WEIzmlJNF_SFXLd z23At6LR{>c*qJ8nl<1 zR{;U1xWG*(1D+$4zlQWW&Phu2nkH15*GW!NPp|WYTDw<+_EPgY00Em_V71I0xb(P_ zG}Yh-j=RAIwv=A;oL7l(ob!sl3D_glF9C;rjmIU*0*)2xtAO7WYA?WvLLCe^Stx(m zSf>UB}|x~O_xRDD`eH@d}ZSP1yEP`=kExHLo-)0cb+m$-p}_p;L< zxhOr#^|;iXE?J37tI{Q(!=`MSzB#S9bUtkS3PZdCT=iLugT922c!-@D}zT!n1r20|#w)Vb@$;=KzS_c3u{XS*=f_T-=jWGo~ex=TXnFD5g5?6#?C zbHo@68)$9qRbqhA&a2EoJ36XF$eIFsr(@SM_69CcBV*S(s-93{H?DoQ4U9d;1*+)S z4LWw}UNPW+OtTKu>{s+(NnAPCOVJMk%IsBzI+t5B3Dl?qsbyp!?xy(>0HFjb>p<$0 zjvt_CdYOM)8FkX{8CY@d7A%$CZgjKTr)qvTnD#i?WS%yex2sn(u-9FPI3;7 zw+(FEl*B(0u1I{fa9QG&!r_TPro{L=LlbCJO2#@#iSPnPHU1co$pSxsW}rQsS3Th+ zjwZwn!stJ zWR#PX2*2y7#?t|rEO6QwXcOmEPk5Z8$~sWl-#8seowh9y@T)FxM0m2JD$F&*Q4yic zL!Gw$pg!DrHU1Fr8ln86wbvx=rB2&KFIRlp=r-3SQ>RV4&1u$d>a_JD;=8c%X(RsP zZtesYi1Wf_iH{bp;I9GW_q)oQCP02DY~TQDuM)i`Ij_>%bJa8LQ0LXSAt0*`99x~f zf$3XZAaww>S2NR(a9(AdzP$E%9Kt|37pS5GsbjkZ)GXe-N%C`Pi7H1+)PqDD5}gJc zpCaP#36~?DDqNoU2f`JI&l9eO_#)wo#Fq+JOMGsvovV4S?#*y;9!OJQE%Eun6^Snt zu7>zx;R?i;2$v_mOt>8JEaB8gwOqV&F$vQTgiIaSIC{--UL`_KBJe1qz3S=3DFxmu zX)ks7-$NOnxN;)G;h4Mmj^Y7;lN=QhPH~ia&$S9Mzvv|DHpU9OAdTNB&IexNN>WwN z#wD)7z|3h`VulLbA9#rw%s(Q@cMe5$ta7ONlD+w-uALln?p3khh8=MJ5p3K*#8(KH zBfd(wJn^-{6^J{9t0BHoxFYf0!qpPrvWIJ+z5yKE3NmgW;`zcAiEkIKhIoN+1>(De z%M*7Cmm^*zT$XtMaft%cR+O_bT}8+mO@I6hVHqmWGTa(^n>R^>oP=o^9Z3DeRtKtQ zAg++t9X4R*KN5cpV?xj5yxLy1?& zL=HfN60CXCIR6|DZgb&PC;thT{^2C*9&z+HY;kA9nj#4saSY6Nvibop*^^T_d@11W zIB!PfpEg!@x(n38_3ssmkU2~RgmM7Y?Qc5jy;tf!6cwH0id3^@MGf$Zf6)i=3E<4) z-g^!QT!U<8&f;^f7`)}cPR@We4$SP-NzCODwZBSo#a}GO*c;WGGG0B0?!QlxYx6Wa z;rj(PXBBqf^9UXP4-|BJ)2*O7Qgq9Tn|keL$i!57&+)6KXtc|vcI%6{L`nQ>e~I^S zZt9_5OH|~Gu-DvP^N$Mq&T-p#24Y*5BRN)0gxqf{d3u8~9>4C33dw zk4H(&9QgEy0q;l;{3tGMj=is*F`g}!T_ydheNXe);`;^uwZ)Uj_dD2u=9g@I3HOVy zy8DJYV|066+|(Ik$i!57&tXZ-VWy%Fp6_XT!}klc%loKhFqSTEmw#bXK9@cfy8XjV zWrmsxLvkwS+;q(7WKB$BS$|*XyPmtBswN!DUSG?!WD_@ zgsUOmN4Ns^* zh079O3a(lp2bYPkmUxzMMdBX{S3}$(T!HvU!sUst5H3f2wQyPDyTMfp^xKAK@(7#_ z^Bhdboiewx@d&}SG1O6@SdF2s1jU4b`ykUD@6weBd4SAAF4J+#48?R@Btz+Rm9*JS zbLr{{d4|m+Jt2?bkj#wuyFsjihsh`jc^;r-aokm-B;>|GscTlBr*Mf_Xy9dW2BtF` z*8IPh=AV&r#Evx&`J4iK#89l2r_xZmDD_rg2)6dKH|Yj-0Mw2!5Z8Q2W@`Jn!#g?T z7MRx^*3LDDl0$BRQTl12^bf-ZsICUqteQzp=i9&ro+kp1GeeH*hTn~t_rS(CeB%3r z%Mm{yT%LH_z3e=UZ-KZ*xIFP!gv$}{C|s7fUATh3Lag@%)DigTm6GL7QX*tl)0#}S z7m}Gb#e~dnRB7oM&};=EXK6Z%v3GT`>j@>c>MC7#17mYlO(zh3)x~aNy1gCMKq%={ zzx*1@!yKlG37O-6nU1knxdWvC!@m)jz?NyGTv`R*v@E z0=w-?LjN6!uQ9&_A~V#_wXv7W{1Q3y$(nk&WYxz)ZhrY)wvz)+WoY%ct7bd>97n^6 z`hmuon7;Nmv#Pw5wIM{xFL31^oCjHauOPlaxE%3J;qt_@gewsLShyPEe+pN0xX#Ym z|#@etwi#KVNk5swruOMJX=MdA~Lt06v7xB~IX!sUtEgv$}1DxBI1uJJ<5 z-`wB!fP6pLz%7qn-Oj5-IM8`Da*mvXY442phk+!0Jt0@p^a%rTl7ZIIff^Wy+hlH( zgd<#$O^nS&H7c*h%#D~Or)WOBBy>44(`R&Y;UX7(GGP-a^Q(vSuflvA@&E2tvvD)% z88WbRD?P7)nW-7N)w&AQZRjJg@kTHtok!1OUX8v zSoKcD0cV?br`i%u)W>%!V)~}ssVLh4A=2(txO^^be5WFQO1KGgOjTZ?B+CW^c=U4mLhd z#4W-Vh>sO6OWZA7AuaxX(3rDx6yO;`wF6!vR9c`-ouowA$$c&PI#6CUX(w*Zo^fl%sJuMNEPumtie(h~JO3@uR~TvKwtOVr>e z>cORFokZRJwM5mTC8`!mmbgT!mR?+X(@9h*Q&uee02mOfniqm!swv_#dS zC8`!m-gAjmE&aHZ-^cD=+}XtMfHR*;^=3HePY8*R-`6@Lj@j}7^uN{)e}D9s74(~Jsj#l4Gi>x3slj8DrrkXZ}SCZLN2q} z2e;&mXnsP_J%a;II2QVU&OkL6x~d|=#g1z6BLzMSoc6y8)P@ss4s75VUQ5)G*AjK) zDfzxj)Icb?oBajr9&DMFu$dutJQCK|KLXw3u+OIW5Bh(}e*?rF1N&^&R`??9%dnda zxBQl{XRYVY#E#cB-fs~1E!g!C*N^)Dmv}?}$MvPfn~Z(91NJW10mJ;2v196|KzAza zb+EqI|4RRp|1f@CfDb)BK-CoM&M(h~r23zvaK0b$A7=YyLF<^RO-#!(Iye0_;n$ zyME3~jA5Gpb@1zg?S=KD{NM6l`hS=|&41I++Zk;SyCZBF)}QZxs=sDK8{wz0*TcRJ z>&O3J>G>Z+--SHO{=VN1)b_Ol5N5EbK>(B3hr9U3=PlmnnKg_qT{?CuLGgt=u zD(vU;{><$7f2IE|^1K5(@;}VCuKq`lv31XcT?G3YtUpWsZ#OG9wO87{Z0!$VuYz3& z>-)EF4rU8$TVYRv_4V7P=w_wp3tQO;U9g$0?RXTdAAd=TZif_o4}L3L-L}XBdnc?P ze@cpOX^OseI~$=7w(cu-d@!sZe;9aQcVdeEQ2hQR{_d!6jI|N>gZ1N6*9*TQbo85o z@kJ^A#T{(KZrFa!Yq0`eUIx)APG4#lNm-Blg15k9qxk z(bu7eE$(c`<6wRLsuW!%MW5N#MreoagZ&WJkAG{5Zscyh(vIJ)wRW4`tsS_hwIg8d zNdBg-75Y#4MLN%K`xO5g==nYM2QYpDRx(-CJI>nZo7VD2;SXW#>!+pY_(Nu2-(Cj> zpZ2uEo&@Xbw@uN_O3@ebZo3;cx1Wu;DXbs=l@#5cDf)lxZzI$mU~NC_-(fc#Z~2{J z{e1N6fqn#Z^z-#?2U_M+*jK-0$M3-U`g6edc)Gy{`AR!}WTLeL54QFQd@tn$_5GB8 z*VRJ*`R`bM6WBxG$2gB+{M(dx|Ea$CZJTcq?B9`hSW5i;aQ#VG&g;w+{a3)VF4ny! zML!OF9qa*~zpYmOe!Sk|;rRS>Q+%|vEvzX2T~`NvFFp(GgQedADgOMy@=WM+&@r!{ zueofQKG^n2c03!_*Y5@1*DXlVM~B!5y|BeY?RXrlAAePfu9BkX7lqm@Hc}t#hp>M9 zTT^r+C;Lh}?ryQRc(}D4u-C!*UXxOEi&ONiN7x8`uyse;@xicu{9)jI-H9ps+;?q+ z4%mL!HL!mC`%`pdj`o#y+;fbzbyKYEg1r;g_nMNTTbiP8Jjt_?Q;|~Mx z>rPD3XHK*c+F|=(KZNz;-^uiX;v*U5Fe*9G_x=M;ZbAgS}4%-L&A*>(&))d{y3w@;>cU@#{;bLps zVQ0hoUgJ}A3sUrTm)HnBu!T$Qcr2_R|GgC5#1wu1Wi~?FENgpVSHSx5=cedZr|7$W zXd@IZx3(R2Hmo0ie2Q*CioR}+jnD&I_>moth4tgVm!g}PqVNB)jnH<5wY{(_VEy=W zQ*^6S^j%lk2!*-Uw!_Yb_2Z9E(Je^PM?bL~*ld*Q6BP;uL-B^)^BuY~2lZd@!sZe;9aQcVdb@ zcax3K0oxC|2G);%e~NC*&A!r(dv39|?pABNVDE(Wy{4q-mZs?2ZnF{kVWZpacrvUX ze>8YscXo=tu)s#>g3a7%$D?5V_)AiBJEZ7)@3Il1yRGepeF)Z%KP^SKJVoFBGeo$@ z+E&<;VEy>prs!s+=zs8Y8{tmauiR_Lb+8K;+VLZ>em?s3!T)OL=6il`rucUwPp#+2 zynep^`z_P9$l6}m6|la3Zi;SoioW9k8zJ`~^1x1q_2ciBqMMhZuY1Tw=z%RfY{z3^ z{rK;t=q9G<>mIQYmcYIX+xkmCvK==(YHbJXuwUD84%YXgt{3``eq|N^fZY&&wJH8% zpu5WRV_rXB;c?4!!Dg1&@hDhdza&MsLyCU=Cv1cU*w>%5%^B}KP1 zMc?+kjnEGpy&KsxqFb7xZ+p!~=!cD7x8upMe*Dqkecjn9`oeM>p$j&%!j4D5`tg^f=ypia_r7T( zM8C1N8}=bsKmN27-SQND`)?89Eo)n0PlEO1Z=0ft_*7m}#fc4|gP0_7R(RY1hBNYB@Z9D92SU>*w6y1Upee_ow zp%=FJu^o?t_2aKf(N$9PkNn+6_!sOa|HK&9k566QKkSv4pnKEv+u~ooryc)ujkRY% z|2{19`d-In22`nQVfPqd$IY+{2iox?u)aV2df|U8bZ2^gZ>IQnBTp@^&xd7RKVREm zTTef1G{lZ4!}|Ks;C+2__ z=oY8wqmedlFKls?9glIR?pugJlV_rXB-xwRebyI75V4s2Y^%tk;K1k8G zZ)W3W3djR{609G8+Z5fb6n$|E8@C%ax1}9#3hT#zB}KPqioS0v)V;N}J+RNf`tdJL z(S4Aj@7UJH&DB`j20IJ)v~cpJAc!P<7%*|2{6@hQ3mDf+r^*|}*&+{`eH#f)ssp zDC&kS9%jemVEy>3QgoFReP%N1hV6s>5Y~@>Yl?1Ui;d&!yAQW~vDMlR*y~_@{iGD# z;uL-Bkv480Y~4|Id@!sZe;9aQcVdb@cQopT?T1|h>&L%8MK|Uc8^_o8OtF03vDS9M z-U;jLr=;kXrs!Lbw{iPm>rSxagJJ#n!@&Ex6I1k=lWe?p*gn_~Vg2~GrszhVY~%R) z?o%vZY_qll_BvQ!KPg4GI7Q!as*QUK?31wFr-kd8i|g}XyI|?x0l%&k|2Lq|c)GNF z??SiT>9(G8zh~`bu>Sn1YlZ$0=#KLIZcp)VhhGNQzX!{_e!gg`t)~~ZIL(g7!TS1D zDY{CEzW+=cuk9@4fn5RX$Df;`Tb-is`hksGINREG*x9gt{P8Ke1u6RI9Mlb4oMFe~ zVEy>3QgoFReSf=+*LE)Qz^;Jx5Y~@>Yl?2nEk?7N_W2 ze`w?O!PZ@F#|Oju@rQx;btk6ib8}EPY(MN8SU>*#DY`K~vT=NU&yOu%cZIcGuy?}x z`Y9>8r78Nht8CnU*l4aDPlom5j|T7S&Q8%6euBDTGuPPhC|E!Kk`&zzDf-@PZMb?N|MzEW}7GSr6_2V(lO^Da;)v>N|cSwoou@Drs#X`vhkO}z6QGz z){jsALtOs|HhZ^?!ofc^t}tM{&iUTO-k{nUjh2fp<`Y@-%4Ds z+;8)o4?AIz9iIt%C9LoN9q1cj%dm_`|CuTNS3>^)EOlx59{q)_YsdrE-u|Gq_rm)1 zQCACn7j(Ygiz)s+@SA|^71*{E{U?iUJ-a_-?duO)`!=j!A9Zce_d@6U4SK}J@%=xA zUpxG+fbB}jSNNrk(+XSvD?2_E){jqJ7xXpI?d19WAjLoPsEywayFYZy>*u?AiPg`8 z?S#DCPiPHwGsIHfr`WJxVWCRZLsaIe!jine++Cd{8zyG z`jyaq2-^?)32YDi7{~WN3D+-&U0Zz{pzhJIV_-QyKmLz4u*{>d8-2l!n_zu??`D>H z4>nh@k`ph2bJ9z_&4jA#(GNB}`V(8%Iam&rDeNR}~^u+=41aks_=#jVDL z7WY`CqLmu0C~DPMON&-(tm3{zE!L&h@0|1g-Zs$Gco^$TG zXJ(2(U%qj)Fbl!!z$KtB{|WrRz^}mXzybIf9N}-DF=Fu;Q00b4$S;Dw5xfPwJwiS) zQS9Fe4(D^tb0a?AJnk5gZv$JW$Z)Qo%mpfneIQyz8ob1$JhG^TC@V>~~du&oYVY z0B{&s1J;6PgMN9UA1|9+ygTDQ)$}hprqu|TncMS6w>2K%hCnEIK zem~9+0Dl%CuXZ&)wHp91CHNV&XKg#as*jGKxdmDM)HX>e)>(8G4;}QPPMeh7g%Bx+CPwjR# zzHji;gS<6>*Mc{Iw}M(uttUT!n<2LY7-dK6N!#7Bi1xY%b|!%;r+N4NZ`VthUBE-Z z0&omC0rd5wtN6YQokFwi{{Z=}3^|P8h^^J&E<2u#T z-<3Fh|JxwfzmxJB&nn_t4Q>}S2p$X00ef#L za=GA=z8r5Q*a|jmEyo4h2)2WAl>Y0-^OzTpZ!dwJWu3Iwm$JN^`m+4%cJdNYh5SWLL}P3 zoE$kG4*L29=q&@E0sjX60}NOvTC|y1&~~ZqY&h+0FnQ2CX&##T`JUz2_y3%4AHDK* z=wC^kt?2JX{rXb;RfAQ}Z*OJRJ|lKrLrfzWn-#vg7AJ zHzI#MsMlSs#wj^?do^+X*usY~5at&)7k50eA&?9oPy6JBr)@ za3vV-!f3VZ3uq871^gqp64dKN z%aN*wTt9L-zHuG))}_BKc6R{>f_s9a!ExY`U=j?xm+}{K{u1yCFxXWTa={A*%JH3` z9Hsw8+1Y%Tu$`~5m)4Yb>i7NJuzYQV{6gdwby8l-F$jBu!BZpbYaANC#t{|&Hk5N0 zunBw!d<1*~R6BVQ`P>t^Vc?-)0T^Xh>rL&R7-4@rc4mPpr}ZqgkpBA&cAf)Y1HS~n z1vlPJbeeV-TnXy;zc=ogwwd`Z%Fcz@y98_m)ox-Bu`*z=U?G@k+rHlfy%RkBZP4!z z?gIMy>aVNv&r$wXQ1yNL8dp?#RnL#Jv*nMndk^`z4_pe)C0?z^JnBvB(T_jM&WqUl z3-~fvh<)|*CH1C$eEU&$P8cMTCxJzvpO*&gFN9~<=~SM?c-I-^*X5j zJFBmLqskLyN8A0n$`@t#EXsQ}cpf;I`q6sQ>-G;3*Kd@ayRdgJct5DusUNqt1Hb;G z?ED3L0r6_w8t11G@ou%3c<2WX0`>PM#&CQj*s{0Cw}W~6%5gd9#~WqmMeO|rTn(z- zMf-@Q7BKa@5}pH%>t;{?5cEcX<3LY6I10UEzNfy%yOw->395hJpT-$ge%15i?rc7y z?5?GJUxNPu8;MuzRqOHUi297OGY~twgL{EdacjHr^Alxf7WOo*XCmUee27@Q9=r*> z4O{{~2tEov4*Kyt1HV?!?gmsFW+mZFh2pe1$O{_`Sai}0xt!x zije>8AhGZj7z~%=La-6M1N8kR4i=^!d>sr9@g?Q>didUl%JGq4A$S592YoB+tA9Us zzXQAdR6J}4?h76P`u-m(5T+H}ag-b%4$4vb@73_Pg1-Tu-$3$*j}>1hfD6H8U^)_c zx6L@gJ;6A50qDywm>|qDuz8XkF9&`3mZODv5&SFoGU&?(g~Id(i@-8)CO8}P^>>F? zy*7?>j`1bs_$tmn4C?;8<0ItXh2MON=DEYslw=Q{&!!Lg;@vSR=D)%Mw ze*XRVwkG}&pkGhE{P8CWQvuEbt3h90e+Om3Npj{Q@N&?XzZL%X;EpGY#CXt`{~L0z zf^UHB;9Br&(ASTW+aki>z*EG+9^l?!drbJkBEfq>IZFRsiCinV8dUwXq`Xu9OXPwg z;k$uyl>V!HFV2q!HLkRzyvr{Z91V^GtHAY@e`SVP*m0)d&fq?vZ@&%skHAmC0UJnu zOoezX24{k&g2AbBegNpl7o|5(^*x1Bj^m(;ReN%J*t>_k>j!QN7Jw?J*j4#%8;fQy zaC5LfsCE_S!=C~E3VaN_u9ryu1B~~U<6A+sqqw2%)#Gy?_q%|Dz&tQ5DesPjKN&0rqkivrV2Sjvl>RjA9}k`dM#b~)I^x+- z_GKFozU9c%#42G0X80-Hd!%V7$l z^e#_Hgx?&rksQwl&jQZ{&jb0|gs`$XT3`K<*dGIq1Eb>c1Nfozk0y?(UOb)k-&y@u z?7swl1?G11J<0J!@RxyCfmefVU_0nrQMso%eHb-PG}cYuDpQF#Kj@diw9Iemj1K?JOl51&#+Nfz!ZwU?X_muA+A@=;!BMqo9Kabr8iRUfBk>DtB95?|y7WCuQdQyK{Po1^@zhZA2$~OSie0<;dI%`k! zruF3K&G(<&U6^aYYr)&WyTAv)hrq|c{}p@9>lx4Y)$eNgefg-oJ|9t@5BCrYUxM8R z%W;2jd+={I9uEFc@>^rSKX_Od{dHEKW&MCTiC!h}-+TPhXI1juKjEc{X z=ZDh20!LSS@pRUIXZ7`Y@v4o)@+;sQpzp6wH(_$X{lR?jXmBc62i^ug20jbE1pXcL z{e55iPyLVCn@2qQeQuhM?_1u^+S>=eoz0^kPiOUKM&$d)(l1?4|55tSW7lt|Z$|h# zsJmov0(ca7Ht6R!O1|a?mY>x{`%&^y<@|Gmea*uaoc|U0@CK6Kq=#g9J8)-k2sjKJ z4Ne4)21~$GLBGDbYXAS0{$cAGf0TYBcCP}j0dE9Xf~}xm{(_#ui~+}kM}fzJF)$96 zg9&gB=-Yn-{yVV8#v+jm?gs7${?PipBmD2#N&j8dpN!op;B>Gt!e5lU?kD#1v!UeY zbupeO`KWR}j{T>>=fO5`4cHET4f^F>mwn&fkEMSd@#=nWEpKQ2cUFH=FR@q%P6dlV z-=Fg3oKJwY;CbMsps)Wd{PW<8;Lg28ayM`v(AV#({L9#X4QvCqi?F|;3N1%Q$ zr(d2Zf1TBj%3C$%OM>S_lvnfY%g@`u@}saf5sZN)U?@5lY>Y{v;C;#<2 z?0;YRKVbK9@J;pO6+`WJRz4~(hmxO>;1Ln|iqaqc1IurU{Vl+uUG&#kecd;#p9{JI zKg%NgDZiZakAo}0Ha<`EKIm0nkPm+_I076A9sy1Qefs{G0>+K-Zd7dxMWUxNGe@mrQ0`|>@Ii;};B^Q*wu!FNF4OIPLlvp~NS zxC^)!=-Xekr7$;ww}7{SEnq9?>np#C^RIw!M#xvNANoviC8(b()6cbi3Ge$m2mV(P zdcJ%&_Caq3ZV7G$4ghxs_W&;f3)ufTK4L%V4{iTZ_G7;eHnA`BUeGV^Px}jV5_mas zs^`nsvHx`*cqVu*cp>;pQ1dXEd`tn4i^xxuovW~SE%+-i%5DkvXM&Xx{-W&OhW*>Y zd%!5WbFhCp_=^aCQFb4|{v+TMV3gfO5%#0(JcGSIgRg)qv9mhDUX)%N`uhExx=(r; zcGRxlexmfgz}|mB{XWqp*i*Z{y(qoS(c1>x0o1&yUEf}m-azzy`7vBC6TzdvBG8vl z!q8JR8(~#f`ZVQ48qDx4!zX?jjaH1V07;3wGaC&TkEF3+@6A0f&KpeE+NV zcih17ZYX=}n?GOwXv#SaJRbZR7z4||xuEVN_w7aL%|g$YZ-wtRP|gehn?PTF74olu zZ-VWhFMl9*W`k#eS47w!0Y4Eu3iS0C{7Cv!JpX?A{CL+T|6A-m2>u>?9Q5sf3jZ%~ z#BL%n8Y}^4fLDULZ(6@E^%eNH!Oub8-}>7BU$M7{^4$PxKE7{!owcX+v@w2jz~P|p zKT3Y@A6R~mF4~Whk1A(p+mWA-{}p@ZN95=G#bc1UwC_0~^3|!1KYYz#G8-b^GfZ-}lu&eLeFNrN0EbcZ2tV`hCkeyGz8mpdbI& z$gA8$qO0{F|oMc@tK&ERd|tDt@#`mjBuM2CTX`7TDjtM<40 zAGh}g|G)X$(D835d*e26exvNw;HM6p2R20b*WcOLZZ9e6 z&fu=#-r!L1U~mLD8r*sx(bvz1Mg5+_hVu8#-r}!2emm=bUFDfXd5#7z0#}0V;5VS( zK8CD9|r&l>DJTu>3(?v>zoORn7(2zXg0a!r!{|??&&pKeGN*{2d22bkToj^*_gM zRR8I>7wyM$_meYg_eWx=;L-yGp8`3X0@c^g*=~iM^|hD3uei|97oWj#$}w-$zb}94 z4=g{Ui}s`B{kXN94`Ba!ur0#hy7d2w-rs*@{j>3R9(ZpT{dZRXJM8utA`PM!I1cpN zOO*TpKd}6MU9=x1A63rT*k1rHityK2{fmEO{Uh;rG^pkA>n|$)&g!dOKTpf>yRQDO z8~s~n`#V4W-=e<)4CrU$;QGq9VpscdwdfgaQ@LTYG@DtFte?0bzz(wGV zU|0RGq8zKipXB+Okz8$9R{u8a-VWXceh}d=N`CbZEdQ4-+K-ZtD(CiGmxI9rz(c_?px0c3&e|LD zBkQ-5uYZABzRt$qS$(xTkn)WOF9WXuwLkRzPvU$WJQX|#JRkJ!-3b3<+pAtjeCvv* ztNNPPb+t>)yYJu6n~q1zD8~|SeDU?yAQv@$`SLnmp2~P!1>PAkK1azf`hn%6et$D6 zo+$aKa{it8-vqyiC{Jhg-}{mEoALJmsO9nNJ1YLp>Z@HpPa|lTV?muC`ToBqzO9iP z0PYXwgTB2Z;eTv#=*vIWLzp$-m*6%%eMvcf7k<-?<+vGa0e!7);SU9yK-Kf*&*l7u z;H93PFW=HvnBH4~TMKRv`ttAcxvmevcJMPW!RNRdKwrO+-%Yp*yav1$?9JzIazI~S zKkxGa=lk=ym|Y@1AJbKN{oG6`pL@9syf)(VFb%95U%)uI6x4C_qlob{f0$T^gZ1Ezpl^T8LBjMKE;tCB4Epj5 z4;E$xxC;Ch^ySAMDoj1N0K6OY<=6gHm_Z{1^TCrrUw-jn!mI+eg zUw-j;VOD`_z&;avNjaWAQE&mc5L^!WT0JKTlMjvst3Y3V>11KnfNQ}WkMbquIDWL? zLU1wo9O!HHJ4TqX;B@c|(3f8^MVPf<{}FX+qHPZMS_xDh#*KL12v;$S^^Bk0SoIZ2p)CkqY&CxgEH!c&A< z0j>hS1%3IkMZ(mB3&6WUIh+2wwpiW|DiO>FPX^PH@@{dd;3{wp*r&{wl;i1f!3E$# za5?B}^_(e8J~$Sv0)6?V<-)81*Md7%_>yuQKUHucxEOp6^tJj`3Nsd*4xRz}@++!@ zSqt{8mg9XvUp`(V%tCN6_#EiV_dHFQd~hsS1^V(!YlT?@t_62Y`jT=SuM=DdE(V_i zeXV}8g&7M@2hRX~`4w}7Sqt`@E64kSzI^>WVHSf+!Ph`ve$dZ_nGVLmOF>_L)#<|Y zJVUS_I0E$L7n~`~Qg8+M5$Mb3HwY64>%kjAIh+2w<`?q5-`RqLz{y}*Qr;~%M{p^) z0{jT{~p0rDaX^V5?lZ-1eb%pR?n-2$p^=RRiH1w^crE-fNQ}W7x|KM9KTj@A-EWP4)nG9 zT`$a7a5{Jf=*zFTL725*&l}}%j%!-Jq|v_8wsd-7A<6o(%f(i<^a6 z1+D@6+~-Tm@$}ycE&vyT%Rygj?ft?GdO$EAJQ?)m7cLcM1-J_Q7WC!w9~34I)`K^K zzWl0X!t{Jdupc-A^yL>kEX-1H1^5x@%MW@)nCV~~ycG21S1cE1E!gu9a=b6-%f}xT zW+Av3d=B*Gdp;&iJ~$Sv0)6?Vj|;N~Tnp~_M_*Eor#~UM09*(z2Ys!zD})*Jq+mXH zGU&@Md`g%V;41K2(3j7DT9`Ok58ep+@~fT^rsuPQ{lF2RFMsKugjo(g2d)Ku`LWLn zQx7fx?*@JOwJ!)Ws8ui@JQ?)m7r!XXDsTR98Z5qZ~?dwTn_qL6ITgS3_b~d z4Epjpe-owYAN(2U`(FwFC-4REMetql6VTT$Y7?dmoC#Kd4dD4;;Jwa$ONbV5 z&oy#YQgN+gLSHdp@uLYYUM4IrinBK|bguXWGFFI;npf@^^ywfcJq9 zgO7r~f90RxyndhlstEa&n~8;I!RNsj!M}iSgTBA@)$i3uEN%hz1%Co=3+@j3{?}K3 zz?Nch7jQQ)0s8*BD&N{yEW8B%4SWmq?RQoF%AbgZw?KVfp4Z>EEXNnaH-e9WUu+=x zUIWC>Cg5gZ4tO9q74)NAU;WRp`!(2oTk)_RxHC8g)cpd@9Q*Moznt@rgR3@>e3aeK zBJ6in{*CRWL|=m4c97$q;3nYapkMy=)&J>^VsQdE2`mDAe_fUT_fBG=$IgPiz@0$f zepluHv5Qz(3w{aq-qp7($6b}b3_DkVSA(}k*zc-*VxYv81aATF0{v2SRsNja#lrPq z^&WEkC3w$59qbBbk~k)SlfgRB zkGiY!f7n|rJPW=6egXRSyDC3;AF*&eSPq^G`t~mzD$Fua_iZjaz?YQc7UZ4-Uj)Ad zeXXv_uf=bl1I5x{aN~U6vK&XruR>4vO-AYe9JwW+?!Rp1*!QZuZ@)c4zKpof0(IYE zOGJ4NA0{4SU?bQB`tf`P-|ZkdvnyB#uCII(c9ww6;2Fbx%W~|??~B|}a2Qw^p?@6w zNnjB;6PyLsfPNHFa1^yMRFO~_)|G<1rHwK zTax2*JbSu-=sNgz?Dh5RwR7xSy$QW$a2ND<^X!a|&_C}GvG@R3gq)Vg_xCt*Z-Tj$ zGpf8Q*MoQh3Wk){)zl`9LxEFE%}J|Zts7kzNpV$0sqtQ8Y??nqoFg$>k~FHN zHC0vdYICwIUQ$0ZR$NLit4z&H_6erl{Lnh$YZHP zcE1O)z0t9YCEd_nxskk4+znpAUpN|KPJHp*cQvZtj=o~w+d5LZ9Q4lDqA4i%$HrC8n(i~*!H=?gt*e_E$2y!@7Jk!%}8zTM{ zhffH5R6VtNuBSg>ZclegaSIg$!`6b}BJ{OB3(!|=vkg4sm7DS2zP-Zyw-Kv;?r+4u z;;XcOD{EiA|0cs_>IaXAzGB%NtCfCf{V8*Yd1Ljpeasyux2AK4NsapX^7ZfY^e;gF zf==o$^Yj;?zX*NbpMSjE({Dn*3H{-u!-%<8E~xN~d1Fhe#(e8u>(IT8AWm3CAKf+kjG+=2Py>+cky-+ra&-!nC=5k4|;qVMPL zSMO}@T$)Km=%xCK`RIp)%t5!G$s>Te8f|GJp?WG`5TQT&Y3X=d>5o<3*S8Dv+ICdN z;}`S z%bV^dI-SR4p6+gXYn`8j`R;+vJHmXAK<5)-zGtBGgfPExpyPj-@0A+g!+h^R$Llb^ zNopJp^P2`bzJ~eD0v$KQ{N{muNO_h5&Pq+z-TJ4Wz3Met1KytCbdT#u1{uA7-z`|ZJA?>&M~zw;Gr zgd1h4N8A5zl%*brJHBH*jy1gX8|)XpQcra9={)P%Q{gXQ+|vHR_7&DngJ&lyewhd6 z1P#m|jJkVWZvJi*Y#9_CAma&iaFgRZ#($^bUEbz|CmxR(K3m@GMm8&-nrxHwp#>jb3@}4up+@bo+0mQ{B<={ey-hWn6EgN$zWS>$l*a;`b=*7htF1 zV398}{_JZqd>g#YuYH|p_-y5>G`!2hmlDV{b8KG?@C9EBUjhFs_~vhfKapYk0r(tx z`51Bj$?&$kMTbb7xz2hJ*iV>ei|2Lhw4EXbmLUHrd}68aw_|@(6Gk>W+Zo=LtLSMF z)P?Q+;G5uAV}Cq+!!sg(D|~5${nL=o{fEdui2T{`ZSY6aysm&R>Lm%g$dt#veq;EJ z0@FWVi6HsB-|<@q4diXShzHLaK3jSJf&GFl#lTwZf24e0;WvfvX2Q1lDcnlxP=uYviNe2toqG7fql6y} ze~HRZ5&lc~8{iuV{5n&9`?^QvPZjy=%&~nvs(iKZp9mei2%p$q>gDIye;dAFXW_LU z`y9Ryek<&lB)B{$;I%#5wA=jW=8Bz*#Cs#{m75zq3AH-H4nEU-vFOL z{t5W@ks`0R!LPw53WUE`+y?fWB3#}^32*N!?JLI|TD}c_me9dL@P(sA{&(=l!{>|< zes|i*Eck-4!ruaara83NY0<618)xoyKJu+oh1c>fgl{So{vG7C{j?yj?dKjNX2;(~ zZ(RGm<2%l0RyzLE@BI1Fi}3!u=pDmvWv_SJF`Cz1#Pbb&qDhGNX;*#CFlyybzI@cY5r+ez!U;3n~VH|^va_=Z4AtKgUWu+I|MY zw{I%)cVOqI@HtCF{!)QplHqlY0#OG=5q!e%eS`i%?u^}?TmyE_!+v5}IL;>c!Yzcq z2PeOCe5OZvzY_Tz<}-448w9Vx7Y&g-e@6SUI{|HdB@Pkd2KbFk!_HQ&tqt$$ceGTX zUDvd)LCQ}T`zKMq!mP^BNZUf&mQVO&XJNv>nUoG}ufiF-y zUy1zYw1=Y&pDq4VoIJ`Ikc^PO4EqV%!#H$qgm3w?I4py|JHpN%k4e! z`yTRbn}_rB4SeH%;`d+3Z)G|t*RD>MJnSURB^U_ba+mPhK8M05mJVn?r|+u@tWOMBCCvjx6+FX7L}{%ZKVEyVAO@NdKC zJQ&W;SMZH<#jm!{ZOyHk%};ZU@OxlqnBlX$%D`97S8i%_|~yv=l95;WcX};%aPCV+CxKx{H4e@9xn0dyyAuk`MZ&CV>}&7 zJdeT`9W8N=fd3nO!((FqK6tyFY4g*zk+j1bMJ@OazUgow%8?&nmM>jC?TwXr>HX;* z@D2A%{i>bA;F}K;{ygj)ZTM_?I~DmR?oUngcCYzr|A}zDT%>%jFn>LK;V-4Ub12tR z_&g>)mty~E$8(SYuOZ*Qx5R%6c0P%a?`0lLx$EwXa6J7DpRK$DkNhU5QK<)??+Nl#NKn}z zTJL)*e^)r3QSfc_w|A2N;CpN% z`N=y%%5{XfKeVr{;hSO-r?%&PRNgM+nwPe>k?=VOi2MmsJ;5Z$cYMw@1^H(0zH)~0 zMPkRU^V-+xj+ghDe;bj{Jw)PXnIpIbzVQPwFp!4w0DQ{~vEPERKf^blCi#pJ{|736 zzQ}7m_B4-iZM$keQw%p@e>?a#Z~bIH_`M(8eBmb|_;>R2 zb%g!R%mW&m&!9}~+x2w&+7-T$^|yV54i1FRnJ)6CdvveK@J0Q@c`k)-VqUHJtcMTi zXm=wI=fk(YFZOl5v=}}Qzq2UUa`>EAL|*H6mE#F61Ku-ywtDH-pX-tNxtVUc*EaCM zEJ=*UznkH+**O^b+=s*Q6v7vlNPhG_s3gM9EaY3pNn&&yvEO{_#vMNDs{O!a@VOU< z^R^_y{v*g26iT_&&hu)A_1HP&VGVrCB@&Oye`EM;dDzk1sMYvJm;H$hTf5fovge zf;z)z%iF~&zqj~JVCPQwmQBRYPSn?9@I|+W+w;rt?d0bq?0l>|1H#F~vys^%VDpgk zOt`-KD(}rV2E(^qAo09Fo)3d>T^P=1A$-%z!s|RdX83G*s8RbvL|*%`^WhuIrMw%H zhntn(GVJ#O_?#&c;4hJ1Vfbu*Uv}~&D+4}K`)7-taU09YjkcG(wI!v#K1HV=eCt)= zeC`2XNITSVd<1;M5V3D=!`*9=;j_hAfqYR>xV)#Uo#({Po|N|r_~wm;{}%hV!?!*b zw(~H2%LgL=I^}&1zUYFme4E-~eS_&z@Ckh0TVm$~skUHKGf}kdEiYf}XurK3eESxX zPaR+Ohi~-O4JN9c_rm>BF?`~l@O3dy?a!9PJRxBP7sEF!3d`RN-?oDY{*inxg%8dZ z;w0=m1>btB5W4>My7KnHoO$hlozLJKjuS%j(8FxCvH8h;Jsi(=hR@a>_CP-8ZOJET z4eTd&v&kQceBO0o`<3u5tRrcA{)O8Am&9qN&F0Ah^l!+5A2j!9Rwb z;Md`JUV(3AJ!cSkYlqLz{>|6XP*lFeJ&P$5Q1I)eW2?e>AVd95JM-QgSlCWdv~ z9BFvhpR>Pp3G!16pRHUovC}q9>>q^u8II>511?9tfPVfm&iy7r{^1DzdF%w<`t!T+ zMb(m@LD>HBJ8&iK5?M%JO&F+F?_aiC6I5o8&}M0F75mrc((z-@c*(W&Fi{c z$$yi#E_%1&Z9TUBC)}PNfzN$3oQGEUqPfCr|MO4yoKvJ8M-k7LD*sg2{wBMK-@H00 z?`MI$c81R(pXN5ly@sg#D`7jM;e)Z^IFE%-+$Z_@Lezpv$73`D8jvsW=6lz`=PnG_ zS2KJo1+B)`3bkW5sF>F#0>P{BEh8kJtC0TyK6ktb>Nnqf1>a~lqL`P?8@Jh2^42&( zcvh^}_-yU@G2|O|75k51=LPs2=B39G)B6#2zESy2!~IF$fs(h}>$^D%+HQA+Z^mX6Y!U#LRMLw4f;0&h~1W&-X zZ7O!A6X##yTfO%1vEg&k&4h1}Py8jEhd#SWewt1Y`%hqh5Pb7(LTEka!{=Qu@|9vP z7;E@!`8fvp2Cx4-6~4ve8{k_H3dec1<1Pg1hN3FU&6QCAiS2h3BG-`l=mLwpN7xPm3kx{!Rzpif0FpM|5*!P!1D!N$Jor= z#@ajt^kZfj!M%2Y&-+~bmXkNT-OG(TZDIRI!M8jRjz6Y66YyuTKL@^fLAV|-FnqQ= zUx$3bAEmsyzi}yiu!+QD9{al2)9^)~3a|D1dW8KCRQ}v>xq6$82e!OLUxfL+;9FRz z{-~$;Jrut11IdH7&!Y{Wtz4%e-~RKk{qvRoTSc+_j}4diG5BU~ zTy&oQGJIaS1U?Kq?S{`5XZOL9huoiu;7`PTFu?HHcLU`Yi37&d4Z@WHx9i0!KGf3<|zKK-)q6qtU8QxxBOT7ELrB43S?^@~iv?)G}{31_& zh2d=;n%zt~U0H!xlwM&1?~K3jXZ*YO?q;ru>A{weGi+7H5*m-YiM!54Y= zO&=-$fW)cw^$mQ2FC19tteNj7$&>u#dh?1M;F~^^g62>!dGI;gN<;McjM`=B+kFk9_%kCTE5l0eh-FkU_VbO@f-=C zyRXDK0z0R`w~)vqkWauD^bx;0U%dc6=VjqPME(Z&wtTVQAN%*KeV#{XzxoV(<27QZ z4m*E`Z{14xUXrHZbNE8`?VpJJrb8tU!Q$b z27HtEJmQz|dHIt6MYNNf;0vAz$McBcbFi5Se?q?9tFPDLgU`cuzJPE1Zfj5RpU{m4 zXT#?`D|X&M{#vz92cZ4io$!s-;r72AzG-r}9-o6RcwFp%gZ(!RZ|8f>-u=|4$hZGm zp>Lx7gntzdwi1Wj?9vnf8~w zZG9E}CT!;x_@)~r{#&uL6h3j3$o~`m8N+95Z~s8Pp}SnaT3-9h>DF%{>pX8_XRBe^ z?F@|I4=}v7)8fq&Ccqco7H+p?Dj%14Dy5o(dGNV3Ouc?DRQdhI&Q`>EHGHB%?C3mp ziQ35%`ClNvT=^@-p|-;p44*A;YmjgGMC@qZzJ+gVk$P#u{uX9}=jze>T;ZPZ&HW@! z?QaX<1Mbr=#Ljg1+^5C9{0)L)!{;KC3H8VqFyEWb$@A6z$I?#T75Bka@U7lDs{LkE z8-H7m@OAefe931u-~!ptrf!W zf&F6m#>d0*b0h3uihK+EUCs6$_qqwbs9ws|41XVdA?qH4;GcmHy!~~r!M8Dg`xg06 z;aeV-n$>mS&CP=+m;XBPdlvFL!WYmU^nUOF_@aBIeziT@Z+f+M8i$7K@i_Q~<>CC) zz_<05c-rv$3;4WQ63;^TuMB3pUamzx=i{*b->Us*!u-?l&E7i68-~wj{{!R;_<-Yo zi08j*XETXYuhahK2G-^whv)LUAip=NLX) z{EZR(ZP-aX67HAohc6(0?e|u|H}han>;13rc}-G}Z&BX&m9G!yf73&`zFrLTJHrQj zUxAsvy4NtnXDio8O4R1((9NQtvIqIUBwxCh?z(oeSU#rU;?=zYad}S~&h@!)L3< z709<&iv6X8`&al}Zy)1(@Xd^~(}>gllDjQ$;aOq7n~liscc%z`cmzMu@Ya4C^8tpj z;CT4l=CI!cd^4Z3C?n1W_`Daw{p98F1xc}^_m8*3Cq5Kj+wJ3O|9Pp$eFcKQ89rN{ z-;ChDR6D;B1IG~OriW#>vyztcn=TZ2=SP%+KL-&I`APoC3*1>wRH%FYLs%oD48GF>>&ds5+A# z@6%q~x57IwwehlwqzTqxtSoN6slDEwjR}#gDV-G*mzBjzD~prKSZPhQDQ0zDGDs#W zW2N!fl=|wrimLe7xux+$o&6Sj^QH7L^KJ3973M4A#l(#I>QZ|u8O)4V$7?G}WAoh? zzsE|e%7WtBnf4pxW69Fu>eRt3cO=^O+vkg`%WQ~7JyxEK*IC6O70KG-AqU{1xUR;0 zhkdM~y0kWKzpGx(9_Tb?7uU{;&r9p%qhqZlW~BLH!K^t$_Rl}y;QXMnqNKLCc3vtg z@!DFGkh=2PnmH&}mDE?xGAT+_&Wp{jNF?HAoJhu#_RIW@D(`H{X2ficYU=C4LNODU zmZP+?K3Ny9jj2}Z)Pd*}*VY!NBXURP3;*Nsptd+Rqo#IFajmH%(-woO`pUYB@|t8_ z%v7wYhm!ibcwl~`pfYYsw}1Xn(_AF8V#BpxQwv&K!d953XNzBKWbH2(Nd1_@_}oNA zZQMnkFwNc8Kyj?9W_CO_-_%U%ptPp4GVW?!tHOv?RF}o)#!AcW?*~{5NjYaKq}VjP zN=s2#vSA|7yqGGF%`{b?tgJED7|DuPCFM}T!gvmC!rn^qFV`!x18J(H!P3>iAidtT2pu zA5t4Lzl~8)aKxkuqsPRCraHG|S;Y+3PjPW1X1M;?wBA%y>dOS;y4E0(H1%6-Te0zG z#w1&HrVE!o#&~zXAd*s#)fUzyI$Db*YU491=5{<`F4k;hX>CdB0+wFewf~fxQML<< z#dJJKb%v&`R#wc9yJ4o<{Im%jMM+Ifr5OaP&F`bsnGBiaRL{)p3{!@Z9fO%~euu?FsHb#wA@`a?xJD@B5jp5#ThA&C8t;0@y^zT85wKLsAezl zct*Nnnm|pw{q+{p!^g|AMOC0W!Ui?k{JxI! z>gO|E${D6Tlpj8t(v;gtN6EYyrggZ9Nk)lED=X~p=A<>t4>uQ%1m$`Hm)P`7!j6M( zIJTXh8@N+Bb7iQCSCyEe7T4F62d*fo(q~S(QtqV1yJ*WwQoX11iT7ldi@wSvp;~KR zhp?1Qlh|sLx1%yzxi%RtxtZ~jd6k)0fu46mY_imp%S=a-wN-97uQXH2Vl$KlnU!j; zqYCr;L-AO$La&JVsV*;Fg{Jz-Yg~P5O=_FBgOZ&nWYn!jQ8F`WM~I9{Fv1*`RMeH$ znE7nD*HX=l@|pg-*0ePnikSk(Q*+;Rn#{yJfmPZg<8)fY^!G_q&l%0bL{ccEX*LrM z50b@+id4ha0VtzZ?QAb~B@WFWhC`hq2PMTxGbxt)iln)9;Cy06RlL^B!0g;a@}RQ_ zGjTtyK3<=m%-So}M3p6v$qF-ugsy0tbjCGfAYIO6jkzmI9cngBuFa5>niSeG(e{5a z+YYLV>&j!*^{%m-sjWE-cdPT=h?+8KFI&@M)5m2cQ|)lV)$cC%h)R%T#^=V(blv=r zS4~;Cr_t-tj7hb{_NK^8>g(*aQ zP0Pp35f_Yol2gqb!rXS2nLf{STOwm?zuL^Q?Y&jPr88Nbs5P}UBW+k6 z8c|aZ(vU0dB-0G+rcdlBXyz$)@@DT0JD#)+Bz+CpA3Za73$+yqTWiLsxxzE2DzOqb z*@(HTM{X@lZ(pCV7raDL9iL+gWLj5s+*mRlp&9lwy9beVw^Zg{)Ldt!4A8dQjMV8))CXL0*ZC#xyxb2qa)SAhX405LGtbb_=b*B5TV)C47 zm!{e?3#I|O2?&wcxmdio+LTfHv6#I<?o4F9pBrJ8GW-C8q25DzxDf9Mbz>H_fM6tbqw5m0d z4!yCSaMO~McuX$3j`}h#ruo=>GuE4HX^Oqs9X-!L#$43)sfD&%ET!eT>%b1SE@HVh zj7-KX#r*7^ojhj_tTch}Tpx=m%eG_XW<#Cr?GJdPPJogDtY24VXx;Dr^@rCl}g2SScP(hH=U~K4a(}XOj;7@p0a~w z(_~H0t!<{b3@8#lq{ZM#&?9JCO)~_6sM+}=MKR3+S)nTn^#u>*>h8AhmM$CJ2g{{((+lc z8O0Tq=^`DN4mOsYU>)SIO- zd)nULb?EaGdiA8LfWgfTN%ggr+)rd)m|97$6HMjP-gC+tNy?N8lJay}^@Lpl`VTwO3z~*n^T!nN@HXi3#utIv2d=` zs;112404+;6&~(MlT+e$4KwB4EqGZKSx-tk$rzDkpmjaBEtI)MH)DXT^tm;iY%Rj9 zOtPMxa+U63{UJ$4WjmUica_Cv)it~Z&)g_us$=TIG-VTwTW+3FR-EO|+1$s+OexGd zyeztC_ZB-~DD5iqIyNmKW+Kd}Qrn574>isHl)Q4PxvWbW7+lMDYvmmk%d1Sdb_g>! z@C+<;VQ$_o4b`+5GqcmVhrdo_Vl<6~*@*hIRg$_fi&UMdd8ZDy#=uA^!CY9+Jb8g(`qZKY^!n0 zz(&UovC`Vew5?{lVA!#q?c~e0&QwS;EivcSFUztsZ44~T|rYF%l`Xo&mwm&sfRDN*~v2@H|_0U#@w-0xd}$e zJU2F^1SI)#f080&E?xGtVKb|^x^yZ7A;PZ4T!tmODZY&tJvSdrY= zS&IhaZi~~iDHB(RaXOU`Essl!d01NQEpAxl$VE<7wtYtU9!$?<9|zpJS1J9z%phI) z?Uij8Dcu_W6nnX<9Uie|T;O^pS??CuNl|*4ln1W%Ez@QHrJZ^ zTDL-%t^u`fZv;BBm3C<+P3zO1G^6TeCCT&}Q+hGi71@Oy0JcN`ZFD zZhlo$b)>lmNre@woZu$csV+b&K*zJR-2ADs8mh)LLvGz~MvYkwsw+3o&CCuj;fa6hflbQp-tJx6K|lrD_vF(;WOpZU1L zF1Ke~(;dcr^^odf)1}l?vfrU(9_z-MDLWF4vWxktyFi@<)Rxwkn)#Ti#yayLJv<1f zf>rBD>7F!e^3X1|B$5($PwD-+xVtZ^DxMXW^#u}dT2<;!&cq;Bty@=e6EV5#>R8N5 z(_g3A=*2|aGR@ql%8bqC$#A`W6m2>L>G&e1x~|_y6@|gq#9{WLn075q-7P80_-4DC ztTXe_z-iJy+3X!a-Od>7e|t%#JZX-l2F;CXdZ&-sYm*X-v7BwoVdoCsvQ%2G{8-y* zNxZ3EBd~LFx8_lkaRI7peSniPq9?-rGS4IB_E;KDM7xQ3mxv8sR$o#s+*0T0dE@Lu zEqCYVmIh*FHD<+QMqawyQV}MPQR@`$*|i(6e5IsW9y6=SS#K*%_1XDn=*i#yW+}k5 znDY9%vYI(|ozOLz)TLBfS!1U7=>n8bFpIUL^>K76)vRjA0ooF)CpeAnYxMTaf8cqKwI#-|d+$ zbVfzSgf~`eCdQ1sp<4i+Nto8`wxlIPH|?n?!o7~2?xdbyCf&}YtXH&7UP+UV1*v|s zlu3-;CYByyrIT_GX>|seQE3^s_pY~eJH|3@Lqok!`B?XK#J`)ZuD8pAt|PJCFweir z&1R0&<2DT~G@{EklXRpq7jTmY%$20loV{t=`!F74XOzjd0>ZHm^J8+GDvWwd?}BqH z>~6(?yt+ADdd8Y+Cb6Vj8A|OV(1(|%u65BYT}%cwzr*C;B-nJo z;VOy!-xjpprV2YTm-X6oO3RNh^9j2HFw1n)X(uM!)HKu% zm&LW~zIo4DRjxT^WQGAD?WugSZF{L>t;`O+$9%f_lw@qaY|Y6!ws0lcb9UIxG6_^i zc6pViZvv4DN=+B#RS0vh;?*$AJ=UPtO=OrfcZ;)W zFcGutO4IPnGKKDAOh1IMhRtNec2#!or>z1mZjR@YdR0VHv}wj0dKvy9*|r z(2h*4eaxM?3Q=hqp9>W?EI_+bn}r%TK6esufph`jE_tD1&RpT0q7#LMF44o6LpL%r!~fI+v~*^_4MUNS`!!`SRG$tF+ks zQJISYYQsEI)dfP^VC;&p9kL`Ox0ak*`pl@3cuBo(hm*aa@l>=&0>|%HK9RZJ2W$y%=n61MOvA7wyxsK9j z?5xYb@7Auujdi78bl!+wik%uMuloFq@a;#xp%hJTQ-N%53Az7ILaXOZj)*qw`Pe zpjEkQvFjfuHTE{oJT^-2|FoBhQ*?jQ7mHhPGj(GgMVXH+==8*FcGiUv`z%BJ_&XXj z;dX6bHW!PWUf^yLn{i#b_Gd;9Q*EYkg>DKcWX1!3&Dnf&Uy=EsTQ;zV=BKu4O);~* zjt{t8mz!>bYBOn)WCr2ljD{NvJ>k*o)II)m`{&bbA?co#s)QuWb_TBQI{M;~WBP=> zwmL>?XOiaLE9FK*HHOn9&vVm+N@W>GwI(wrnsz~mp1|FPWK@lrP9_pmLF(bOE7{@%>Wv_!Hd?PZUG$R}k&D{QXk&3ycA z$LJ15?bF(hEYBg#0*p2&T>+;F=x=O7f+I4N!NNszxr&HS>VkPE7GiJ|h+NbSw zT(aD#$=+(2`rCb@9U_)ZhNRXsXWi)DLD1M`XwJ}7RI(XTWmXUs#Evr3$3ASyOqjKK|_-`HC-NlkfgNiVU~8 zHS6e5on~>)%n8z$tXnnk@1fO0dO-%;T9)+sMCQ#IcNNIG`$v}nd(GO7NUpOn^YBE4 z`N&$rPz%A(!pnIb3a?l~TaxMwizi5fgRMLI$NorDNcA-Y(vLR>f!;I{v z&0d%EenMH;HIosMOiu6Yy<%y!{ z#Vb;I;sIG^b2ppByv2OYoL&Ai55(P4L`}jx=&*yG+Z(6(F(a;AxJqPPPdb2@_Mfgb zHErjh(j}Sb?4@bf@N8LA`EU!d`Z-l|#?I{J$)>xQ43iqbY@gcEg-q(G_+qyi)?CnO zIr|jD%=*%V#ul|>mj#OOY>I-r#}Y}l&8I_^f@MC3wR`&Pw8%ta#WL0vY_U29<$LwzQ zpemVtE@D4^gR1TS%tS;Nw8Wmf8FHge`uR%cY}OR6!vcG>X2zp)cjJ~$qi!*b*z^?I z$;;*9J|Zp8{_30C(4?BbM;2eWag(AxznL)wV7t=U5`u`&9hJ2fK9V! z;+U=~yL^=8?lHA~pT6TBdLY$eI~RWknQ5-}X_YpZ)Jjr{aq|avLEG!pL}M=9GQBHG zZOkywxaPXqquVeTeyAvwnC`RutAO3L?iMSZ8_9qPna742p>#LWEqIe$K9HEsQF5~T zG+f37Z_1zg6pVIU_J&cOfg}@L&JuFQr=m<#$k;}fj#6t-HUye^c-py16``j%m3CV& zQ>@+Zqbse)4s5_8doK8Bq3Vg^w6x#jv~WjyYE;M6o^w}aaC&FKPYsX|KDJn o()Po>PmG7S@)%ll -// +---------------------------------------------------------------------- - -// 测试入口文件 -$_SERVER['REQUEST_METHOD'] = 'GET'; -// 定义项目测试基础路径 -define('TEST_PATH', __DIR__ . '/'); -// 定义项目路径 -define('APP_PATH', __DIR__ . '/application/'); -// 加载框架基础文件 -require __DIR__ . '/../base.php'; -\think\Loader::addNamespace('tests', TEST_PATH); diff --git a/tests/script/install.sh b/tests/script/install.sh deleted file mode 100755 index 0adee7ed..00000000 --- a/tests/script/install.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -if [ $(phpenv version-name) != "hhvm" ]; then - cp tests/extensions/$(phpenv version-name)/*.so $(php-config --extension-dir) - - if [ $(phpenv version-name) = "7.0" ]; then - phpenv config-add tests/conf/apcu_bc.ini - else - phpenv config-add tests/conf/apcu.ini - fi - - phpenv config-add tests/conf/memcached.ini - phpenv config-add tests/conf/redis.ini - - phpenv config-add tests/conf/timezone.ini -fi - -composer install --no-interaction --ignore-platform-reqs diff --git a/tests/thinkphp/baseTest.php b/tests/thinkphp/baseTest.php deleted file mode 100644 index 18f1d3dd..00000000 --- a/tests/thinkphp/baseTest.php +++ /dev/null @@ -1,39 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * 保证运行环境正常 - */ -class baseTest extends \PHPUnit_Framework_TestCase -{ - public function testConstants() - { - $this->assertNotEmpty(THINK_START_TIME); - $this->assertNotEmpty(THINK_START_MEM); - $this->assertNotEmpty(THINK_VERSION); - $this->assertNotEmpty(DS); - $this->assertNotEmpty(THINK_PATH); - $this->assertNotEmpty(LIB_PATH); - $this->assertNotEmpty(EXTEND_PATH); - $this->assertNotEmpty(CORE_PATH); - $this->assertNotEmpty(TRAIT_PATH); - $this->assertNotEmpty(APP_PATH); - $this->assertNotEmpty(RUNTIME_PATH); - $this->assertNotEmpty(LOG_PATH); - $this->assertNotEmpty(CACHE_PATH); - $this->assertNotEmpty(TEMP_PATH); - $this->assertNotEmpty(VENDOR_PATH); - $this->assertNotEmpty(EXT); - $this->assertNotEmpty(ENV_PREFIX); - $this->assertTrue(!is_null(IS_WIN)); - $this->assertTrue(!is_null(IS_CLI)); - } -} diff --git a/tests/thinkphp/library/think/appTest.php b/tests/thinkphp/library/think/appTest.php deleted file mode 100644 index 366b4f55..00000000 --- a/tests/thinkphp/library/think/appTest.php +++ /dev/null @@ -1,91 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * app类测试 - * @author Haotong Lin - */ - -namespace tests\thinkphp\library\think; - -use ReflectionClass; -use think\App; -use think\Config; -use think\Request; - -function func_trim($value) -{ - return trim($value); -} - -function func_strpos($haystack, $needle) -{ - return strpos($haystack, $needle); -} - -class AppInvokeMethodTestClass -{ - public static function staticRun($string) - { - return $string; - } - - public function run($string) - { - return $string; - } -} - -class appTest extends \PHPUnit_Framework_TestCase -{ - public function testRun() - { - $response = App::run(Request::create("http://www.example.com")); - - $expectOutputString = '

'; - - $this->assertEquals($expectOutputString, $response->getContent()); - $this->assertEquals(200, $response->getCode()); - - $this->assertEquals(true, function_exists('lang')); - $this->assertEquals(true, function_exists('config')); - $this->assertEquals(true, function_exists('input')); - - $this->assertEquals(Config::get('default_timezone'), date_default_timezone_get()); - - } - - // function调度 - public function testInvokeFunction() - { - $args1 = ['a b c ']; - $this->assertEquals( - trim($args1[0]), - App::invokeFunction('tests\thinkphp\library\think\func_trim', $args1) - ); - - $args2 = ['abcdefg', 'g']; - $this->assertEquals( - strpos($args2[0], $args2[1]), - App::invokeFunction('tests\thinkphp\library\think\func_strpos', $args2) - ); - } - - // 类method调度 - public function testInvokeMethod() - { - $result = App::invokeMethod(['tests\thinkphp\library\think\AppInvokeMethodTestClass', 'run'], ['thinkphp']); - $this->assertEquals('thinkphp', $result); - - $result = App::invokeMethod('tests\thinkphp\library\think\AppInvokeMethodTestClass::staticRun', ['thinkphp']); - $this->assertEquals('thinkphp', $result); - } -} diff --git a/tests/thinkphp/library/think/behavior/One.php b/tests/thinkphp/library/think/behavior/One.php deleted file mode 100644 index ae8fa49d..00000000 --- a/tests/thinkphp/library/think/behavior/One.php +++ /dev/null @@ -1,15 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * build测试 - * @author 刘志淳 - */ - -namespace tests\thinkphp\library\think; - -use think\Build; - -class buildTest extends \PHPUnit_Framework_TestCase -{ - public function testRun() - { - $build = [ - // Test run directory - '__dir__' => ['runtime/cache', 'runtime/log', 'runtime/temp', 'runtime/template'], - '__file__' => ['common.php'], - - // Test generation module - 'demo' => [ - '__file__' => ['common.php'], - '__dir__' => ['behavior', 'controller', 'model', 'view', 'service'], - 'controller' => ['Index', 'Test', 'UserType'], - 'model' => ['User', 'UserType'], - 'service' => ['User', 'UserType'], - 'view' => ['index/index'], - ], - ]; - Build::run($build); - - $this->buildFileExists($build); - } - - protected function buildFileExists($build) - { - foreach ($build as $module => $list) { - if ('__dir__' == $module || '__file__' == $module) { - foreach ($list as $file) { - $this->assertFileExists(APP_PATH . $file); - } - } else { - foreach ($list as $path => $moduleList) { - if ('__file__' == $path || '__dir__' == $path) { - foreach ($moduleList as $file) { - $this->assertFileExists(APP_PATH . $module . '/' . $file); - } - } else { - foreach ($moduleList as $file) { - if ('view' == $path) { - $file_name = APP_PATH . $module . '/' . $path . '/' . $file . '.html'; - } else { - $file_name = APP_PATH . $module . '/' . $path . '/' . $file . EXT; - } - $this->assertFileExists($file_name); - } - } - } - $this->assertFileExists(APP_PATH . ($module ? $module . DS : '') . 'config.php'); - } - } - } -} diff --git a/tests/thinkphp/library/think/cache/driver/cacheTestCase.php b/tests/thinkphp/library/think/cache/driver/cacheTestCase.php deleted file mode 100644 index ec8f9b6b..00000000 --- a/tests/thinkphp/library/think/cache/driver/cacheTestCase.php +++ /dev/null @@ -1,207 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * 缓存抽象类,提供一些测试 - * @author simon - */ - -namespace tests\thinkphp\library\think\cache\driver; - -use think\Cache; - -abstract class cacheTestCase extends \PHPUnit_Framework_TestCase -{ - - /** - * 获取缓存句柄,子类必须有 - * @access protected - */ - abstract protected function getCacheInstance(); - - /** - * tearDown函数 - */ - protected function tearDown() - { - } - - /** - * 设定一组测试值,包括测试字符串、整数、数组和对象 - * @return mixed - * @access public - */ - public function prepare() - { - $cache = $this->getCacheInstance(); - $cache->clear(); - $cache->set('string_test', 'string_test'); - $cache->set('number_test', 11); - $cache->set('array_test', ['array_test' => 'array_test']); - return $cache; - } - - /** - * 测试缓存设置,包括测试字符串、整数、数组和对象 - * @return mixed - * @access public - */ - public function testSet() - { - $cache = $this->getCacheInstance(); - $this->assertTrue($cache->set('string_test', 'string_test')); - $this->assertTrue($cache->set('number_test', 11)); - $this->assertTrue($cache->set('array_test', ['array_test' => 'array_test'])); - } - - /** - * 测试缓存自增 - * @return mixed - * @access public - */ - public function testInc() - { - $cache = $this->getCacheInstance(); - $this->assertEquals(14, $cache->inc('number_test', 3)); - } - - /** - * 测试缓存自减 - * @return mixed - * @access public - */ - public function testDec() - { - $cache = $this->getCacheInstance(); - $this->assertEquals(8, $cache->dec('number_test', 6)); - } - - /** - * 测试缓存读取,包括测试字符串、整数、数组和对象 - * @return mixed - * @access public - */ - public function testGet() - { - $cache = $this->prepare(); - $this->assertEquals('string_test', $cache->get('string_test')); - $this->assertEquals(11, $cache->get('number_test')); - $array = $cache->get('array_test'); - $this->assertArrayHasKey('array_test', $array); - $this->assertEquals('array_test', $array['array_test']); - - $result = $cache->set('no_expire', 1, 0); - $this->assertTrue($result); - } - - /** - * 测试缓存存在情况,包括测试字符串、整数、数组和对象 - * @return mixed - * @access public - */ - public function testExists() - { - $cache = $this->prepare(); - $this->assertNotEmpty($cache->has('string_test')); - $this->assertNotEmpty($cache->has('number_test')); - $this->assertFalse($cache->has('not_exists')); - } - - /** - * 测试缓存不存在情况,包括测试字符串、整数、数组和对象 - * @return mixed - * @access public - */ - public function testGetNonExistent() - { - $cache = $this->getCacheInstance(); - $this->assertFalse($cache->get('non_existent_key', false)); - } - - /** - * 测试特殊值缓存,包括测试字符串、整数、数组和对象 - * @return mixed - * @access public - */ - public function testStoreSpecialValues() - { - $cache = $this->getCacheInstance(); - $cache->set('null_value', null); - //清空缓存后,返回null而不是false - $this->assertTrue(is_null($cache->get('null_value'))); - } - - /** - * 缓存过期测试 - * @return mixed - * @access public - */ - public function testExpire() - { - $cache = $this->getCacheInstance(); - $this->assertTrue($cache->set('expire_test', 'expire_test', 1)); - usleep(600000); - $this->assertEquals('expire_test', $cache->get('expire_test')); - usleep(800000); - $this->assertFalse($cache->get('expire_test')); - } - - /** - * 删除缓存测试 - * @return mixed - * @access public - */ - public function testDelete() - { - $cache = $this->prepare(); - $this->assertNotNull($cache->rm('number_test')); - $this->assertFalse($cache->get('number_test')); - } - - /** - * 获取并删除缓存测试 - * @return mixed - * @access public - */ - public function testPull() - { - $cache = $this->prepare(); - $this->assertEquals(11, $cache->pull('number_test')); - $this->assertFalse($cache->get('number_test')); - } - - /** - * 清空缓存测试 - * @return mixed - * @access public - */ - public function testClear() - { - $cache = $this->prepare(); - $this->assertTrue($cache->clear()); - $this->assertFalse($cache->get('number_test')); - } - - public function testStaticCall() - { - $this->assertTrue(Cache::set('a', 1)); - $this->assertEquals(1, Cache::get('a')); - $this->assertEquals(2, Cache::inc('a')); - $this->assertEquals(4, Cache::inc('a', 2)); - $this->assertEquals(4, Cache::get('a')); - $this->assertEquals(3, Cache::dec('a')); - $this->assertEquals(1, Cache::dec('a', 2)); - $this->assertEquals(1, Cache::get('a')); - $this->assertNotNull(Cache::rm('a')); - $this->assertNotNull(Cache::clear()); - } - -} diff --git a/tests/thinkphp/library/think/cache/driver/fileTest.php b/tests/thinkphp/library/think/cache/driver/fileTest.php deleted file mode 100644 index aee0bd46..00000000 --- a/tests/thinkphp/library/think/cache/driver/fileTest.php +++ /dev/null @@ -1,46 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * File缓存驱动测试 - * @author 刘志淳 - */ - -namespace tests\thinkphp\library\think\cache\driver; - -class fileTest extends cacheTestCase -{ - private $_cacheInstance = null; - - /** - * 基境缓存类型 - */ - protected function setUp() - { - \think\Cache::connect(['type' => 'File', 'path' => CACHE_PATH]); - } - - /** - * @return FileCache - */ - protected function getCacheInstance() - { - if (null === $this->_cacheInstance) { - $this->_cacheInstance = new \think\cache\driver\File(); - } - return $this->_cacheInstance; - } - - // skip testExpire - public function testExpire() - { - } -} diff --git a/tests/thinkphp/library/think/cache/driver/liteTest.php b/tests/thinkphp/library/think/cache/driver/liteTest.php deleted file mode 100644 index b52bdff9..00000000 --- a/tests/thinkphp/library/think/cache/driver/liteTest.php +++ /dev/null @@ -1,69 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Lite缓存驱动测试 - * @author 刘志淳 - */ - -namespace tests\thinkphp\library\think\cache\driver; - -use think\Cache; - -class liteTest extends \PHPUnit_Framework_TestCase -{ - protected function getCacheInstance() - { - return Cache::connect(['type' => 'Lite', 'path' => CACHE_PATH]); - } - - /** - * 测试缓存读取 - * @return mixed - * @access public - */ - public function testGet() - { - $cache = $this->getCacheInstance(); - $this->assertFalse($cache->get('test')); - } - - /** - * 测试缓存设置 - * @return mixed - * @access public - */ - public function testSet() - { - $cache = $this->getCacheInstance(); - $this->assertNotEmpty($cache->set('test', 'test')); - } - - /** - * 删除缓存测试 - * @return mixed - * @access public - */ - public function testRm() - { - $cache = $this->getCacheInstance(); - $this->assertTrue($cache->rm('test')); - } - - /** - * 清空缓存测试 - * @return mixed - * @access public - */ - public function testClear() - { - } -} diff --git a/tests/thinkphp/library/think/cache/driver/memcacheTest.php b/tests/thinkphp/library/think/cache/driver/memcacheTest.php deleted file mode 100644 index 15dfaa5a..00000000 --- a/tests/thinkphp/library/think/cache/driver/memcacheTest.php +++ /dev/null @@ -1,49 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Memcache缓存驱动测试 - * @author 刘志淳 - */ - -namespace tests\thinkphp\library\think\cache\driver; - -class memcacheTest extends cacheTestCase -{ - private $_cacheInstance = null; - - /** - * 基境缓存类型 - */ - protected function setUp() - { - if (!extension_loaded('memcache')) { - $this->markTestSkipped("Memcache没有安装,已跳过测试!"); - } - \think\Cache::connect(['type' => 'memcache', 'expire' => 2]); - } - - /** - * @return ApcCache - */ - protected function getCacheInstance() - { - if (null === $this->_cacheInstance) { - $this->_cacheInstance = new \think\cache\driver\Memcache(['length' => 3]); - } - return $this->_cacheInstance; - } - - // skip testExpire - public function testExpire() - { - } -} diff --git a/tests/thinkphp/library/think/cache/driver/memcachedTest.php b/tests/thinkphp/library/think/cache/driver/memcachedTest.php deleted file mode 100644 index 58bcbd88..00000000 --- a/tests/thinkphp/library/think/cache/driver/memcachedTest.php +++ /dev/null @@ -1,72 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Memcached缓存驱动测试 - * @author 7IN0SAN9 - */ - -namespace tests\thinkphp\library\think\cache\driver; - -class memcachedTest extends cacheTestCase -{ - private $_cacheInstance = null; - /** - * 基境缓存类型 - */ - protected function setUp() - { - if (!extension_loaded("memcached") && !extension_loaded('memcache')) { - $this->markTestSkipped("Memcached或Memcache没有安装,已跳过测试!"); - } - \think\Cache::connect(array('type' => 'memcached', 'expire' => 2)); - } - /** - * @return ApcCache - */ - protected function getCacheInstance() - { - if (null === $this->_cacheInstance) { - $this->_cacheInstance = new \think\cache\driver\Memcached(['length' => 3]); - } - return $this->_cacheInstance; - } - /** - * 缓存过期测试《提出来测试,因为目前看通不过缓存过期测试,所以还需研究》 - * @return mixed - * @access public - */ - public function testExpire() - { - } - - public function testStaticCall() - { - } - - /** - * 测试缓存自增 - * @return mixed - * @access public - */ - public function testInc() - { - } - - /** - * 测试缓存自减 - * @return mixed - * @access public - */ - public function testDec() - { - } -} diff --git a/tests/thinkphp/library/think/cache/driver/redisTest.php b/tests/thinkphp/library/think/cache/driver/redisTest.php deleted file mode 100644 index 839e5165..00000000 --- a/tests/thinkphp/library/think/cache/driver/redisTest.php +++ /dev/null @@ -1,66 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Redis缓存驱动测试 - * @author 7IN0SAN9 - */ - -namespace tests\thinkphp\library\think\cache\driver; - -class redisTest extends cacheTestCase -{ - private $_cacheInstance = null; - - protected function setUp() - { - if (!extension_loaded("redis")) { - $this->markTestSkipped("Redis没有安装,已跳过测试!"); - } - \think\Cache::connect(array('type' => 'redis', 'expire' => 2)); - } - - protected function getCacheInstance() - { - if (null === $this->_cacheInstance) { - $this->_cacheInstance = new \think\cache\driver\Redis(['length' => 3]); - } - return $this->_cacheInstance; - } - - public function testGet() - { - $cache = $this->prepare(); - $this->assertEquals('string_test', $cache->get('string_test')); - $this->assertEquals(11, $cache->get('number_test')); - $result = $cache->get('array_test'); - $this->assertEquals('array_test', $result['array_test']); - } - - public function testStoreSpecialValues() - { - $redis = new \think\cache\driver\Redis(['length' => 3]); - $redis->set('key', 'value'); - $redis->get('key'); - - $redis->handler()->setnx('key', 'value'); - $value = $redis->handler()->get('key'); - $this->assertEquals('value', $value); - - $redis->handler()->hset('hash', 'key', 'value'); - $value = $redis->handler()->hget('hash', 'key'); - $this->assertEquals('value', $value); - } - - public function testExpire() - { - } -} diff --git a/tests/thinkphp/library/think/config/driver/fixtures/config.ini b/tests/thinkphp/library/think/config/driver/fixtures/config.ini deleted file mode 100644 index 2a17cc0d..00000000 --- a/tests/thinkphp/library/think/config/driver/fixtures/config.ini +++ /dev/null @@ -1 +0,0 @@ -inifile=1 \ No newline at end of file diff --git a/tests/thinkphp/library/think/config/driver/fixtures/config.json b/tests/thinkphp/library/think/config/driver/fixtures/config.json deleted file mode 100644 index 716ed064..00000000 --- a/tests/thinkphp/library/think/config/driver/fixtures/config.json +++ /dev/null @@ -1 +0,0 @@ -{"jsonstring":1} \ No newline at end of file diff --git a/tests/thinkphp/library/think/config/driver/fixtures/config.xml b/tests/thinkphp/library/think/config/driver/fixtures/config.xml deleted file mode 100644 index 3c3266a1..00000000 --- a/tests/thinkphp/library/think/config/driver/fixtures/config.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - 1 - - \ No newline at end of file diff --git a/tests/thinkphp/library/think/config/driver/iniTest.php b/tests/thinkphp/library/think/config/driver/iniTest.php deleted file mode 100644 index 2575157c..00000000 --- a/tests/thinkphp/library/think/config/driver/iniTest.php +++ /dev/null @@ -1,33 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Ini配置测试 - * @author 7IN0SAN9 - */ - -namespace tests\thinkphp\library\think\config\driver; - -use think\config; - -class iniTest extends \PHPUnit_Framework_TestCase -{ - public function testParse() - { - Config::parse('inistring=1', 'ini'); - $this->assertEquals(1, Config::get('inistring')); - Config::reset(); - Config::parse(__DIR__ . '/fixtures/config.ini'); - $this->assertTrue(Config::has('inifile')); - $this->assertEquals(1, Config::get('inifile')); - Config::reset(); - } -} diff --git a/tests/thinkphp/library/think/config/driver/jsonTest.php b/tests/thinkphp/library/think/config/driver/jsonTest.php deleted file mode 100644 index 9d438d40..00000000 --- a/tests/thinkphp/library/think/config/driver/jsonTest.php +++ /dev/null @@ -1,33 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Xml配置测试 - * @author 7IN0SAN9 - */ - -namespace tests\thinkphp\library\think\config\driver; - -use think\config; - -class jsonTest extends \PHPUnit_Framework_TestCase -{ - public function testParse() - { - Config::parse('{"jsonstring":1}', 'json'); - $this->assertEquals(1, Config::get('jsonstring')); - Config::reset(); - Config::parse(__DIR__ . '/fixtures/config.json'); - $this->assertTrue(Config::has('jsonstring')); - $this->assertEquals(1, Config::get('jsonstring')); - Config::reset(); - } -} diff --git a/tests/thinkphp/library/think/config/driver/xmlTest.php b/tests/thinkphp/library/think/config/driver/xmlTest.php deleted file mode 100644 index 9ab1d257..00000000 --- a/tests/thinkphp/library/think/config/driver/xmlTest.php +++ /dev/null @@ -1,33 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Xml配置测试 - * @author 7IN0SAN9 - */ - -namespace tests\thinkphp\library\think\config\driver; - -use think\config; - -class xmlTest extends \PHPUnit_Framework_TestCase -{ - public function testParse() - { - Config::parse('1', 'xml'); - $this->assertEquals(1, Config::get('xmlstring')); - Config::reset(); - Config::parse(__DIR__ . '/fixtures/config.xml'); - $this->assertTrue(Config::has('xmlfile.istrue')); - $this->assertEquals(1, Config::get('xmlfile.istrue')); - Config::reset(); - } -} diff --git a/tests/thinkphp/library/think/configTest.php b/tests/thinkphp/library/think/configTest.php deleted file mode 100644 index 166f0f8a..00000000 --- a/tests/thinkphp/library/think/configTest.php +++ /dev/null @@ -1,169 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * 配置测试 - * @author Haotong Lin - */ - -namespace tests\thinkphp\library\think; - -use ReflectionClass; -use think\Config; - -class configTest extends \PHPUnit_Framework_TestCase -{ - public function testRange() - { - $reflectedClass = new ReflectionClass('\think\Config'); - $reflectedPropertyRange = $reflectedClass->getProperty('range'); - $reflectedPropertyRange->setAccessible(true); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - // test default range - $this->assertEquals('_sys_', $reflectedPropertyRange->getValue()); - $config = $reflectedPropertyConfig->getValue(); - $this->assertTrue(is_array($config)); - // test range initialization - Config::range('_test_'); - $this->assertEquals('_test_', $reflectedPropertyRange->getValue()); - $config = $reflectedPropertyConfig->getValue(); - $this->assertEquals([], $config['_test_']); - } - - // public function testParse() - // { - // see \think\config\driver\...Test.php - // } - - public function testLoad() - { - $file = APP_PATH . 'config' . EXT; - $config = array_change_key_case(include $file); - $name = '_name_'; - $range = '_test_'; - - $reflectedClass = new ReflectionClass('\think\Config'); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - $reflectedPropertyConfig->setValue([]); - - $this->assertEquals($config, Config::load($file, $name, $range)); - $this->assertNotEquals(null, Config::load($file, $name, $range)); - } - - public function testHas() - { - $range = '_test_'; - $this->assertFalse(Config::has('abcd', $range)); - $reflectedClass = new ReflectionClass('\think\Config'); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - - // if (!strpos($name, '.')): - $reflectedPropertyConfig->setValue([ - $range => ['abcd' => 'value'], - ]); - $this->assertTrue(Config::has('abcd', $range)); - - // else ... - $this->assertFalse(Config::has('abcd.efg', $range)); - - $reflectedPropertyConfig->setValue([ - $range => ['abcd' => ['efg' => 'value']], - ]); - $this->assertTrue(Config::has('abcd.efg', $range)); - } - - public function testGet() - { - $range = '_test_'; - $reflectedClass = new ReflectionClass('\think\Config'); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - // test all configurations - $reflectedPropertyConfig->setValue([$range => []]); - $this->assertEquals([], Config::get(null, $range)); - $this->assertEquals(null, Config::get(null, 'does_not_exist')); - $value = 'value'; - // test getting configuration - $reflectedPropertyConfig->setValue([$range => ['abcd' => 'efg']]); - $this->assertEquals('efg', Config::get('abcd', $range)); - $this->assertEquals(null, Config::get('does_not_exist', $range)); - $this->assertEquals(null, Config::get('abcd', 'does_not_exist')); - // test getting configuration with dot syntax - $reflectedPropertyConfig->setValue([$range => [ - 'one' => ['two' => $value], - ]]); - $this->assertEquals($value, Config::get('one.two', $range)); - $this->assertEquals(null, Config::get('one.does_not_exist', $range)); - $this->assertEquals(null, Config::get('one.two', 'does_not_exist')); - } - - public function testSet() - { - $range = '_test_'; - $reflectedClass = new ReflectionClass('\think\Config'); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - $reflectedPropertyConfig->setValue([]); - // if (is_string($name)): - // without dot syntax - $name = 'name'; - $value = 'value'; - Config::set($name, $value, $range); - $config = $reflectedPropertyConfig->getValue(); - $this->assertEquals($value, $config[$range][$name]); - // with dot syntax - $name = 'one.two'; - $value = 'dot value'; - Config::set($name, $value, $range); - $config = $reflectedPropertyConfig->getValue(); - $this->assertEquals($value, $config[$range]['one']['two']); - // if (is_array($name)): - // see testLoad() - // ... - // test getting all configurations...? - // return self::$config[$range]; ?? - $value = ['all' => 'configuration']; - $reflectedPropertyConfig->setValue([$range => $value]); - $this->assertEquals($value, Config::set(null, null, $range)); - $this->assertNotEquals(null, Config::set(null, null, $range)); - } - - public function testReset() - { - $range = '_test_'; - $reflectedClass = new ReflectionClass('\think\Config'); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - $reflectedPropertyConfig->setValue([$range => ['abcd' => 'efg']]); - - // clear all configurations - Config::reset(true); - $config = $reflectedPropertyConfig->getValue(); - $this->assertEquals([], $config); - // clear the configuration in range of parameter. - $reflectedPropertyConfig->setValue([ - $range => [ - 'abcd' => 'efg', - 'hijk' => 'lmn', - ], - 'a' => 'b', - ]); - Config::reset($range); - $config = $reflectedPropertyConfig->getValue(); - $this->assertEquals([ - $range => [], - 'a' => 'b', - ], $config); - } -} diff --git a/tests/thinkphp/library/think/controller/.gitignore b/tests/thinkphp/library/think/controller/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/think/controller/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/think/controllerTest.php b/tests/thinkphp/library/think/controllerTest.php deleted file mode 100644 index ce5f49a0..00000000 --- a/tests/thinkphp/library/think/controllerTest.php +++ /dev/null @@ -1,194 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * 控制器测试 - * @author Haotong Lin - */ - -namespace tests\thinkphp\library\think; - -use ReflectionClass; -use think\Controller; -use think\Request; -use think\View; - -require_once CORE_PATH . '../../helper.php'; - -class Foo extends Controller -{ - public $test = 'test'; - - public function _initialize() - { - $this->test = 'abcd'; - } - - public function assignTest() - { - $this->assign('abcd', 'dcba'); - $this->assign(['key1' => 'value1', 'key2' => 'value2']); - } - - public function fetchTest() - { - $template = dirname(__FILE__) . '/display.html'; - return $this->fetch($template, ['name' => 'ThinkPHP']); - } - - public function displayTest() - { - $template = dirname(__FILE__) . '/display.html'; - return $this->display($template, ['name' => 'ThinkPHP']); - } - public function test() - { - $data = [ - 'username' => 'username', - 'nickname' => 'nickname', - 'password' => '123456', - 'repassword' => '123456', - 'email' => 'abc@abc.com', - 'sex' => '0', - 'age' => '20', - 'code' => '1234', - ]; - - $validate = [ - ['username', 'length:5,15', '用户名长度为5到15个字符'], - ['nickname', 'require', '请填昵称'], - ['password', '[\w-]{6,15}', '密码长度为6到15个字符'], - ['repassword', 'confirm:password', '两次密码不一到致'], - ['email', 'filter:validate_email', '邮箱格式错误'], - ['sex', 'in:0,1', '性别只能为为男或女'], - ['age', 'between:1,80', '年龄只能在10-80之间'], - ]; - return $this->validate($data, $validate); - } -} - -class Bar extends Controller -{ - public $test = 1; - - public $beforeActionList = ['action1', 'action2']; - - public function action1() - { - $this->test += 2; - return 'action1'; - } - - public function action2() - { - $this->test += 4; - return 'action2'; - } -} - -class Baz extends Controller -{ - public $test = 1; - - public $beforeActionList = [ - 'action1' => ['only' => 'index'], - 'action2' => ['except' => 'index'], - 'action3' => ['only' => 'abcd'], - 'action4' => ['except' => 'abcd'], - ]; - - public function action1() - { - $this->test += 2; - return 'action1'; - } - - public function action2() - { - $this->test += 4; - return 'action2'; - } - - public function action3() - { - $this->test += 8; - return 'action2'; - } - - public function action4() - { - $this->test += 16; - return 'action2'; - } -} - -class controllerTest extends \PHPUnit_Framework_TestCase -{ - public function testInitialize() - { - $foo = new Foo(Request::instance()); - $this->assertEquals('abcd', $foo->test); - } - - public function testBeforeAction() - { - $obj = new Bar(Request::instance()); - $this->assertEquals(7, $obj->test); - - $obj = new Baz(Request::instance()); - $this->assertEquals(19, $obj->test); - } - - private function getView($controller) - { - $view = new View(); - $rc = new ReflectionClass(get_class($controller)); - $property = $rc->getProperty('view'); - $property->setAccessible(true); - $property->setValue($controller, $view); - return $view; - } - - public function testFetch() - { - $controller = new Foo(Request::instance()); - $view = $this->getView($controller); - $template = dirname(__FILE__) . '/display.html'; - $viewFetch = $view->fetch($template, ['name' => 'ThinkPHP']); - $this->assertEquals($controller->fetchTest(), $viewFetch); - } - - public function testDisplay() - { - $controller = new Foo; - $view = $this->getView($controller); - $template = dirname(__FILE__) . '/display.html'; - $viewFetch = $view->display($template, ['name' => 'ThinkPHP']); - - $this->assertEquals($controller->displayTest(), $viewFetch); - } - - public function testAssign() - { - $controller = new Foo(Request::instance()); - $view = $this->getView($controller); - $controller->assignTest(); - $expect = ['abcd' => 'dcba', 'key1' => 'value1', 'key2' => 'value2']; - $this->assertAttributeEquals($expect, 'data', $view); - } - - public function testValidate() - { - $controller = new Foo(Request::instance()); - $result = $controller->test(); - $this->assertTrue($result); - } -} diff --git a/tests/thinkphp/library/think/cookieTest.php b/tests/thinkphp/library/think/cookieTest.php deleted file mode 100644 index 24cb153e..00000000 --- a/tests/thinkphp/library/think/cookieTest.php +++ /dev/null @@ -1,150 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Cookie测试 - * @author Haotong Lin - */ - -namespace tests\thinkphp\library\think; - -use ReflectionClass; -use think\Cookie; - -class cookieTest extends \PHPUnit_Framework_TestCase -{ - protected $ref; - - protected $default = [ - // cookie 名称前缀 - 'prefix' => '', - // cookie 保存时间 - 'expire' => 0, - // cookie 保存路径 - 'path' => '/', - // cookie 有效域名 - 'domain' => '', - // cookie 启用安全传输 - 'secure' => false, - // httponly设置 - 'httponly' => '', - // 是否使用 setcookie - 'setcookie' => false, - ]; - - protected function setUp() - { - $reflectedClass = new ReflectionClass('\think\Cookie'); - $reflectedPropertyConfig = $reflectedClass->getProperty('config'); - $reflectedPropertyConfig->setAccessible(true); - $reflectedPropertyConfig->setValue($this->default); - $this->ref = $reflectedPropertyConfig; - } - - public function testInit() - { - $config = [ - // cookie 名称前缀 - 'prefix' => 'think_', - // cookie 保存时间 - 'expire' => 0, - // cookie 保存路径 - 'path' => '/path/to/test/', - // cookie 有效域名 - 'domain' => '.thinkphp.cn', - // cookie 启用安全传输 - 'secure' => true, - // httponly设置 - 'httponly' => '1', - ]; - Cookie::init($config); - - $this->assertEquals( - array_merge($this->default, array_change_key_case($config)), - $this->ref->getValue() - ); - } - - public function testPrefix() - { - $this->assertEquals($this->default['prefix'], Cookie::prefix()); - - $prefix = '_test_'; - $this->assertNotEquals($prefix, Cookie::prefix()); - Cookie::prefix($prefix); - - $config = $this->ref->getValue(); - $this->assertEquals($prefix, $config['prefix']); - } - - public function testSet() - { - $value = 'value'; - - $name = 'name1'; - Cookie::set($name, $value, 10); - $this->assertEquals($value, $_COOKIE[$this->default['prefix'] . $name]); - - $name = 'name2'; - Cookie::set($name, $value, null); - $this->assertEquals($value, $_COOKIE[$this->default['prefix'] . $name]); - - $name = 'name3'; - Cookie::set($name, $value, 'expire=100&prefix=pre_'); - $this->assertEquals($value, $_COOKIE['pre_' . $name]); - - $name = 'name4'; - $value = ['_test_中文_']; - Cookie::set($name, $value); - $this->assertEquals('think:' . json_encode([urlencode('_test_中文_')]), $_COOKIE[$name]); - } - - public function testGet() - { - $_COOKIE = [ - 'a' => 'b', - 'pre_abc' => 'c', - 'd' => 'think:' . json_encode([urlencode('_test_中文_')]), - ]; - $this->assertEquals('b', Cookie::get('a')); - $this->assertEquals(null, Cookie::get('does_not_exist')); - $this->assertEquals('c', Cookie::get('abc', 'pre_')); - $this->assertEquals(['_test_中文_'], Cookie::get('d')); - } - - public function testDelete() - { - $_COOKIE = [ - 'a' => 'b', - 'pre_abc' => 'c', - ]; - $this->assertEquals('b', Cookie::get('a')); - Cookie::delete('a'); - $this->assertEquals(null, Cookie::get('a')); - - $this->assertEquals('c', Cookie::get('abc', 'pre_')); - Cookie::delete('abc', 'pre_'); - $this->assertEquals(null, Cookie::get('abc', 'pre_')); - } - - public function testClear() - { - $_COOKIE = []; - $this->assertEquals(null, Cookie::clear()); - - $_COOKIE = [ - 'a' => 'b', - 'pre_abc' => 'c', - ]; - Cookie::clear('pre_'); - $this->assertEquals(['a' => 'b'], $_COOKIE); - } -} diff --git a/tests/thinkphp/library/think/db/driver/.gitignore b/tests/thinkphp/library/think/db/driver/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/think/db/driver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/think/dbTest.php b/tests/thinkphp/library/think/dbTest.php deleted file mode 100644 index 9019917e..00000000 --- a/tests/thinkphp/library/think/dbTest.php +++ /dev/null @@ -1,27 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Db类测试 - */ - -namespace tests\thinkphp\library\think; - -use \think\Db; - -class dbTest extends \PHPUnit_Framework_TestCase -{ - public function testConnect() - { - Db::connect('mysql://root@127.0.0.1/test#utf8')->execute('show databases'); - } - -} diff --git a/tests/thinkphp/library/think/debugTest.php b/tests/thinkphp/library/think/debugTest.php deleted file mode 100644 index 12ff815f..00000000 --- a/tests/thinkphp/library/think/debugTest.php +++ /dev/null @@ -1,179 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Debug测试 - * @author 大漠 - */ - -namespace tests\thinkphp\library\think; - -use think\Debug; - -class debugTest extends \PHPUnit_Framework_TestCase -{ - - /** - * - * @var Debug - */ - protected $object; - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - protected function setUp() - { - $this->object = new Debug(); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown() - {} - - /** - * @covers think\Debug::remark - * @todo Implement testRemark(). - */ - public function testRemark() - { - $name = "testremarkkey"; - Debug::remark($name); - } - - /** - * @covers think\Debug::getRangeTime - * @todo Implement testGetRangeTime(). - */ - public function testGetRangeTime() - { - $start = "testGetRangeTimeStart"; - $end = "testGetRangeTimeEnd"; - Debug::remark($start); - usleep(20000); - // \think\Debug::remark($end); - - $time = Debug::getRangeTime($start, $end); - $this->assertLessThan(0.03, $time); - //$this->assertEquals(0.03, ceil($time)); - } - - /** - * @covers think\Debug::getUseTime - * @todo Implement testGetUseTime(). - */ - public function testGetUseTime() - { - $time = Debug::getUseTime(); - $this->assertLessThan(20, $time); - } - - /** - * @covers think\Debug::getThroughputRate - * @todo Implement testGetThroughputRate(). - */ - public function testGetThroughputRate() - { - usleep(100000); - $throughputRate = Debug::getThroughputRate(); - $this->assertLessThan(10, $throughputRate); - } - - /** - * @covers think\Debug::getRangeMem - * @todo Implement testGetRangeMem(). - */ - public function testGetRangeMem() - { - $start = "testGetRangeMemStart"; - $end = "testGetRangeMemEnd"; - Debug::remark($start); - $str = ""; - for ($i = 0; $i < 10000; $i++) { - $str .= "mem"; - } - - $rangeMem = Debug::getRangeMem($start, $end); - - $this->assertLessThan(33, explode(" ", $rangeMem)[0]); - } - - /** - * @covers think\Debug::getUseMem - * @todo Implement testGetUseMem(). - */ - public function testGetUseMem() - { - $useMem = Debug::getUseMem(); - - $this->assertLessThan(30, explode(" ", $useMem)[0]); - } - - /** - * @covers think\Debug::getMemPeak - * @todo Implement testGetMemPeak(). - */ - public function testGetMemPeak() - { - $start = "testGetMemPeakStart"; - $end = "testGetMemPeakEnd"; - Debug::remark($start); - $str = ""; - for ($i = 0; $i < 100000; $i++) { - $str .= "mem"; - } - $memPeak = Debug::getMemPeak($start, $end); - $this->assertLessThan(500, explode(" ", $memPeak)[0]); - } - - /** - * @covers think\Debug::getFile - * @todo Implement testGetFile(). - */ - public function testGetFile() - { - $count = Debug::getFile(); - - $this->assertEquals(count(get_included_files()), $count); - - $info = Debug::getFile(true); - $this->assertEquals(count(get_included_files()), count($info)); - - $this->assertContains("KB", $info[0]); - } - - /** - * @covers think\Debug::dump - * @todo Implement testDump(). - */ - public function testDump() - { - if (strstr(PHP_VERSION, 'hhvm')) { - return ; - } - - $var = []; - $var["key"] = "val"; - $output = Debug::dump($var, false, $label = "label"); - $array = explode("array", json_encode($output)); - if (IS_WIN) { - $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\r\\n\"", end($array)); - } else if (strstr(PHP_OS, 'Darwin')) { - $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); - } else { - $this->assertEquals("(1) {\\n 'key' =>\\n string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); - } - } -} diff --git a/tests/thinkphp/library/think/display.html b/tests/thinkphp/library/think/display.html deleted file mode 100644 index e99d3025..00000000 --- a/tests/thinkphp/library/think/display.html +++ /dev/null @@ -1 +0,0 @@ -{$name??'default'} \ No newline at end of file diff --git a/tests/thinkphp/library/think/exceptionTest.php b/tests/thinkphp/library/think/exceptionTest.php deleted file mode 100644 index 66957ce6..00000000 --- a/tests/thinkphp/library/think/exceptionTest.php +++ /dev/null @@ -1,52 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * exception类测试 - * @author Haotong Lin - */ - -namespace tests\thinkphp\library\think; - -use ReflectionMethod; -use think\Exception as ThinkException; -use think\exception\HttpException; - -class MyException extends ThinkException -{ - -} - -class exceptionTest extends \PHPUnit_Framework_TestCase -{ - public function testGetHttpStatus() - { - try { - throw new HttpException(404, "Error Processing Request"); - } catch (HttpException $e) { - $this->assertEquals(404, $e->getStatusCode()); - } - } - - public function testDebugData() - { - $data = ['a' => 'b', 'c' => 'd']; - try { - $e = new MyException("Error Processing Request", 1); - $method = new ReflectionMethod($e, 'setData'); - $method->setAccessible(true); - $method->invokeArgs($e, ['test', $data]); - throw $e; - } catch (MyException $e) { - $this->assertEquals(['test' => $data], $e->getData()); - } - } -} diff --git a/tests/thinkphp/library/think/extend.html b/tests/thinkphp/library/think/extend.html deleted file mode 100644 index 922192dc..00000000 --- a/tests/thinkphp/library/think/extend.html +++ /dev/null @@ -1,2 +0,0 @@ -{extend name="extend2" /} -{block name="head"}header{/block} \ No newline at end of file diff --git a/tests/thinkphp/library/think/extend2.html b/tests/thinkphp/library/think/extend2.html deleted file mode 100644 index eb22e1da..00000000 --- a/tests/thinkphp/library/think/extend2.html +++ /dev/null @@ -1,17 +0,0 @@ -{layout name="layout2" replace="[__REPLACE__]" /} -{block name="head"}{/block} -
- {include file="include" name="info" value="$info.value" /} -{block name="main"} -{block name="side"} - side -{/block} -{block name="mainbody"} - -{/block} -{/block} -{literal} - {$name} -{/literal} - -
\ No newline at end of file diff --git a/tests/thinkphp/library/think/hookTest.php b/tests/thinkphp/library/think/hookTest.php deleted file mode 100644 index 930ecf53..00000000 --- a/tests/thinkphp/library/think/hookTest.php +++ /dev/null @@ -1,67 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Hook类测试 - * @author liu21st - */ - -namespace tests\thinkphp\library\think; - -use think\Hook; - -class hookTest extends \PHPUnit_Framework_TestCase -{ - - public function testRun() - { - Hook::add('my_pos', '\tests\thinkphp\library\think\behavior\One'); - Hook::add('my_pos', ['\tests\thinkphp\library\think\behavior\Two']); - Hook::add('my_pos', '\tests\thinkphp\library\think\behavior\Three', true); - $data['id'] = 0; - $data['name'] = 'thinkphp'; - Hook::listen('my_pos', $data); - $this->assertEquals(2, $data['id']); - $this->assertEquals('thinkphp', $data['name']); - $this->assertEquals([ - '\tests\thinkphp\library\think\behavior\Three', - '\tests\thinkphp\library\think\behavior\One', - '\tests\thinkphp\library\think\behavior\Two'], - Hook::get('my_pos')); - } - - public function testImport() - { - Hook::import(['my_pos' => [ - '\tests\thinkphp\library\think\behavior\One', - '\tests\thinkphp\library\think\behavior\Three'], - ]); - Hook::import(['my_pos' => ['\tests\thinkphp\library\think\behavior\Two']], false); - Hook::import(['my_pos' => ['\tests\thinkphp\library\think\behavior\Three', '_overlay' => true]]); - $data['id'] = 0; - $data['name'] = 'thinkphp'; - Hook::listen('my_pos', $data); - $this->assertEquals(3, $data['id']); - - } - - public function testExec() - { - $data['id'] = 0; - $data['name'] = 'thinkphp'; - $this->assertEquals(true, Hook::exec('\tests\thinkphp\library\think\behavior\One')); - $this->assertEquals(false, Hook::exec('\tests\thinkphp\library\think\behavior\One', 'test', $data)); - $this->assertEquals('test', $data['name']); - $this->assertEquals('Closure', Hook::exec(function (&$data) {$data['name'] = 'Closure';return 'Closure';})); - - } - -} diff --git a/tests/thinkphp/library/think/include.html b/tests/thinkphp/library/think/include.html deleted file mode 100644 index e01ae9ef..00000000 --- a/tests/thinkphp/library/think/include.html +++ /dev/null @@ -1,2 +0,0 @@ - -{include file="include2" /} \ No newline at end of file diff --git a/tests/thinkphp/library/think/include2.html b/tests/thinkphp/library/think/include2.html deleted file mode 100644 index 24bdacb3..00000000 --- a/tests/thinkphp/library/think/include2.html +++ /dev/null @@ -1 +0,0 @@ -{$info.value}: \ No newline at end of file diff --git a/tests/thinkphp/library/think/lang/lang.php b/tests/thinkphp/library/think/lang/lang.php deleted file mode 100644 index 044b171b..00000000 --- a/tests/thinkphp/library/think/lang/lang.php +++ /dev/null @@ -1,4 +0,0 @@ -'加载', -]; \ No newline at end of file diff --git a/tests/thinkphp/library/think/langTest.php b/tests/thinkphp/library/think/langTest.php deleted file mode 100644 index 360ee43d..00000000 --- a/tests/thinkphp/library/think/langTest.php +++ /dev/null @@ -1,76 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Lang测试 - * @author liu21st - */ - -namespace tests\thinkphp\library\think; - -use think\Config; -use think\Lang; - -class langTest extends \PHPUnit_Framework_TestCase -{ - - public function testSetAndGet() - { - Lang::set('hello,%s', '欢迎,%s'); - $this->assertEquals('欢迎,ThinkPHP', Lang::get('hello,%s', ['ThinkPHP'])); - Lang::set('hello,%s', '歡迎,%s', 'zh-tw'); - $this->assertEquals('歡迎,ThinkPHP', Lang::get('hello,%s', ['ThinkPHP'], 'zh-tw')); - Lang::set(['hello' => '欢迎', 'use' => '使用']); - $this->assertEquals('欢迎', Lang::get('hello')); - $this->assertEquals('欢迎', Lang::get('HELLO')); - $this->assertEquals('使用', Lang::get('use')); - - Lang::set('hello,{:name}', '欢迎,{:name}'); - $this->assertEquals('欢迎,liu21st', Lang::get('hello,{:name}', ['name' => 'liu21st'])); - } - - public function testLoad() - { - Lang::load(__DIR__ . DS . 'lang' . DS . 'lang.php'); - $this->assertEquals('加载', Lang::get('load')); - Lang::load(__DIR__ . DS . 'lang' . DS . 'lang.php', 'test'); - $this->assertEquals('加载', Lang::get('load', [], 'test')); - } - - public function testDetect() - { - - Config::set('lang_list', ['zh-cn', 'zh-tw']); - Lang::set('hello', '欢迎', 'zh-cn'); - Lang::set('hello', '歡迎', 'zh-tw'); - - Config::set('lang_detect_var', 'lang'); - Config::set('lang_cookie_var', 'think_cookie'); - - $_GET['lang'] = 'zh-tw'; - Lang::detect(); - $this->assertEquals('歡迎', Lang::get('hello')); - - $_GET['lang'] = 'zh-cn'; - Lang::detect(); - $this->assertEquals('欢迎', Lang::get('hello')); - - } - - public function testRange() - { - $this->assertEquals('zh-cn', Lang::range()); - Lang::set('hello', '欢迎', 'test'); - Lang::range('test'); - $this->assertEquals('test', Lang::range()); - $this->assertEquals('欢迎', Lang::get('hello')); - } -} diff --git a/tests/thinkphp/library/think/layout.html b/tests/thinkphp/library/think/layout.html deleted file mode 100644 index be8d4a07..00000000 --- a/tests/thinkphp/library/think/layout.html +++ /dev/null @@ -1,2 +0,0 @@ -
{__CONTENT__} -
\ No newline at end of file diff --git a/tests/thinkphp/library/think/layout2.html b/tests/thinkphp/library/think/layout2.html deleted file mode 100644 index a5b75729..00000000 --- a/tests/thinkphp/library/think/layout2.html +++ /dev/null @@ -1,2 +0,0 @@ - \ No newline at end of file diff --git a/tests/thinkphp/library/think/loader/test/Hello.php b/tests/thinkphp/library/think/loader/test/Hello.php deleted file mode 100644 index c1414561..00000000 --- a/tests/thinkphp/library/think/loader/test/Hello.php +++ /dev/null @@ -1,7 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Loader测试 - * @author liu21st - */ - -namespace tests\thinkphp\library\think; - -use think\Loader; - -class loaderTest extends \PHPUnit_Framework_TestCase -{ - - public function testAutoload() - { - $this->assertEquals(false, Loader::autoload('\think\Url')); - $this->assertEquals(false, Loader::autoload('think\Test')); - $this->assertEquals(false, Loader::autoload('my\HelloTest')); - } - - public function testAddClassMap() - { - Loader::addClassMap('my\hello\Test', __DIR__ . DS . 'loader' . DS . 'Test.php'); - } - - public function testAddNamespace() - { - Loader::addNamespace('top', __DIR__ . DS . 'loader' . DS); - $this->assertEquals(true, Loader::autoload('top\test\Hello')); - } - - public function testAddNamespaceAlias() - { - Loader::addNamespaceAlias('top', 'top\test'); - Loader::addNamespaceAlias(['top' => 'top\test', 'app' => 'app\index']); - //$this->assertEquals(true, Loader::autoload('top\Hello')); - } - - public function testTable() - { - Loader::db('mysql://root@127.0.0.1/test#utf8'); - } - - public function testImport() - { - $this->assertEquals(false, Loader::import('think.log.driver.MyTest')); - } - - public function testParseName() - { - $this->assertEquals('HelloTest', Loader::parseName('hello_test', 1)); - $this->assertEquals('hello_test', Loader::parseName('HelloTest', 0)); - } - - public function testParseClass() - { - $this->assertEquals('app\index\controller\User', Loader::parseClass('index', 'controller', 'user')); - $this->assertEquals('app\index\controller\user\Type', Loader::parseClass('index', 'controller', 'user.type')); - $this->assertEquals('app\admin\model\UserType', Loader::parseClass('admin', 'model', 'user_type')); - } -} diff --git a/tests/thinkphp/library/think/log/driver/fileTest.php b/tests/thinkphp/library/think/log/driver/fileTest.php deleted file mode 100644 index 3453c09b..00000000 --- a/tests/thinkphp/library/think/log/driver/fileTest.php +++ /dev/null @@ -1,34 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Test File Log - */ -namespace tests\thinkphp\library\think\log\driver; - -use think\Log; - -class fileTest extends \PHPUnit_Framework_TestCase -{ - protected function setUp() - { - Log::init(['type' => 'file']); - } - - public function testRecord() - { - $record_msg = 'record'; - Log::record($record_msg, 'notice'); - $logs = Log::getLog(); - - $this->assertNotFalse(array_search($record_msg, $logs['notice'])); - } -} diff --git a/tests/thinkphp/library/think/logTest.php b/tests/thinkphp/library/think/logTest.php deleted file mode 100644 index 554cf60d..00000000 --- a/tests/thinkphp/library/think/logTest.php +++ /dev/null @@ -1,51 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Log测试 - * @author liu21st - */ -namespace tests\thinkphp\library\think; - -use think\Log; - -class logTest extends \PHPUnit_Framework_TestCase -{ - - public function testRecord() - { - Log::clear(); - Log::record('test'); - $this->assertEquals(['log' => ['test']], Log::getLog()); - Log::record('hello', 'info'); - $this->assertEquals(['log' => ['test'], 'info' => ['hello']], Log::getLog()); - Log::clear(); - Log::info('test'); - $this->assertEquals(['info' => ['test']], Log::getLog()); - } - - public function testSave() - { - Log::init(['type' => 'test']); - Log::clear(); - Log::record('test'); - Log::record([1, 2, 3]); - $this->assertTrue(Log::save()); - } - - public function testWrite() - { - Log::init(['type' => 'test']); - Log::clear(); - $this->assertTrue(Log::write('hello', 'info')); - $this->assertTrue(Log::write([1, 2, 3], 'log')); - } -} diff --git a/tests/thinkphp/library/think/model/.gitignore b/tests/thinkphp/library/think/model/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/think/model/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/think/paginateTest.php b/tests/thinkphp/library/think/paginateTest.php deleted file mode 100644 index 5bbcc961..00000000 --- a/tests/thinkphp/library/think/paginateTest.php +++ /dev/null @@ -1,44 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace tests\thinkphp\library\think; - - -use think\paginator\driver\Bootstrap; - -class paginateTest extends \PHPUnit_Framework_TestCase -{ - public function testPaginatorInfo() - { - $p = Bootstrap::make($array = ['item3', 'item4'], 2, 2, 4); - - $this->assertEquals(4, $p->total()); - - $this->assertEquals(2, $p->listRows()); - - $this->assertEquals(2, $p->currentPage()); - - $p2 = Bootstrap::make($array2 = ['item3', 'item4'], 2, 2, 2); - $this->assertEquals(1, $p2->currentPage()); - } - - public function testPaginatorRender() - { - $p = Bootstrap::make($array = ['item3', 'item4'], 2, 2, 100); - $render = ''; - - $this->assertEquals($render, $p->render()); - } - - - - -} \ No newline at end of file diff --git a/tests/thinkphp/library/think/requestTest.php b/tests/thinkphp/library/think/requestTest.php deleted file mode 100644 index 964b5c85..00000000 --- a/tests/thinkphp/library/think/requestTest.php +++ /dev/null @@ -1,186 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Db类测试 - */ - -namespace tests\thinkphp\library\think; - -use think\Config; -use think\Request; -use think\Route; - -class requestTest extends \PHPUnit_Framework_TestCase -{ - protected $request; - - public function setUp() - { - //$request = Request::create('http://www.domain.com/index/index/hello/?name=thinkphp'); - - } - - public function testCreate() - { - $request = Request::create('http://www.thinkphp.cn/index/index/hello.html?name=thinkphp'); - $this->assertEquals('http://www.thinkphp.cn', $request->domain()); - $this->assertEquals('/index/index/hello.html?name=thinkphp', $request->url()); - $this->assertEquals('/index/index/hello.html', $request->baseurl()); - $this->assertEquals('index/index/hello.html', $request->pathinfo()); - $this->assertEquals('index/index/hello', $request->path()); - $this->assertEquals('html', $request->ext()); - $this->assertEquals('name=thinkphp', $request->query()); - $this->assertEquals('www.thinkphp.cn', $request->host()); - $this->assertEquals(80, $request->port()); - $this->assertEquals($_SERVER['REQUEST_TIME'], $request->time()); - $this->assertEquals($_SERVER['REQUEST_TIME_FLOAT'], $request->time(true)); - $this->assertEquals('GET', $request->method()); - $this->assertEquals(['name' => 'thinkphp'], $request->param()); - $this->assertFalse($request->isSsl()); - $this->assertEquals('http', $request->scheme()); - } - - public function testDomain() - { - $request = Request::instance(); - $request->domain('http://thinkphp.cn'); - $this->assertEquals('http://thinkphp.cn', $request->domain()); - } - - public function testUrl() - { - $request = Request::instance(); - $request->url('/index.php/index/hello?name=thinkphp'); - $this->assertEquals('/index.php/index/hello?name=thinkphp', $request->url()); - $this->assertEquals('http://thinkphp.cn/index.php/index/hello?name=thinkphp', $request->url(true)); - } - - public function testBaseUrl() - { - $request = Request::instance(); - $request->baseurl('/index.php/index/hello'); - $this->assertEquals('/index.php/index/hello', $request->baseurl()); - $this->assertEquals('http://thinkphp.cn/index.php/index/hello', $request->baseurl(true)); - } - - public function testbaseFile() - { - $request = Request::instance(); - $request->basefile('/index.php'); - $this->assertEquals('/index.php', $request->basefile()); - $this->assertEquals('http://thinkphp.cn/index.php', $request->basefile(true)); - } - - public function testroot() - { - $request = Request::instance(); - $request->root('/index.php'); - $this->assertEquals('/index.php', $request->root()); - $this->assertEquals('http://thinkphp.cn/index.php', $request->root(true)); - } - - public function testType() - { - $request = Request::instance(); - $_SERVER['HTTP_ACCEPT'] = 'application/json'; - $this->assertEquals('json', $request->type()); - $request->mimeType('test', 'application/test'); - $request->mimeType(['test' => 'application/test']); - $_SERVER['HTTP_ACCEPT'] = 'application/test'; - $this->assertEquals('test', $request->type()); - } - - public function testmethod() - { - $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'DELETE'; - - $request = new Request(); - $this->assertEquals('DELETE', $request->method()); - $this->assertEquals('GET', $request->method(true)); - - Config::set('var_method', '_method'); - $_POST['_method'] = 'POST'; - $request = new Request(); - $this->assertEquals('POST', $request->method()); - $this->assertEquals('GET', $request->method(true)); - $this->assertTrue($request->isPost()); - $this->assertFalse($request->isGet()); - $this->assertFalse($request->isPut()); - $this->assertFalse($request->isDelete()); - $this->assertFalse($request->isHead()); - $this->assertFalse($request->isPatch()); - $this->assertFalse($request->isOptions()); - } - - public function testCli() - { - $request = Request::instance(); - $this->assertTrue($request->isCli()); - } - - public function testVar() - { - Config::set('app_multi_module', true); - $request = new Request(); - $request->route(['name' => 'thinkphp', 'id' => 6]); - $request->get(['id' => 10]); - $request->post(['id' => 8]); - $request->put(['id' => 7]); - $request->request(['test' => 'value']); - $this->assertEquals(['name' => 'thinkphp', 'id' => 6], $request->route()); - //$this->assertEquals(['id' => 10], $request->get()); - $this->assertEquals('thinkphp', $request->route('name')); - $this->assertEquals('default', $request->route('test', 'default')); - $this->assertEquals(10, $request->get('id')); - $this->assertEquals(0, $request->get('ids', 0)); - $this->assertEquals(8, $request->post('id')); - $this->assertEquals(7, $request->put('id')); - $this->assertEquals('value', $request->request('test')); - $this->assertEquals('thinkphp', $request->param('name')); - $this->assertEquals(6, $request->param('id')); - $this->assertFalse($request->has('user_id')); - $this->assertTrue($request->has('test', 'request')); - $this->assertEquals(['id' => 6], $request->only('id')); - $this->assertEquals(['name' => 'thinkphp', 'lang' => 'zh-cn'], $request->except('id')); - $this->assertEquals('THINKPHP', $request->param('name', '', 'strtoupper')); - } - - public function testIsAjax() - { - $request = new Request(); - $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest'; - $this->assertTrue($request->isAjax()); - } - - public function testIsPjax() - { - $request = new Request(); - $_SERVER['HTTP_X_PJAX'] = true; - $this->assertTrue($request->isPjax()); - } - - public function testIsMobile() - { - $request = new Request(); - $_SERVER['HTTP_VIA'] = 'wap'; - $this->assertTrue($request->isMobile()); - } - - public function testBind() - { - $request = new Request(); - $request->user = 'User1'; - $request->bind(['user' => 'User2']); - $this->assertEquals('User2', $request->user); - } - -} diff --git a/tests/thinkphp/library/think/responseTest.php b/tests/thinkphp/library/think/responseTest.php deleted file mode 100644 index aa21a4b6..00000000 --- a/tests/thinkphp/library/think/responseTest.php +++ /dev/null @@ -1,95 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Response测试 - * @author 大漠 - */ - -namespace tests\thinkphp\library\think; - -use think\Config; -use think\Request; -use think\Response; - -class responseTest extends \PHPUnit_Framework_TestCase -{ - - /** - * - * @var \think\Response - */ - protected $object; - - protected $default_return_type; - - protected $default_ajax_return; - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - protected function setUp() - { - // 1. - // restore_error_handler(); - // Warning: Cannot modify header information - headers already sent by (output started at PHPUnit\Util\Printer.php:173) - // more see in https://www.analysisandsolutions.com/blog/html/writing-phpunit-tests-for-wordpress-plugins-wp-redirect-and-continuing-after-php-errors.htm - - // 2. - // the Symfony used the HeaderMock.php - - // 3. - // not run the eclipse will held, and travis-ci.org Searching for coverage reports - // **> Python coverage not found - // **> No coverage report found. - // add the - // /** - // * @runInSeparateProcess - // */ - if (!$this->default_return_type) { - $this->default_return_type = Config::get('default_return_type'); - } - if (!$this->default_ajax_return) { - $this->default_ajax_return = Config::get('default_ajax_return'); - } - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown() - { - Config::set('default_ajax_return', $this->default_ajax_return); - Config::set('default_return_type', $this->default_return_type); - } - - /** - * @covers think\Response::send - * @todo Implement testSend(). - */ - public function testSend() - { - $dataArr = []; - $dataArr["key"] = "value"; - - $response = Response::create($dataArr, 'json'); - $result = $response->getContent(); - $this->assertEquals('{"key":"value"}', $result); - $request = Request::instance(); - $request->get(['callback' => 'callback']); - $response = Response::create($dataArr, 'jsonp'); - $result = $response->getContent(); - $this->assertEquals('callback({"key":"value"});', $result); - } - -} diff --git a/tests/thinkphp/library/think/routeTest.php b/tests/thinkphp/library/think/routeTest.php deleted file mode 100644 index d6790347..00000000 --- a/tests/thinkphp/library/think/routeTest.php +++ /dev/null @@ -1,287 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Route测试 - * @author liu21st - */ - -namespace tests\thinkphp\library\think; - -use think\Config; -use think\Request; -use think\Route; - -class routeTest extends \PHPUnit_Framework_TestCase -{ - - protected function setUp() - { - Config::set('app_multi_module', true); - } - - public function testRegister() - { - $request = Request::instance(); - Route::get('hello/:name', 'index/hello'); - Route::get(['hello/:name' => 'index/hello']); - Route::post('hello/:name', 'index/post'); - Route::put('hello/:name', 'index/put'); - Route::delete('hello/:name', 'index/delete'); - Route::patch('hello/:name', 'index/patch'); - Route::any('user/:id', 'index/user'); - $result = Route::check($request, 'hello/thinkphp'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - $this->assertEquals(['hello' => true, 'user/:id' => true, 'hello/:name' => ['rule' => 'hello/:name', 'route' => 'index/hello', 'var' => ['name' => 1], 'option' => [], 'pattern' => []]], Route::rules('GET')); - Route::rule('type1/:name', 'index/type', 'PUT|POST'); - Route::rule(['type2/:name' => 'index/type1']); - Route::rule([['type3/:name', 'index/type2', ['method' => 'POST']]]); - Route::rule(['name', 'type4/:name'], 'index/type4'); - } - - public function testImport() - { - $rule = [ - '__domain__' => ['subdomain2.thinkphp.cn' => 'blog1'], - '__alias__' => ['blog1' => 'blog1'], - '__rest__' => ['res' => ['index/blog']], - 'bbb' => ['index/blog1', ['method' => 'POST']], - 'ddd' => '', - ['hello1/:ddd', 'index/hello1', ['method' => 'POST']], - ]; - Route::import($rule); - } - - public function testResource() - { - $request = Request::instance(); - Route::resource('res', 'index/blog'); - Route::resource(['res' => ['index/blog']]); - $result = Route::check($request, 'res'); - $this->assertEquals(['index', 'blog', 'index'], $result['module']); - $result = Route::check($request, 'res/create'); - $this->assertEquals(['index', 'blog', 'create'], $result['module']); - $result = Route::check($request, 'res/8'); - $this->assertEquals(['index', 'blog', 'read'], $result['module']); - $result = Route::check($request, 'res/8/edit'); - $this->assertEquals(['index', 'blog', 'edit'], $result['module']); - - Route::resource('blog.comment', 'index/comment'); - $result = Route::check($request, 'blog/8/comment/10'); - $this->assertEquals(['index', 'comment', 'read'], $result['module']); - $result = Route::check($request, 'blog/8/comment/10/edit'); - $this->assertEquals(['index', 'comment', 'edit'], $result['module']); - - } - - public function testRest() - { - $request = Request::instance(); - Route::rest('read', ['GET', '/:id', 'look']); - Route::rest('create', ['GET', '/create', 'add']); - Route::rest(['read' => ['GET', '/:id', 'look'], 'create' => ['GET', '/create', 'add']]); - Route::resource('res', 'index/blog'); - $result = Route::check($request, 'res/create'); - $this->assertEquals(['index', 'blog', 'add'], $result['module']); - $result = Route::check($request, 'res/8'); - $this->assertEquals(['index', 'blog', 'look'], $result['module']); - - } - - public function testMixVar() - { - $request = Request::instance(); - Route::get('hello-', 'index/hello', [], ['name' => '\w+']); - $result = Route::check($request, 'hello-thinkphp'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - Route::get('hello-', 'index/hello', [], ['name' => '\w+', 'id' => '\d+']); - $result = Route::check($request, 'hello-thinkphp2016'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - Route::get('hello-/[:id]', 'index/hello', [], ['name' => '\w+', 'id' => '\d+']); - $result = Route::check($request, 'hello-thinkphp/2016'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - } - - public function testParseUrl() - { - $result = Route::parseUrl('hello'); - $this->assertEquals(['hello', null, null], $result['module']); - $result = Route::parseUrl('index/hello'); - $this->assertEquals(['index', 'hello', null], $result['module']); - $result = Route::parseUrl('index/hello?name=thinkphp'); - $this->assertEquals(['index', 'hello', null], $result['module']); - $result = Route::parseUrl('index/user/hello'); - $this->assertEquals(['index', 'user', 'hello'], $result['module']); - $result = Route::parseUrl('index/user/hello/name/thinkphp'); - $this->assertEquals(['index', 'user', 'hello'], $result['module']); - $result = Route::parseUrl('index-index-hello', '-'); - $this->assertEquals(['index', 'index', 'hello'], $result['module']); - } - - public function testCheckRoute() - { - Route::get('hello/:name', 'index/hello'); - Route::get('blog/:id', 'blog/read', [], ['id' => '\d+']); - $request = Request::instance(); - $this->assertEquals(false, Route::check($request, 'test/thinkphp')); - $this->assertEquals(false, Route::check($request, 'blog/thinkphp')); - $result = Route::check($request, 'blog/5'); - $this->assertEquals([null, 'blog', 'read'], $result['module']); - $result = Route::check($request, 'hello/thinkphp/abc/test'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - } - - public function testCheckRouteGroup() - { - $request = Request::instance(); - Route::pattern(['id' => '\d+']); - Route::pattern('name', '\w{6,25}'); - Route::group('group', [':id' => 'index/hello', ':name' => 'index/say']); - $this->assertEquals(false, Route::check($request, 'empty/think')); - $result = Route::check($request, 'group/think'); - $this->assertEquals(false, $result['module']); - $result = Route::check($request, 'group/10'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - $result = Route::check($request, 'group/thinkphp'); - $this->assertEquals([null, 'index', 'say'], $result['module']); - Route::group('group2', function () { - Route::group('group3', [':id' => 'index/hello', ':name' => 'index/say']); - Route::rule(':name', 'index/hello'); - Route::auto('index'); - }); - $result = Route::check($request, 'group2/thinkphp'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - $result = Route::check($request, 'group2/think'); - $this->assertEquals(['index', 'group2', 'think'], $result['module']); - $result = Route::check($request, 'group2/group3/thinkphp'); - $this->assertEquals([null, 'index', 'say'], $result['module']); - Route::group('group4', function () { - Route::group('group3', [':id' => 'index/hello', ':name' => 'index/say']); - Route::rule(':name', 'index/hello'); - Route::miss('index/__miss__'); - }); - $result = Route::check($request, 'group4/thinkphp'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - $result = Route::check($request, 'group4/think'); - $this->assertEquals([null, 'index', '__miss__'], $result['module']); - - Route::group(['prefix' => 'prefix/'], function () { - Route::rule('hello4/:name', 'hello'); - }); - Route::group(['prefix' => 'prefix/'], [ - 'hello4/:name' => 'hello', - ]); - $result = Route::check($request, 'hello4/thinkphp'); - $this->assertEquals([null, 'prefix', 'hello'], $result['module']); - Route::group('group5', [ - [':name', 'hello', ['method' => 'GET|POST']], - ':id' => 'hello', - ], ['prefix' => 'prefix/']); - $result = Route::check($request, 'group5/thinkphp'); - $this->assertEquals([null, 'prefix', 'hello'], $result['module']); - } - - public function testControllerRoute() - { - $request = Request::instance(); - Route::controller('controller', 'index/Blog'); - $result = Route::check($request, 'controller/info'); - $this->assertEquals(['index', 'Blog', 'getinfo'], $result['module']); - Route::setMethodPrefix('GET', 'read'); - Route::setMethodPrefix(['get' => 'read']); - Route::controller('controller', 'index/Blog'); - $result = Route::check($request, 'controller/phone'); - $this->assertEquals(['index', 'Blog', 'readphone'], $result['module']); - } - - public function testAliasRoute() - { - $request = Request::instance(); - Route::alias('alias', 'index/Alias'); - $result = Route::check($request, 'alias/info'); - $this->assertEquals('index/Alias/info', $result['module']); - } - - public function testRouteToModule() - { - $request = Request::instance(); - Route::get('hello/:name', 'index/hello'); - Route::get('blog/:id', 'blog/read', [], ['id' => '\d+']); - $this->assertEquals(false, Route::check($request, 'test/thinkphp')); - $this->assertEquals(false, Route::check($request, 'blog/thinkphp')); - $result = Route::check($request, 'hello/thinkphp'); - $this->assertEquals([null, 'index', 'hello'], $result['module']); - $result = Route::check($request, 'blog/5'); - $this->assertEquals([null, 'blog', 'read'], $result['module']); - } - - public function testRouteToController() - { - $request = Request::instance(); - Route::get('say/:name', '@index/hello'); - $this->assertEquals(['type' => 'controller', 'controller' => 'index/hello'], Route::check($request, 'say/thinkphp')); - } - - public function testRouteToMethod() - { - $request = Request::instance(); - Route::get('user/:name', '\app\index\service\User::get', [], ['name' => '\w+']); - Route::get('info/:name', '\app\index\model\Info@getInfo', [], ['name' => '\w+']); - $this->assertEquals(['type' => 'method', 'method' => '\app\index\service\User::get'], Route::check($request, 'user/thinkphp')); - $this->assertEquals(['type' => 'method', 'method' => ['\app\index\model\Info', 'getInfo']], Route::check($request, 'info/thinkphp')); - } - - public function testRouteToRedirect() - { - $request = Request::instance(); - Route::get('art/:id', '/article/read/id/:id', [], ['id' => '\d+']); - $this->assertEquals(['type' => 'redirect', 'url' => '/article/read/id/8', 'status' => 301], Route::check($request, 'art/8')); - } - - public function testBind() - { - $request = Request::instance(); - Route::bind('index/blog'); - Route::get('blog/:id', 'index/blog/read'); - $result = Route::check($request, 'blog/10'); - $this->assertEquals(['index', 'blog', 'read'], $result['module']); - $result = Route::parseUrl('test'); - $this->assertEquals(['index', 'blog', 'test'], $result['module']); - - Route::bind('\app\index\controller', 'namespace'); - $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\blog', 'read']], Route::check($request, 'blog/read')); - - Route::bind('\app\index\controller\blog', 'class'); - $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\blog', 'read']], Route::check($request, 'read')); - } - - public function testDomain() - { - $request = Request::create('http://subdomain.thinkphp.cn'); - Route::domain('subdomain.thinkphp.cn', 'sub?abc=test&status=1'); - $rules = Route::rules('GET'); - Route::checkDomain($request, $rules); - $this->assertEquals('sub', Route::getbind('module')); - $this->assertEquals('test', $_GET['abc']); - $this->assertEquals(1, $_GET['status']); - - Route::domain('subdomain.thinkphp.cn', '\app\index\controller'); - $rules = Route::rules('GET'); - Route::checkDomain($request, $rules); - $this->assertEquals('\app\index\controller', Route::getbind('namespace')); - - Route::domain(['subdomain.thinkphp.cn' => '@\app\index\controller\blog']); - $rules = Route::rules('GET'); - Route::checkDomain($request, $rules); - $this->assertEquals('\app\index\controller\blog', Route::getbind('class')); - - } -} diff --git a/tests/thinkphp/library/think/session/.gitignore b/tests/thinkphp/library/think/session/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/think/session/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/think/sessionTest.php b/tests/thinkphp/library/think/sessionTest.php deleted file mode 100644 index ab3e64f4..00000000 --- a/tests/thinkphp/library/think/sessionTest.php +++ /dev/null @@ -1,319 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Session测试 - * @author 大漠 - */ - -namespace tests\thinkphp\library\think; - -use think\Session; - -class sessionTest extends \PHPUnit_Framework_TestCase -{ - - /** - * - * @var \think\Session - */ - protected $object; - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - protected function setUp() - { - // $this->object = new Session (); - // register_shutdown_function ( function () { - // } ); // 此功能无法取消,需要回调函数配合。 - set_exception_handler(function () {}); - set_error_handler(function () {}); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown() - { - register_shutdown_function('think\Error::appShutdown'); - set_error_handler('think\Error::appError'); - set_exception_handler('think\Error::appException'); - } - - /** - * @covers think\Session::prefix - * - * @todo Implement testPrefix(). - */ - public function testPrefix() - { - Session::prefix(null); - Session::prefix('think_'); - - $this->assertEquals('think_', Session::prefix()); - } - - /** - * @covers think\Session::init - * - * @todo Implement testInit(). - */ - public function testInit() - { - Session::prefix(null); - $config = [ - // cookie 名称前缀 - 'prefix' => 'think_', - // cookie 保存时间 - 'expire' => 60, - // cookie 保存路径 - 'path' => '/path/to/test/session/', - // cookie 有效域名 - 'domain' => '.thinkphp.cn', - 'var_session_id' => 'sessionidtest', - 'id' => 'sess_8fhgkjuakhatbeg2fa14lo84q1', - 'name' => 'session_name', - 'use_trans_sid' => '1', - 'use_cookies' => '1', - 'cache_limiter' => '60', - 'cache_expire' => '60', - 'type' => '', // memcache - 'namespace' => '\\think\\session\\driver\\', // ? - 'auto_start' => '1', - ]; - - $_REQUEST[$config['var_session_id']] = $config['id']; - Session::init($config); - - // 开始断言 - $this->assertEquals($config['prefix'], Session::prefix()); - $this->assertEquals($config['id'], $_REQUEST[$config['var_session_id']]); - $this->assertEquals($config['name'], session_name()); - - $this->assertEquals($config['path'], session_save_path()); - $this->assertEquals($config['use_cookies'], ini_get('session.use_cookies')); - $this->assertEquals($config['domain'], ini_get('session.cookie_domain')); - $this->assertEquals($config['expire'], ini_get('session.gc_maxlifetime')); - $this->assertEquals($config['expire'], ini_get('session.cookie_lifetime')); - - $this->assertEquals($config['cache_limiter'], session_cache_limiter($config['cache_limiter'])); - $this->assertEquals($config['cache_expire'], session_cache_expire($config['cache_expire'])); - - // 检测分支 - $_REQUEST[$config['var_session_id']] = null; - session_write_close(); - session_destroy(); - - Session::init($config); - - // 测试auto_start - // PHP_SESSION_DISABLED - // PHP_SESSION_NONE - // PHP_SESSION_ACTIVE - // session_status() - if (strstr(PHP_VERSION, 'hhvm')) { - $this->assertEquals('', ini_get('session.auto_start')); - } else { - $this->assertEquals(0, ini_get('session.auto_start')); - } - - $this->assertEquals($config['use_trans_sid'], ini_get('session.use_trans_sid')); - - Session::init($config); - $this->assertEquals($config['id'], session_id()); - } - - /** - * 单独重现异常 - * @expectedException \think\Exception - */ - public function testException() - { - $config = [ - // cookie 名称前缀 - 'prefix' => 'think_', - // cookie 保存时间 - 'expire' => 0, - // cookie 保存路径 - 'path' => '/path/to/test/session/', - // cookie 有效域名 - 'domain' => '.thinkphp.cn', - 'var_session_id' => 'sessionidtest', - 'id' => 'sess_8fhgkjuakhatbeg2fa14lo84q1', - 'name' => 'session_name', - 'use_trans_sid' => '1', - 'use_cookies' => '1', - 'cache_limiter' => '60', - 'cache_expire' => '60', - 'type' => '\\think\\session\\driver\\Memcache', // - 'auto_start' => '1', - ]; - - // 测试session驱动是否存在 - // @expectedException 异常类名 - $this->setExpectedException('\think\exception\ClassNotFoundException', 'error session handler'); - - Session::init($config); - } - - /** - * @covers think\Session::set - * - * @todo Implement testSet(). - */ - public function testSet() - { - Session::prefix(null); - Session::set('sessionname', 'sessionvalue'); - $this->assertEquals('sessionvalue', $_SESSION['sessionname']); - - Session::set('sessionnamearr.subname', 'sessionvalue'); - $this->assertEquals('sessionvalue', $_SESSION['sessionnamearr']['subname']); - - Session::set('sessionnameper', 'sessionvalue', 'think_'); - $this->assertEquals('sessionvalue', $_SESSION['think_']['sessionnameper']); - - Session::set('sessionnamearrper.subname', 'sessionvalue', 'think_'); - $this->assertEquals('sessionvalue', $_SESSION['think_']['sessionnamearrper']['subname']); - } - - /** - * @covers think\Session::get - * - * @todo Implement testGet(). - */ - public function testGet() - { - Session::prefix(null); - - Session::set('sessionnameget', 'sessionvalue'); - $this->assertEquals(Session::get('sessionnameget'), $_SESSION['sessionnameget']); - - Session::set('sessionnamegetarr.subname', 'sessionvalue'); - $this->assertEquals(Session::get('sessionnamegetarr.subname'), $_SESSION['sessionnamegetarr']['subname']); - - Session::set('sessionnamegetarrperall', 'sessionvalue', 'think_'); - $this->assertEquals(Session::get('', 'think_')['sessionnamegetarrperall'], $_SESSION['think_']['sessionnamegetarrperall']); - - Session::set('sessionnamegetper', 'sessionvalue', 'think_'); - $this->assertEquals(Session::get('sessionnamegetper', 'think_'), $_SESSION['think_']['sessionnamegetper']); - - Session::set('sessionnamegetarrper.subname', 'sessionvalue', 'think_'); - $this->assertEquals(Session::get('sessionnamegetarrper.subname', 'think_'), $_SESSION['think_']['sessionnamegetarrper']['subname']); - } - - public function testPull() - { - Session::prefix(null); - Session::set('sessionnamedel', 'sessionvalue'); - $this->assertEquals('sessionvalue', Session::pull('sessionnameget')); - $this->assertNull(Session::get('sessionnameget')); - } - - /** - * @covers think\Session::delete - * - * @todo Implement testDelete(). - */ - public function testDelete() - { - Session::prefix(null); - Session::set('sessionnamedel', 'sessionvalue'); - Session::delete('sessionnamedel'); - $this->assertEmpty($_SESSION['sessionnamedel']); - - Session::set('sessionnamedelarr.subname', 'sessionvalue'); - Session::delete('sessionnamedelarr.subname'); - $this->assertEmpty($_SESSION['sessionnamedelarr']['subname']); - - Session::set('sessionnamedelper', 'sessionvalue', 'think_'); - Session::delete('sessionnamedelper', 'think_'); - $this->assertEmpty($_SESSION['think_']['sessionnamedelper']); - - Session::set('sessionnamedelperarr.subname', 'sessionvalue', 'think_'); - Session::delete('sessionnamedelperarr.subname', 'think_'); - $this->assertEmpty($_SESSION['think_']['sessionnamedelperarr']['subname']); - } - - /** - * @covers think\Session::clear - * - * @todo Implement testClear(). - */ - public function testClear() - { - Session::prefix(null); - - Session::set('sessionnameclsper', 'sessionvalue1', 'think_'); - Session::clear('think_'); - $this->assertNull($_SESSION['think_']); - - Session::set('sessionnameclsper', 'sessionvalue1', 'think_'); - Session::clear(); - $this->assertEmpty($_SESSION); - } - - /** - * @covers think\Session::has - * - * @todo Implement testHas(). - */ - public function testHas() - { - Session::prefix(null); - Session::set('sessionnamehas', 'sessionvalue'); - $this->assertTrue(Session::has('sessionnamehas')); - - Session::set('sessionnamehasarr.subname', 'sessionvalue'); - $this->assertTrue(Session::has('sessionnamehasarr.subname')); - - Session::set('sessionnamehasper', 'sessionvalue', 'think_'); - $this->assertTrue(Session::has('sessionnamehasper', 'think_')); - - Session::set('sessionnamehasarrper.subname', 'sessionvalue', 'think_'); - $this->assertTrue(Session::has('sessionnamehasarrper.subname', 'think_')); - } - - /** - * @covers think\Session::pause - * - * @todo Implement testPause(). - */ - public function testPause() - { - Session::pause(); - } - - /** - * @covers think\Session::start - * - * @todo Implement testStart(). - */ - public function testStart() - { - Session::start(); - } - - /** - * @covers think\Session::destroy - * - * @todo Implement testDestroy(). - */ - public function testDestroy() - { - Session::set('sessionnamedestroy', 'sessionvalue'); - Session::destroy(); - $this->assertEmpty($_SESSION['sessionnamedestroy']); - } -} diff --git a/tests/thinkphp/library/think/template/driver/.gitignore b/tests/thinkphp/library/think/template/driver/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/think/template/driver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/think/template/taglib/cxTest.php b/tests/thinkphp/library/think/template/taglib/cxTest.php deleted file mode 100644 index a77824b6..00000000 --- a/tests/thinkphp/library/think/template/taglib/cxTest.php +++ /dev/null @@ -1,575 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * 模板测试 - * @author Haotong Lin - */ - -namespace tests\thinkphp\library\think\tempplate\taglib; - -use think\Template; -use think\template\taglib\Cx; - -class templateTest extends \PHPUnit_Framework_TestCase -{ - public function testPhp() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testVolist() - { - $template = new template(); - $cx = new Cx($template); - - $content = <<\$vo): \$mod = (\$key % 2 );++\$key;?> - - -EOF; - $cx->parseTag($content); - $this->assertEquals($data, $content); - - $content = <<display($content, ['list' => [1, 2, 3, 4, 5]]); - $this->expectOutputString('234'); - } - - public function testForeach() - { - $template = new template(); - $cx = new Cx($template); - - $content = <<\$val} - -{/foreach} -EOF; - $data = <<\$val): ?> - - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = <<\$val): ?> - - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = <<display($content); - $this->expectOutputString('234'); - } - - public function testIf() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -one - -two - -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testSwitch() - { - $template = new template(); - $cx = new Cx($template); - - $content = << - -a - - -b - - -d - - -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testCompare() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << '0'): ?> -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = <<= '0'): ?> -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - } - - public function testRange() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = <<display($content); - $this->expectOutputString('yesno'); - } - - public function testPresent() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testEmpty() - { - $template = new template(); - $cx = new Cx($template); - - $content = <<isEmpty())): ?> -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = <<isEmpty()))): ?> -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testDefined() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -default - -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testImport() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testAssign() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testDefine() - { - $template = new template(); - $cx = new Cx($template); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - - $content = << -EOF; - $cx->parseTag($content); - $this->assertEquals($content, $data); - } - - public function testFor() - { - $template = new template(); - - $content = <<display($content); - $this->expectOutputString('123456789'); - } - public function testUrl() - { - $template = new template(); - $content = <<display($content); - $this->expectOutputString(\think\Url::build('Index/index')); - } - - public function testFunction() - { - $template = new template(); - $data = [ - 'list' => ['language' => 'php', 'version' => ['5.4', '5.5']], - 'a' => '[', - 'b' => ']', - ]; - - $content = <<\$val} -{if is_array(\$val)} -{~\$func(\$val)} -{else} -{if !is_numeric(\$key)} -{\$key.':'.\$val.','} -{else} -{\$a.\$val.\$b} -{/if} -{/if} -{/foreach} -{/function} -EOF; - $template->display($content, $data); - $this->expectOutputString("language:php,[5.4][5.5]"); - } -} diff --git a/tests/thinkphp/library/think/templateTest.php b/tests/thinkphp/library/think/templateTest.php deleted file mode 100644 index 29100f4d..00000000 --- a/tests/thinkphp/library/think/templateTest.php +++ /dev/null @@ -1,414 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * 模板测试 - * @author oldrind - */ - -namespace tests\thinkphp\library\think; - -use think\Template; - -class templateTest extends \PHPUnit_Framework_TestCase -{ - public function testVar() - { - $template = new Template(); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = <<parse($content); - $this->assertEquals($data, $content); - - $content = <<parse($content); - $this->assertEquals($data, $content); - - } - - public function testVarFunction() - { - $template = new Template(); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << - -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - } - - public function testVarIdentify() - { - $config['tpl_begin'] = '<#'; - $config['tpl_end'] = '#>'; - $config['tpl_var_identify'] = ''; - $template = new Template($config); - - $content = << -EOF; - $data = <<a)) ? (is_array(\$info)?\$info['a']:\$info->a) : 'test'; ?> -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - $data = <<a)) echo 'test'; ?> -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - $data = <<a)==(is_array(\$info)?\$info['b']:\$info->b)) echo 'test'; ?> -EOF; - - $template->parse($content); - $this->assertEquals($data, $content); - - $content = << -EOF; - $data = <<a) !== ''?(is_array(\$info)?\$info['a']:\$info->a):'test')?'yes':'no'; ?> -EOF; - $template->parse($content); - $this->assertEquals($data, $content); - - $template2 = new Template(); - $template2->tpl_var_identify = 'obj'; - $content = <<b)?'yes':'no'; ?> -EOF; - $template2->parse($content); - $this->assertEquals($data, $content); - } - - public function testThinkVar() - { - $config['tpl_begin'] = '{'; - $config['tpl_end'] = '}'; - $template = new Template($config); - - $_SERVER['SERVER_NAME'] = 'server_name'; - $_GET['action'] = 'action'; - $_POST['action'] = 'action'; - $_COOKIE['name'] = 'name'; - $_SESSION['action'] = ['name' => 'name']; - define('SITE_NAME', 'site_name'); - - $content = << -{\$Think.GET.action}
-{\$Think.POST.action}
-{\$Think.COOKIE.action}
-{\$Think.COOKIE.action.name}
-{\$Think.SESSION.action}
-{\$Think.SESSION.action.name}
-{\$Think.ENV.OS}
-{\$Think.REQUEST.action}
-{\$Think.CONST.SITE_NAME}
-{\$Think.LANG.action}
-{\$Think.CONFIG.action.name}
-{\$Think.NOW}
-{\$Think.VERSION}
-{\$Think.LDELIM}
-{\$Think.RDELIM}
-{\$Think.SITE_NAME}
-{\$Think.SITE.URL} -EOF; - $data = <<
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -EOF; - $template->parse($content); - $this->assertEquals($data, $content); - } - - public function testFetch() - { - $template = new Template(); - $template->assign('name', 'name'); - $config = [ - 'strip_space' => true, - 'view_path' => dirname(__FILE__) . DS, - 'cache_id' => '__CACHE_ID__', - 'display_cache' => true, - ]; - $data = ['name' => 'value']; - $template->layout('layout')->fetch('display', $data, $config); - $this->expectOutputString('value'); - } - - public function testDisplay() - { - $config['view_path'] = dirname(__FILE__) . DS; - $config['view_suffix'] = '.html'; - $config['layout_on'] = true; - $config['layout_name'] = 'layout'; - $template = new Template($config); - $files = ['extend' => 'extend', 'include' => 'include']; - $template->assign('files', $files); - $template->assign('user', ['name' => 'name', 'account' => 100]); - $template->assign('message', 'message'); - $template->assign('info', ['value' => 'value']); - - $content = << -header -
- -value: - -main - - - side - - -value: - message{\$message} - - - mainbody - - - - {\$name} - - php code
- -EOF; - $template->display($content); - $this->expectOutputString($content2); -// $template->parse($content); - // var_dump($content); - } - - public function testVarAssign() - { - $template = new Template(); - $template->assign('name', 'value'); - $value = $template->get('name'); - $this->assertEquals('value', $value); - } - - public function testVarGet() - { - $template = new Template(); - $data = ['a' => 'a', 'b' => 'b']; - $template->assign($data); - $this->assertEquals($data, $template->get()); - } - - public function testIsCache() - { - $template = new Template(['cache_id' => '__CACHE_ID__', 'display_cache' => true]); - $this->assertTrue($template->isCache('__CACHE_ID__')); - $template->display_cache = false; - $this->assertTrue(!$template->isCache('__CACHE_ID__')); - } -} diff --git a/tests/thinkphp/library/think/urlTest.php b/tests/thinkphp/library/think/urlTest.php deleted file mode 100644 index 94dfc71d..00000000 --- a/tests/thinkphp/library/think/urlTest.php +++ /dev/null @@ -1,116 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Url测试 - * @author liu21st - */ - -namespace tests\thinkphp\library\think; - -use think\Config; -use think\Route; -use think\Url; - -class urlTest extends \PHPUnit_Framework_TestCase -{ - - public function setUp() - { - Route::rules([]); - Route::name([]); - } - - public function testBuildModule() - { - - Route::get('blog/:name', 'index/blog'); - Route::get('blog/:id', 'index/blog'); - Config::set('pathinfo_depr', '/'); - Config::set('url_html_suffix', ''); - - $this->assertEquals('/blog/thinkphp', Url::build('index/blog?name=thinkphp')); - $this->assertEquals('/blog/thinkphp.html', Url::build('index/blog', 'name=thinkphp', 'html')); - $this->assertEquals('/blog/10', Url::build('index/blog?id=10')); - $this->assertEquals('/blog/10.html', Url::build('index/blog', 'id=10', 'html')); - - Route::get('item-', 'blog/item', [], ['name' => '\w+', 'id' => '\d+']); - $this->assertEquals('/item-thinkphp', Url::build('blog/item?name=thinkphp')); - $this->assertEquals('/item-thinkphp2016', Url::build('blog/item?name=thinkphp&id=2016')); - } - - public function testBuildController() - { - Config::set('url_html_suffix', ''); - Route::get('blog/:id', '@index/blog/read'); - $this->assertEquals('/blog/10.html', Url::build('@index/blog/read', 'id=10', 'html')); - - Route::get('foo/bar', '@foo/bar/index'); - $this->assertEquals('/foo/bar', Url::build('@foo/bar/index')); - - Route::get('foo/bar/baz', '@foo/bar.BAZ/index'); - $this->assertEquals('/foo/bar/baz', Url::build('@foo/bar.BAZ/index')); - } - - public function testBuildMethod() - { - Route::get('blog/:id', '\app\index\controller\blog@read'); - $this->assertEquals('/blog/10.html', Url::build('[\app\index\controller\blog@read]', 'id=10', 'html')); - } - - public function testBuildRoute() - { - Route::get('blog/:id', 'index/blog'); - Config::set('url_html_suffix', 'shtml'); - $this->assertNotEquals('/blog/10.html', Url::build('/blog/10')); - $this->assertEquals('/blog/10.shtml', Url::build('/blog/10')); - } - - public function testBuildNameRoute() - { - Route::get(['name', 'blog/:id'], 'index/blog'); - $this->assertEquals([['blog/:id', ['id' => 1], null]], Route::name('name')); - Config::set('url_html_suffix', 'shtml'); - $this->assertEquals('/blog/10.shtml', Url::build('name?id=10')); - } - - public function testBuildAnchor() - { - Route::get('blog/:id', 'index/blog'); - Config::set('url_html_suffix', 'shtml'); - $this->assertEquals('/blog/10.shtml#detail', Url::build('/blog/10#detail')); - - Config::set('url_common_param', true); - $this->assertEquals('/blog/10.shtml?foo=bar#detail', Url::build('/blog/10#detail', "foo=bar")); - } - - public function testBuildDomain() - { - Config::set('url_domain_deploy', true); - Route::domain('subdomain.thinkphp.cn', 'admin'); - $this->assertEquals('http://subdomain.thinkphp.cn/blog/10.shtml', Url::build('/blog/10')); - Route::domain('subdomain.thinkphp.cn', [ - 'hello/:name' => 'index/hello', - ]); - $this->assertEquals('http://subdomain.thinkphp.cn/hello/thinkphp.shtml', Url::build('index/hello?name=thinkphp')); - } - - public function testRoot() - { - Config::set('url_domain_deploy', false); - Config::set('url_common_param', false); - Url::root('/index.php'); - Route::get('blog/:id', 'index/blog/read'); - Config::set('url_html_suffix', 'shtml'); - $this->assertEquals('/index.php/blog/10/name/thinkphp.shtml', Url::build('index/blog/read?id=10&name=thinkphp')); - - } -} diff --git a/tests/thinkphp/library/think/validateTest.php b/tests/thinkphp/library/think/validateTest.php deleted file mode 100644 index 4d7819b5..00000000 --- a/tests/thinkphp/library/think/validateTest.php +++ /dev/null @@ -1,200 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * Validate类测试 - */ - -namespace tests\thinkphp\library\think; - -use think\File; -use think\Validate; - -class validateTest extends \PHPUnit_Framework_TestCase -{ - - public function testCheck() - { - $rule = [ - 'name' => 'require|max:25', - 'age' => 'number|between:1,120', - 'email' => 'email', - ]; - $msg = [ - 'name.require' => '名称必须', - 'name.max' => '名称最多不能超过25个字符', - 'age.number' => '年龄必须是数字', - 'age.between' => '年龄只能在1-120之间', - 'email' => '邮箱格式错误', - ]; - $data = [ - 'name' => 'thinkphp', - 'age' => 10, - 'email' => 'thinkphp@qq.com', - ]; - $validate = new Validate($rule, $msg); - $result = $validate->check($data); - $this->assertEquals(true, $result); - } - - public function testRule() - { - $rule = [ - 'name' => 'require|method:get|alphaNum|max:25|expire:2016-1-1,2026-1-1', - 'account' => 'requireIf:name,thinkphp|alphaDash|min:4|length:4,30', - 'age' => 'number|between:1,120', - 'email' => 'requireWith:name|email', - 'host' => 'activeUrl|activeUrl:A', - 'url' => 'url', - 'ip' => 'ip|ip:ipv4', - 'score' => 'float|gt:60|notBetween:90,100|notIn:70,80|lt:100|elt:100|egt:60', - 'status' => 'integer|in:0,1,2', - 'begin_time' => 'after:2016-3-18', - 'end_time' => 'before:2016-10-01', - 'info' => 'require|array|length:4|max:5|min:2', - 'info.name' => 'require|length:8|alpha|same:thinkphp', - 'value' => 'same:100|different:status', - 'bool' => 'boolean', - 'title' => 'chsAlpha', - 'city' => 'chs', - 'nickname' => 'chsDash', - 'aliasname' => 'chsAlphaNum', - 'file' => 'file|fileSize:20480', - 'image' => 'image|fileMime:image/png|image:80,80,png', - 'test' => 'test', - ]; - $data = [ - 'name' => 'thinkphp', - 'account' => 'liuchen', - 'age' => 10, - 'email' => 'thinkphp@qq.com', - 'host' => 'thinkphp.cn', - 'url' => 'http://thinkphp.cn/topic', - 'ip' => '114.34.54.5', - 'score' => '89.15', - 'status' => 1, - 'begin_time' => '2016-3-20', - 'end_time' => '2016-5-1', - 'info' => [1, 2, 3, 'name' => 'thinkphp'], - 'zip' => '200000', - 'date' => '16-3-8', - 'ok' => 'yes', - 'value' => 100, - 'bool' => 'true', - 'title' => '流年ThinkPHP', - 'city' => '上海', - 'nickname' => '流年ThinkPHP_2016', - 'aliasname' => '流年Think2016', - 'file' => new File(THINK_PATH . 'base.php'), - 'image' => new File(THINK_PATH . 'logo.png'), - 'test' => 'test', - ]; - $validate = new Validate($rule); - $validate->extend('test', function ($value) {return 'test' == $value ? true : false;}); - $validate->rule('zip', '/^\d{6}$/'); - $validate->rule([ - 'ok' => 'require|accepted', - 'date' => 'date|dateFormat:y-m-d', - ]); - $result = $validate->batch()->check($data); - $this->assertEquals(true, $result); - } - - public function testMsg() - { - $validate = new Validate(); - $validate->message('name.require', '名称必须'); - $validate->message([ - 'name.require' => '名称必须', - 'name.max' => '名称最多不能超过25个字符', - 'age.number' => '年龄必须是数字', - 'age.between' => '年龄只能在1-120之间', - 'email' => '邮箱格式错误', - ]); - } - - public function testMake() - { - $rule = [ - 'name' => 'require|max:25', - 'age' => 'number|between:1,120', - 'email' => 'email', - ]; - $msg = [ - 'name.require' => '名称必须', - 'name.max' => '名称最多不能超过25个字符', - 'age.number' => '年龄必须是数字', - 'age.between' => '年龄只能在1-120之间', - 'email' => '邮箱格式错误', - ]; - $validate = Validate::make($rule, $msg); - } - - public function testExtend() - { - $validate = new Validate(['name' => 'check:1']); - $validate->extend('check', function ($value, $rule) {return $rule == $value ? true : false;}); - $validate->extend(['check' => function ($value, $rule) {return $rule == $value ? true : false;}]); - $data = ['name' => 1]; - $result = $validate->check($data); - $this->assertEquals(true, $result); - } - - public function testScene() - { - $rule = [ - 'name' => 'require|max:25', - 'age' => 'number|between:1,120', - 'email' => 'email', - ]; - $msg = [ - 'name.require' => '名称必须', - 'name.max' => '名称最多不能超过25个字符', - 'age.number' => '年龄必须是数字', - 'age.between' => '年龄只能在1-120之间', - 'email' => '邮箱格式错误', - ]; - $data = [ - 'name' => 'thinkphp', - 'age' => 10, - 'email' => 'thinkphp@qq.com', - ]; - $validate = new Validate($rule); - $validate->scene(['edit' => ['name', 'age']]); - $validate->scene('edit', ['name', 'age']); - $validate->scene('edit'); - $result = $validate->check($data); - $this->assertEquals(true, $result); - } - - public function testSetTypeMsg() - { - $rule = [ - 'name|名称' => 'require|max:25', - 'age' => 'number|between:1,120', - 'email' => 'email', - ['sex', 'in:1,2', '性别错误'], - ]; - $data = [ - 'name' => '', - 'age' => 10, - 'email' => 'thinkphp@qq.com', - 'sex' => '3', - ]; - $validate = new Validate($rule); - $validate->setTypeMsg('require', ':attribute必须'); - $validate->setTypeMsg(['require' => ':attribute必须']); - $result = $validate->batch()->check($data); - $this->assertFalse($result); - $this->assertEquals(['name' => '名称必须', 'sex' => '性别错误'], $validate->getError()); - } - -} diff --git a/tests/thinkphp/library/think/view/driver/.gitignore b/tests/thinkphp/library/think/view/driver/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/think/view/driver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/think/view/theme/index/template.html b/tests/thinkphp/library/think/view/theme/index/template.html deleted file mode 100644 index 5f4548be..00000000 --- a/tests/thinkphp/library/think/view/theme/index/template.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - Document - - - - - diff --git a/tests/thinkphp/library/think/viewTest.php b/tests/thinkphp/library/think/viewTest.php deleted file mode 100644 index 56804798..00000000 --- a/tests/thinkphp/library/think/viewTest.php +++ /dev/null @@ -1,76 +0,0 @@ - -// +---------------------------------------------------------------------- - -/** - * view测试 - * @author mahuan - */ - -namespace tests\thinkphp\library\think; - -class viewTest extends \PHPUnit_Framework_TestCase -{ - - /** - * 句柄测试 - * @return mixed - * @access public - */ - public function testGetInstance() - { - \think\Cookie::get('a'); - $view_instance = \think\View::instance(); - $this->assertInstanceOf('\think\view', $view_instance, 'instance方法返回错误'); - } - - /** - * 测试变量赋值 - * @return mixed - * @access public - */ - public function testAssign() - { - $view_instance = \think\View::instance(); - $view_instance->key = 'value'; - $this->assertTrue(isset($view_instance->key)); - $this->assertEquals('value', $view_instance->key); - $data = $view_instance->assign(array('key' => 'value')); - $data = $view_instance->assign('key2', 'value2'); - //测试私有属性 - $expect_data = array('key' => 'value', 'key2' => 'value2'); - $this->assertAttributeEquals($expect_data, 'data', $view_instance); - } - - /** - * 测试引擎设置 - * @return mixed - * @access public - */ - public function testEngine() - { - $view_instance = \think\View::instance(); - $data = $view_instance->engine('php'); - $data = $view_instance->engine(['type' => 'php', 'view_path' => '', 'view_suffix' => '.php', 'view_depr' => DS]); - $php_engine = new \think\view\driver\Php(['view_path' => '', 'view_suffix' => '.php', 'view_depr' => DS]); - $this->assertAttributeEquals($php_engine, 'engine', $view_instance); - //测试模板引擎驱动 - $data = $view_instance->engine(['type' => 'think', 'view_path' => '', 'view_suffix' => '.html', 'view_depr' => DS]); - $think_engine = new \think\view\driver\Think(['view_path' => '', 'view_suffix' => '.html', 'view_depr' => DS]); - $this->assertAttributeEquals($think_engine, 'engine', $view_instance); - } - - public function testReplace() - { - $view_instance = \think\View::instance(); - $view_instance->replace('string', 'replace')->display('string'); - } - -} diff --git a/tests/thinkphp/library/traits/controller/.gitignore b/tests/thinkphp/library/traits/controller/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/traits/controller/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/tests/thinkphp/library/traits/model/.gitignore b/tests/thinkphp/library/traits/model/.gitignore deleted file mode 100644 index a3a0c8b5..00000000 --- a/tests/thinkphp/library/traits/model/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file -- Gitee From 71523c22720fb551d105b0793009ae0b9140bc49 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 16 Feb 2017 11:11:29 +0800 Subject: [PATCH 0015/1384] =?UTF-8?q?=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 96a9dffc..00000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -sudo: false - -language: php - -services: - - memcached - - mongodb - - mysql - - postgresql - - redis-server - -matrix: - fast_finish: true - include: - - php: 5.4 - - php: 5.5 - - php: 5.6 - - php: 7.0 - - php: hhvm - allow_failures: - - php: hhvm - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - composer self-update - - mysql -e "create database IF NOT EXISTS test;" -uroot - - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres - - psql -c 'create database test;' -U postgres - -install: - - ./tests/script/install.sh - -script: - ## LINT - - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; - ## PHP_CodeSniffer - - vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 --standard=PSR2 --ignore="vendor/*" ./ - ## PHP Copy/Paste Detector - - vendor/bin/phpcpd --verbose --exclude vendor ./ || true - ## PHPLOC - - vendor/bin/phploc --exclude vendor ./ - ## PHPUNIT - - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml - -after_success: - - bash <(curl -s https://codecov.io/bash) -- Gitee From 9e25db2f226a275f1eb0766967086a0e0fc71a9e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 16 Feb 2017 18:07:16 +0800 Subject: [PATCH 0016/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Route=E7=B1=BBparse?= =?UTF-8?q?UrlPath=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 679a1372..6954de68 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -1277,9 +1277,6 @@ class Route } elseif (strpos($url, '/')) { // [模块/控制器/操作] $path = explode('/', $url); - } elseif (false !== strpos($url, '=')) { - // 参数1=值1&参数2=值2... - parse_str($url, $var); } else { $path = [$url]; } -- Gitee From b1f24997a66ccdb115d3e7e74cd92461a4a0b5ea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 17 Feb 2017 14:58:53 +0800 Subject: [PATCH 0017/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Barray=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E8=BD=AC=E6=8D=A2=E7=9A=84=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 9e38d8fc..f789dfd8 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -514,7 +514,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = json_decode($value, true); break; case 'array': - $value = is_null($value) ? [] : json_decode($value, true); + $value = empty($value) ? [] : json_decode($value, true); break; case 'object': $value = empty($value) ? new \stdClass() : json_decode($value); -- Gitee From 35d6ad4f5042210dc19d38154346a49b5683c37e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 20 Feb 2017 18:39:48 +0800 Subject: [PATCH 0018/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- library/think/model/relation/OneToOne.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index fe610f5e..1c8b0f83 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2209,7 +2209,7 @@ class Query // 执行操作 $result = '' == $sql ? 0 : $this->execute($sql, $bind); if ($result) { - if (isset($where[$pk])) { + if (is_string($pk) && isset($where[$pk])) { $data[$pk] = $where[$pk]; } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { list($a, $val) = explode('|', $key); diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 18b92225..f23a0851 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -82,12 +82,14 @@ abstract class OneToOne extends Relation if ($closure) { // 执行闭包查询 - call_user_func_array($closure, [& $query]); + call_user_func_array($closure, [ & $query]); // 使用withField指定获取关联的字段,如 // $query->where(['id'=>1])->withField('id,name'); if ($query->getOptions('with_field')) { $field = $query->getOptions('with_field'); $query->removeOption('with_field'); + } else { + $field = true; } } elseif (isset($this->option['field'])) { $field = $this->option['field']; @@ -290,7 +292,7 @@ abstract class OneToOne extends Relation { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [& $model]); + call_user_func_array($closure, [ & $model]); if ($field = $model->getOptions('with_field')) { $model->field($field)->removeOption('with_field'); } -- Gitee From 7a7c4fb38cdbd5cc3231ec7fc276c11cd930b8c4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 22 Feb 2017 11:26:20 +0800 Subject: [PATCH 0019/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BReuquest=E7=B1=BBco?= =?UTF-8?q?ntentType=E6=96=B9=E6=B3=95=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 9c8ae276..dafaf72f 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1357,7 +1357,11 @@ class Request { $contentType = $this->server('CONTENT_TYPE'); if ($contentType) { - list($type) = explode(';', $contentType); + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } return trim($type); } return ''; -- Gitee From b80b2cc182102b66c58187650c9bcc7dcd8fa6f1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 23 Feb 2017 16:49:04 +0800 Subject: [PATCH 0020/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BLoader=E7=B1=BB?= =?UTF-8?q?=E7=9A=84model/controller/validate=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index 70de3175..a0727fef 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -369,8 +369,9 @@ class Loader if (isset(self::$instance[$guid])) { return self::$instance[$guid]; } - if (strpos($name, '\\')) { - $class = $name; + if (false !== strpos($name, '\\')) { + $class = $name; + $module = Request::instance()->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name, 2); @@ -404,8 +405,9 @@ class Loader */ public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') { - if (strpos($name, '\\')) { - $class = $name; + if (false !== strpos($name, '\\')) { + $class = $name; + $module = Request::instance()->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name); @@ -440,8 +442,9 @@ class Loader if (isset(self::$instance[$guid])) { return self::$instance[$guid]; } - if (strpos($name, '\\')) { - $class = $name; + if (false !== strpos($name, '\\')) { + $class = $name; + $module = Request::instance()->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name); -- Gitee From 07c09b8092ace677964bc3da8b5fc7a61231819d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 23 Feb 2017 18:33:10 +0800 Subject: [PATCH 0021/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BBwith?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- library/think/model/relation/MorphTo.php | 10 ++++++++++ library/think/model/relation/OneToOne.php | 1 - 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1c8b0f83..02092bb4 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1894,7 +1894,7 @@ class Query $relation = Loader::parseName($relation, 1, false); $model = $class->$relation(); if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { - $model->eagerly($this, $relation, $subRelation, $closure, $first); + $model->removeOption()->eagerly($this, $relation, $subRelation, $closure, $first); $first = false; } elseif ($closure) { $with[$key] = $closure; diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 04df0e79..371d2521 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -114,6 +114,16 @@ class MorphTo extends Relation return $this; } + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + /** * 预载入关联查询 * @access public diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index f23a0851..fb6b575b 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -197,7 +197,6 @@ abstract class OneToOne extends Relation */ public function getEagerlyType() { - $this->removeOption(); return $this->eagerlyType; } -- Gitee From 8e1f3b1e2420dce0899a06c5ea70a7c95f5d76d6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 25 Feb 2017 15:51:01 +0800 Subject: [PATCH 0022/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=B1=BB=E7=9A=84?= =?UTF-8?q?=E9=9D=99=E6=80=81=E4=BB=A3=E7=90=86=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 548 +------ library/think/Cache.php | 216 +-- library/think/Config.php | 155 +- library/think/Cookie.php | 196 +-- library/think/Db.php | 52 +- library/think/Debug.php | 3 +- library/think/Facade.php | 57 + library/think/Hook.php | 4 +- library/think/Lang.php | 202 +-- library/think/Loader.php | 6 +- library/think/Log.php | 4 +- library/think/Request.php | 1597 +------------------- library/think/Response.php | 313 +--- library/think/Route.php | 1574 +------------------- library/think/Session.php | 350 +---- library/think/cache/Driver.php | 16 +- library/think/cache/driver/File.php | 4 + library/think/cache/driver/Lite.php | 4 + library/think/cache/driver/Memcache.php | 6 + library/think/cache/driver/Memcached.php | 6 + library/think/cache/driver/Redis.php | 6 + library/think/cache/driver/Sqlite.php | 4 + library/think/cache/driver/Wincache.php | 7 + library/think/cache/driver/Xcache.php | 6 + library/think/debug/Html.php | 8 +- library/think/exception/Handle.php | 10 +- library/think/log/driver/File.php | 4 +- library/think/log/driver/Socket.php | 2 +- library/think/manager/AppManager.php | 601 ++++++++ library/think/manager/CacheManager.php | 99 ++ library/think/manager/ConfigManager.php | 170 +++ library/think/manager/CookieManager.php | 213 +++ library/think/manager/LangManager.php | 221 +++ library/think/manager/RequestManager.php | 1604 ++++++++++++++++++++ library/think/manager/ResponseManager.php | 333 +++++ library/think/manager/RouteManager.php | 1608 +++++++++++++++++++++ library/think/manager/SessionManager.php | 368 +++++ library/think/manager/UrlManager.php | 327 +++++ library/think/view/driver/Php.php | 4 +- library/think/view/driver/Think.php | 4 +- tpl/think_exception.tpl | 4 +- 41 files changed, 5713 insertions(+), 5203 deletions(-) create mode 100644 library/think/Facade.php create mode 100644 library/think/manager/AppManager.php create mode 100644 library/think/manager/CacheManager.php create mode 100644 library/think/manager/ConfigManager.php create mode 100644 library/think/manager/CookieManager.php create mode 100644 library/think/manager/LangManager.php create mode 100644 library/think/manager/RequestManager.php create mode 100644 library/think/manager/ResponseManager.php create mode 100644 library/think/manager/RouteManager.php create mode 100644 library/think/manager/SessionManager.php create mode 100644 library/think/manager/UrlManager.php diff --git a/library/think/App.php b/library/think/App.php index 963d8b35..d750ce51 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -11,557 +11,15 @@ namespace think; -use think\exception\HttpException; -use think\exception\HttpResponseException; -use think\exception\RouteNotFoundException; - /** * App 应用管理 * @author liu21st */ -class App +class App extends Facade { - /** - * @var bool 是否初始化过 - */ - protected static $init = false; - - /** - * @var string 当前模块路径 - */ - public static $modulePath; - - /** - * @var bool 应用调试模式 - */ - public static $debug = true; - - /** - * @var string 应用类库命名空间 - */ - public static $namespace = 'app'; - - /** - * @var bool 应用类库后缀 - */ - public static $suffix = false; - - /** - * @var bool 应用路由检测 - */ - protected static $routeCheck; - - /** - * @var bool 严格路由检测 - */ - protected static $routeMust; - - protected static $dispatch; - protected static $file = []; - - /** - * 执行应用程序 - * @access public - * @param Request $request Request对象 - * @return Response - * @throws Exception - */ - public static function run(Request $request = null) - { - is_null($request) && $request = Request::instance(); - - try { - $config = self::initCommon(); - if (defined('BIND_MODULE')) { - // 模块/控制器绑定 - BIND_MODULE && Route::bind(BIND_MODULE); - } elseif ($config['auto_bind_module']) { - // 入口自动绑定 - $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); - if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { - Route::bind($name); - } - } - - $request->filter($config['default_filter']); - - if ($config['lang_switch_on']) { - // 开启多语言机制 检测当前语言 - Lang::detect(); - } else { - // 读取默认语言 - Lang::range($config['default_lang']); - } - $request->langset(Lang::range()); - // 加载系统语言包 - Lang::load([ - THINK_PATH . 'lang' . DS . $request->langset() . EXT, - APP_PATH . 'lang' . DS . $request->langset() . EXT, - ]); - - // 获取应用调度信息 - $dispatch = self::$dispatch; - if (empty($dispatch)) { - // 进行URL路由检测 - $dispatch = self::routeCheck($request, $config); - } - // 记录当前调度信息 - $request->dispatch($dispatch); - - // 记录路由和请求信息 - if (self::$debug) { - Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); - Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); - Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); - } - - // 监听app_begin - Hook::listen('app_begin', $dispatch); - // 请求缓存检查 - $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); - - switch ($dispatch['type']) { - case 'redirect': - // 执行重定向跳转 - $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']); - break; - case 'module': - // 模块/控制器/操作 - $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null); - break; - case 'controller': - // 执行控制器操作 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']); - break; - case 'method': - // 执行回调方法 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = self::invokeMethod($dispatch['method'], $vars); - break; - case 'function': - // 执行闭包 - $data = self::invokeFunction($dispatch['function']); - break; - case 'response': - $data = $dispatch['response']; - break; - default: - throw new \InvalidArgumentException('dispatch type not support'); - } - } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); - } - - // 清空类的实例化 - Loader::clearInstance(); - - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $isAjax = $request->isAjax(); - $type = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type'); - $response = Response::create($data, $type); - } else { - $response = Response::create(); - } - - // 监听app_end - Hook::listen('app_end', $response); - - return $response; - } - - /** - * 设置当前请求的调度信息 - * @access public - * @param array|string $dispatch 调度信息 - * @param string $type 调度类型 - * @return void - */ - public static function dispatch($dispatch, $type = 'module') - { - self::$dispatch = ['type' => $type, $type => $dispatch]; - } - - /** - * 执行函数或者闭包方法 支持参数调用 - * @access public - * @param string|array|\Closure $function 函数或者闭包 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeFunction($function, $vars = []) - { - $reflect = new \ReflectionFunction($function); - $args = self::bindParams($reflect, $vars); - // 记录执行信息 - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); - return $reflect->invokeArgs($args); - } - - /** - * 调用反射执行类的方法 支持参数绑定 - * @access public - * @param string|array $method 方法 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeMethod($method, $vars = []) - { - if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); - $reflect = new \ReflectionMethod($class, $method[1]); - } else { - // 静态方法 - $reflect = new \ReflectionMethod($method); - } - $args = self::bindParams($reflect, $vars); - - self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); - return $reflect->invokeArgs(isset($class) ? $class : null, $args); - } - - /** - * 调用反射执行类的实例化 支持依赖注入 - * @access public - * @param string $class 类名 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeClass($class, $vars = []) - { - $reflect = new \ReflectionClass($class); - $constructor = $reflect->getConstructor(); - if ($constructor) { - $args = self::bindParams($constructor, $vars); - } else { - $args = []; - } - return $reflect->newInstanceArgs($args); - } - - /** - * 绑定参数 - * @access public - * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 - * @return array - */ - private static function bindParams($reflect, $vars = []) - { - if (empty($vars)) { - // 自动获取请求变量 - if (Config::get('url_param_type')) { - $vars = Request::instance()->route(); - } else { - $vars = Request::instance()->param(); - } - } - $args = []; - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; - if ($reflect->getNumberOfParameters() > 0) { - $params = $reflect->getParameters(); - foreach ($params as $param) { - $name = $param->getName(); - $class = $param->getClass(); - if ($class) { - $className = $class->getName(); - $bind = Request::instance()->$name; - if ($bind instanceof $className) { - $args[] = $bind; - } else { - if (method_exists($className, 'invoke')) { - $method = new \ReflectionMethod($className, 'invoke'); - if ($method->isPublic() && $method->isStatic()) { - $args[] = $className::invoke(Request::instance()); - continue; - } - } - $args[] = method_exists($className, 'instance') ? $className::instance() : new $className; - } - } elseif (1 == $type && !empty($vars)) { - $args[] = array_shift($vars); - } elseif (0 == $type && isset($vars[$name])) { - $args[] = $vars[$name]; - } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); - } else { - throw new \InvalidArgumentException('method param miss:' . $name); - } - } - } - return $args; - } - - /** - * 执行模块 - * @access public - * @param array $result 模块/控制器/操作 - * @param array $config 配置参数 - * @param bool $convert 是否自动转换控制器和操作名 - * @return mixed - */ - public static function module($result, $config, $convert = null) - { - if (is_string($result)) { - $result = explode('/', $result); - } - $request = Request::instance(); - if ($config['app_multi_module']) { - // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); - $bind = Route::getBind('module'); - $available = false; - if ($bind) { - // 绑定模块 - list($bindModule) = explode('/', $bind); - if (empty($result[0])) { - $module = $bindModule; - $available = true; - } elseif ($module == $bindModule) { - $available = true; - } - } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { - $available = true; - } - - // 模块初始化 - if ($module && $available) { - // 初始化模块 - $request->module($module); - $config = self::init($module); - // 模块请求缓存检查 - $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); - } else { - throw new HttpException(404, 'module not exists:' . $module); - } - } else { - // 单一模块部署 - $module = ''; - $request->module($module); - } - // 当前模块路径 - App::$modulePath = APP_PATH . ($module ? $module . DS : ''); - - // 是否自动转换控制器和操作名 - $convert = is_bool($convert) ? $convert : $config['url_convert']; - // 获取控制器名 - $controller = strip_tags($result[1] ?: $config['default_controller']); - $controller = $convert ? strtolower($controller) : $controller; - - // 获取操作名 - $actionName = strip_tags($result[2] ?: $config['default_action']); - $actionName = $convert ? strtolower($actionName) : $actionName; - - // 设置当前请求的控制器、操作 - $request->controller(Loader::parseName($controller, 1))->action($actionName); - - // 监听module_init - Hook::listen('module_init', $request); - - $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']); - if (is_null($instance)) { - throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); - } - // 获取当前操作名 - $action = $actionName . $config['action_suffix']; - - $vars = []; - if (is_callable([$instance, $action])) { - // 执行操作方法 - $call = [$instance, $action]; - } elseif (is_callable([$instance, '_empty'])) { - // 空操作 - $call = [$instance, '_empty']; - $vars = [$actionName]; - } else { - // 操作不存在 - throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); - } - - Hook::listen('action_begin', $call); - - return self::invokeMethod($call, $vars); - } - - /** - * 初始化应用 - */ - public static function initCommon() - { - if (empty(self::$init)) { - // 初始化应用 - $config = self::init(); - self::$suffix = $config['class_suffix']; - - // 应用调试模式 - self::$debug = Env::get('app_debug', Config::get('app_debug')); - if (!self::$debug) { - ini_set('display_errors', 'Off'); - } elseif (!IS_CLI) { - //重新申请一块比较大的buffer - if (ob_get_level() > 0) { - $output = ob_get_clean(); - } - ob_start(); - if (!empty($output)) { - echo $output; - } - } - - // 注册应用命名空间 - self::$namespace = $config['app_namespace']; - Loader::addNamespace($config['app_namespace'], APP_PATH); - if (!empty($config['root_namespace'])) { - Loader::addNamespace($config['root_namespace']); - } - - // 加载额外文件 - if (!empty($config['extra_file_list'])) { - foreach ($config['extra_file_list'] as $file) { - $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; - if (is_file($file) && !isset(self::$file[$file])) { - include $file; - self::$file[$file] = true; - } - } - } - - // 设置系统时区 - date_default_timezone_set($config['default_timezone']); - - // 监听app_init - Hook::listen('app_init'); - - self::$init = true; - } - return Config::get(); - } - - /** - * 初始化应用或模块 - * @access public - * @param string $module 模块名 - * @return array - */ - private static function init($module = '') - { - // 定位模块目录 - $module = $module ? $module . DS : ''; - - // 加载初始化文件 - if (is_file(APP_PATH . $module . 'init' . EXT)) { - include APP_PATH . $module . 'init' . EXT; - } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { - include RUNTIME_PATH . $module . 'init' . EXT; - } else { - $path = APP_PATH . $module; - // 加载模块配置 - $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - Config::load($filename, 'database'); - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if (strpos($file, CONF_EXT)) { - $filename = $dir . DS . $file; - Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - - // 加载应用状态配置 - if ($config['app_status']) { - $config = Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - - // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - Hook::import(include CONF_PATH . $module . 'tags' . EXT); - } - - // 加载公共文件 - if (is_file($path . 'common' . EXT)) { - include $path . 'common' . EXT; - } - - // 加载当前模块语言包 - if ($module) { - Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); - } - } - return Config::get(); - } - - /** - * URL路由检测(根据PATH_INFO) - * @access public - * @param \think\Request $request - * @param array $config - * @return array - * @throws \think\Exception - */ - public static function routeCheck($request, array $config) - { - $path = $request->path(); - $depr = $config['pathinfo_depr']; - $result = false; - // 路由检测 - $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; - if ($check) { - // 开启路由 - if (is_file(RUNTIME_PATH . 'route.php')) { - // 读取路由缓存 - $rules = include RUNTIME_PATH . 'route.php'; - if (is_array($rules)) { - Route::rules($rules); - } - } else { - $files = $config['route_config_file']; - foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - // 导入路由配置 - $rules = include CONF_PATH . $file . CONF_EXT; - if (is_array($rules)) { - Route::import($rules); - } - } - } - } - - // 路由检测(根据路由定义返回不同的URL调度) - $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); - $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; - if ($must && false === $result) { - // 路由无效 - throw new RouteNotFoundException(); - } - } - if (false === $result) { - // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 - $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); - } - return $result; - } - /** - * 设置应用的路由检测机制 - * @access public - * @param bool $route 是否需要检测路由 - * @param bool $must 是否强制检测路由 - * @return void - */ - public static function route($route, $must = false) + public static function version() { - self::$routeCheck = $route; - self::$routeMust = $must; + return '5.1.0alpha'; } } diff --git a/library/think/Cache.php b/library/think/Cache.php index 9e4b30ef..2eda8151 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -11,221 +11,9 @@ namespace think; -use think\cache\Driver; +use think\Facade; -class Cache +class Cache extends Facade { - protected static $instance = []; - public static $readTimes = 0; - public static $writeTimes = 0; - - /** - * 操作句柄 - * @var object - * @access protected - */ - protected static $handler; - - /** - * 连接缓存 - * @access public - * @param array $options 配置数组 - * @param bool|string $name 缓存连接标识 true 强制重新连接 - * @return Driver - */ - public static 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, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type); - - // 记录初始化信息 - App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); - if (true === $name) { - return new $class($options); - } else { - self::$instance[$name] = new $class($options); - } - } - self::$handler = self::$instance[$name]; - return self::$handler; - } - - /** - * 自动初始化缓存 - * @access public - * @param array $options 配置数组 - * @return void - */ - public static function init(array $options = []) - { - if (is_null(self::$handler)) { - // 自动初始化缓存 - if (!empty($options)) { - self::connect($options); - } elseif ('complex' == Config::get('cache.type')) { - self::connect(Config::get('cache.default')); - } else { - self::connect(Config::get('cache')); - } - } - } - - /** - * 切换缓存类型 需要配置 cache.type 为 complex - * @access public - * @param string $name 缓存标识 - * @return Driver - */ - public static function store($name = '') - { - if ('' !== $name && 'complex' == Config::get('cache.type')) { - self::connect(Config::get('cache.' . $name), strtolower($name)); - } - return self::$handler; - } - - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public static function has($name) - { - self::init(); - self::$readTimes++; - return self::$handler->has($name); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $default 默认值 - * @return mixed - */ - public static function get($name, $default = false) - { - self::init(); - self::$readTimes++; - return self::$handler->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::init(); - self::$writeTimes++; - return self::$handler->set($name, $value, $expire); - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function inc($name, $step = 1) - { - self::init(); - self::$writeTimes++; - return self::$handler->inc($name, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function dec($name, $step = 1) - { - self::init(); - self::$writeTimes++; - return self::$handler->dec($name, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存标识 - * @return boolean - */ - public static function rm($name) - { - self::init(); - self::$writeTimes++; - return self::$handler->rm($name); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public static function clear($tag = null) - { - self::init(); - self::$writeTimes++; - return self::$handler->clear($tag); - } - - /** - * 读取缓存并删除 - * @access public - * @param string $name 缓存变量名 - * @return mixed - */ - public static function pull($name) - { - self::init(); - self::$readTimes++; - self::$writeTimes++; - return self::$handler->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::init(); - self::$readTimes++; - return self::$handler->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) - { - self::init(); - return self::$handler->tag($name, $keys, $overlay); - } } diff --git a/library/think/Config.php b/library/think/Config.php index fc6c50ac..f2c5f0cb 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -11,160 +11,7 @@ namespace think; -class Config +class Config extends Facade { - // 配置参数 - private static $config = []; - // 参数作用域 - private static $range = '_sys_'; - // 设定配置参数的作用域 - public static function range($range) - { - self::$range = $range; - if (!isset(self::$config[$range])) { - self::$config[$range] = []; - } - } - - /** - * 解析配置文件或内容 - * @param string $config 配置文件路径或内容 - * @param string $type 配置解析类型 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 - * @return mixed - */ - public static function parse($config, $type = '', $name = '', $range = '') - { - $range = $range ?: self::$range; - if (empty($type)) { - $type = pathinfo($config, PATHINFO_EXTENSION); - } - $class = false !== strpos($type, '\\') ? $type : '\\think\\config\\driver\\' . ucwords($type); - return self::set((new $class())->parse($config), $name, $range); - } - - /** - * 加载配置文件(PHP格式) - * @param string $file 配置文件名 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 - * @return mixed - */ - public static function load($file, $name = '', $range = '') - { - $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); - } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { - return self::set(yaml_parse_file($file), $name, $range); - } else { - return self::parse($file, $type, $name, $range); - } - } else { - return self::$config[$range]; - } - } - - /** - * 检测配置是否存在 - * @param string $name 配置参数名(支持二级配置 .号分割) - * @param string $range 作用域 - * @return bool - */ - public static function has($name, $range = '') - { - $range = $range ?: self::$range; - - if (!strpos($name, '.')) { - return isset(self::$config[$range][strtolower($name)]); - } else { - // 二维数组设置和获取支持 - $name = explode('.', $name); - return isset(self::$config[$range][strtolower($name[0])][$name[1]]); - } - } - - /** - * 获取配置参数 为空则获取所有配置 - * @param string $name 配置参数名(支持二级配置 .号分割) - * @param string $range 作用域 - * @return mixed - */ - public static function get($name = null, $range = '') - { - $range = $range ?: self::$range; - // 无参数时获取所有 - if (empty($name) && isset(self::$config[$range])) { - return self::$config[$range]; - } - - if (!strpos($name, '.')) { - $name = strtolower($name); - return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; - } else { - // 二维数组设置和获取支持 - $name = explode('.', $name); - $name[0] = strtolower($name[0]); - return isset(self::$config[$range][$name[0]][$name[1]]) ? self::$config[$range][$name[0]][$name[1]] : null; - } - } - - /** - * 设置配置参数 name为数组则为批量设置 - * @param string|array $name 配置参数名(支持二级配置 .号分割) - * @param mixed $value 配置值 - * @param string $range 作用域 - * @return mixed - */ - public static function set($name, $value = null, $range = '') - { - $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; - } else { - // 二维数组设置和获取支持 - $name = explode('.', $name); - self::$config[$range][strtolower($name[0])][$name[1]] = $value; - } - return; - } elseif (is_array($name)) { - // 批量设置 - if (!empty($value)) { - self::$config[$range][$value] = isset(self::$config[$range][$value]) ? - array_merge(self::$config[$range][$value], $name) : - self::$config[$range][$value] = $name; - return self::$config[$range][$value]; - } else { - return self::$config[$range] = array_merge(self::$config[$range], array_change_key_case($name)); - } - } else { - // 为空直接返回 已有配置 - return self::$config[$range]; - } - } - - /** - * 重置配置参数 - */ - public static function reset($range = '') - { - $range = $range ?: self::$range; - if (true === $range) { - self::$config = []; - } else { - self::$config[$range] = []; - } - } } diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 93edb8d7..656b9518 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -11,201 +11,7 @@ namespace think; -class Cookie +class Cookie extends Facade { - protected static $config = [ - // cookie 名称前缀 - 'prefix' => '', - // cookie 保存时间 - 'expire' => 0, - // cookie 保存路径 - 'path' => '/', - // cookie 有效域名 - 'domain' => '', - // cookie 启用安全传输 - 'secure' => false, - // httponly设置 - 'httponly' => '', - // 是否使用 setcookie - 'setcookie' => true, - ]; - - protected static $init; - - /** - * Cookie初始化 - * @param array $config - * @return void - */ - public static function init(array $config = []) - { - if (empty($config)) { - $config = Config::get('cookie'); - } - self::$config = array_merge(self::$config, array_change_key_case($config)); - if (!empty(self::$config['httponly'])) { - ini_set('session.cookie_httponly', 1); - } - self::$init = true; - } - - /** - * 设置或者获取cookie作用域(前缀) - * @param string $prefix - * @return string|void - */ - public static function prefix($prefix = '') - { - if (empty($prefix)) { - return self::$config['prefix']; - } - self::$config['prefix'] = $prefix; - } - - /** - * Cookie 设置、获取、删除 - * - * @param string $name cookie名称 - * @param mixed $value cookie值 - * @param mixed $option 可选参数 可能会是 null|integer|string - * - * @return mixed - * @internal param mixed $options cookie参数 - */ - public static function set($name, $value = '', $option = null) - { - !isset(self::$init) && self::init(); - // 参数设置(会覆盖黙认设置) - if (!is_null($option)) { - if (is_numeric($option)) { - $option = ['expire' => $option]; - } elseif (is_string($option)) { - parse_str($option, $option); - } - $config = array_merge(self::$config, array_change_key_case($option)); - } else { - $config = self::$config; - } - $name = $config['prefix'] . $name; - // 设置cookie - if (is_array($value)) { - array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); - $value = 'think:' . json_encode($value); - } - $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']); - } - $_COOKIE[$name] = $value; - } - - /** - * 永久保存Cookie数据 - * @param string $name cookie名称 - * @param mixed $value cookie值 - * @param mixed $option 可选参数 可能会是 null|integer|string - * @return void - */ - public static function forever($name, $value = '', $option = null) - { - if (is_null($option) || is_numeric($option)) { - $option = []; - } - $option['expire'] = 315360000; - self::set($name, $value, $option); - } - - /** - * 判断Cookie数据 - * @param string $name cookie名称 - * @param string|null $prefix cookie前缀 - * @return bool - */ - public static function has($name, $prefix = null) - { - !isset(self::$init) && self::init(); - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; - $name = $prefix . $name; - return isset($_COOKIE[$name]); - } - - /** - * Cookie获取 - * @param string $name cookie名称 - * @param string|null $prefix cookie前缀 - * @return mixed - */ - public static function get($name, $prefix = null) - { - !isset(self::$init) && self::init(); - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; - $name = $prefix . $name; - if (isset($_COOKIE[$name])) { - $value = $_COOKIE[$name]; - if (0 === strpos($value, 'think:')) { - $value = substr($value, 6); - $value = json_decode($value, true); - array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); - } - return $value; - } else { - return; - } - } - - /** - * Cookie删除 - * @param string $name cookie名称 - * @param string|null $prefix cookie前缀 - * @return mixed - */ - public static function delete($name, $prefix = null) - { - !isset(self::$init) && self::init(); - $config = self::$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']); - } - // 删除指定cookie - unset($_COOKIE[$name]); - } - - /** - * Cookie清空 - * @param string|null $prefix cookie前缀 - * @return mixed - */ - public static function clear($prefix = null) - { - // 清除指定前缀的所有cookie - if (empty($_COOKIE)) { - return; - } - !isset(self::$init) && self::init(); - // 要删除的cookie前缀,不指定则删除config设置的指定前缀 - $config = self::$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']); - } - unset($_COOKIE[$key]); - } - } - } - return; - } - - private static function jsonFormatProtect(&$val, $key, $type = 'encode') - { - if (!empty($val) && true !== $val) { - $val = 'decode' == $type ? urldecode($val) : urlencode($val); - } - } } diff --git a/library/think/Db.php b/library/think/Db.php index 00f719e0..758a5661 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -17,43 +17,27 @@ 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 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 插入一条记录 - * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID - * @method integer insertAll(array $dataSet) static 插入多条记录 - * @method integer update(array $data) static 更新记录 - * @method integer delete(mixed $data = null) static 删除记录 - * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 - * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = null) 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 mixed transaction(callable $callback) static 执行数据库事务 - * @method void startTrans() static 启动事务 - * @method void commit() static 用于非自动提交状态下面的查询提交 - * @method void rollback() static 事务回滚 - * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @mixin Query */ -class Db +class Db extends Facade { - // 数据库连接实例 - private static $instance = []; // 查询次数 public static $queryTimes = 0; // 执行次数 public static $executeTimes = 0; + /** + * 创建对象实例 + * @static + * @access protected + * @param mixed $config 连接配置 + * @return object + */ + protected static function createFacade() + { + return self::connect(); + } + /** * 数据库初始化 并取得数据库类实例 * @static @@ -76,7 +60,7 @@ class Db } $class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']); // 记录初始化信息 - if (App::$debug) { + if (App::isDebug()) { Log::record('[ DB ] INIT ' . $options['type'], 'info'); } if (true === $name) { @@ -142,10 +126,4 @@ class Db return $dsn; } - // 调用驱动类的方法 - public static function __callStatic($method, $params) - { - // 自动初始化数据库 - return call_user_func_array([self::connect(), $method], $params); - } } diff --git a/library/think/Debug.php b/library/think/Debug.php index 02a07ed2..3fbfe945 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -12,6 +12,7 @@ namespace think; use think\exception\ClassNotFoundException; +use think\manager\ResponseManager; use think\response\Redirect; class Debug @@ -184,7 +185,7 @@ class Debug } } - public static function inject(Response $response, &$content) + public static function inject(ResponseManager $response, &$content) { $config = Config::get('trace'); $type = isset($config['type']) ? $config['type'] : 'Html'; diff --git a/library/think/Facade.php b/library/think/Facade.php new file mode 100644 index 00000000..142356e4 --- /dev/null +++ b/library/think/Facade.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Facade +{ + // 对象实例 + protected static $instance = []; + + /** + * 创建对象实例 + * @static + * @access protected + * @param mixed $config 连接配置 + * @return object + */ + protected static function createFacade() + { + $name = static::class; + + if (!isset(self::$instance[$name])) { + $class = static::getFacadeClass() ?: '\\think\\manager\\' . basename(str_replace('\\', '/', $name) . 'Manager'); + + self::$instance[$name] = new $class(); + } + return self::$instance[$name]; + } + + protected static function getFacadeClass() + { + } + + /** + * 初始化 + * @access public + * @return object + */ + public static function instance() + { + return self::createFacade(); + } + + // 调用驱动类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([self::createFacade(), $method], $params); + } +} diff --git a/library/think/Hook.php b/library/think/Hook.php index f06196e4..c2fd2f1f 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -106,7 +106,7 @@ class Hook */ public static function exec($class, $tag = '', &$params = null, $extra = null) { - App::$debug && Debug::remark('behavior_start', 'time'); + App::isDebug() && Debug::remark('behavior_start', 'time'); $method = Loader::parseName($tag, 1, false); if ($class instanceof \Closure) { $result = call_user_func_array($class, [ & $params, $extra]); @@ -126,7 +126,7 @@ class Hook $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; $result = $obj->$method($params, $extra); } - if (App::$debug) { + if (App::isDebug()) { Debug::remark('behavior_end', 'time'); Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); } diff --git a/library/think/Lang.php b/library/think/Lang.php index d52f1947..7ece68f1 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -11,207 +11,7 @@ namespace think; -class Lang +class Lang extends Facade { - // 语言数据 - private static $lang = []; - // 语言作用域 - private static $range = 'zh-cn'; - // 语言自动侦测的变量 - protected static $langDetectVar = 'lang'; - // 语言Cookie变量 - protected static $langCookieVar = 'think_var'; - // 语言Cookie的过期时间 - protected static $langCookieExpire = 3600; - // 允许语言列表 - protected static $allowLangList = []; - // 设定当前的语言 - public static function range($range = '') - { - if ('' == $range) { - return self::$range; - } else { - self::$range = $range; - } - } - - /** - * 设置语言定义(不区分大小写) - * @param string|array $name 语言变量 - * @param string $value 语言值 - * @param string $range 语言作用域 - * @return mixed - */ - public static function set($name, $value = null, $range = '') - { - $range = $range ?: self::$range; - // 批量定义 - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; - } - if (is_array($name)) { - return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; - } else { - return self::$lang[$range][strtolower($name)] = $value; - } - } - - /** - * 加载语言定义(不区分大小写) - * @param string $file 语言文件 - * @param string $range 语言作用域 - * @return mixed - */ - public static function load($file, $range = '') - { - $range = $range ?: self::$range; - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; - } - // 批量定义 - if (is_string($file)) { - $file = [$file]; - } - $lang = []; - foreach ($file as $_file) { - if (is_file($_file)) { - // 记录加载信息 - App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); - $_lang = include $_file; - if (is_array($_lang)) { - $lang = array_change_key_case($_lang) + $lang; - } - } - } - if (!empty($lang)) { - self::$lang[$range] = $lang + self::$lang[$range]; - } - return self::$lang[$range]; - } - - /** - * 获取语言定义(不区分大小写) - * @param string|null $name 语言变量 - * @param array $vars 变量替换 - * @param string $range 语言作用域 - * @return mixed - */ - public static function has($name, $range = '') - { - $range = $range ?: self::$range; - return isset(self::$lang[$range][strtolower($name)]); - } - - /** - * 获取语言定义(不区分大小写) - * @param string|null $name 语言变量 - * @param array $vars 变量替换 - * @param string $range 语言作用域 - * @return mixed - */ - public static function get($name = null, $vars = [], $range = '') - { - $range = $range ?: self::$range; - // 空参数返回所有定义 - if (empty($name)) { - return self::$lang[$range]; - } - $key = strtolower($name); - $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name; - - // 变量解析 - if (!empty($vars) && is_array($vars)) { - /** - * Notes: - * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 - * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 - */ - if (key($vars) === 0) { - // 数字索引解析 - array_unshift($vars, $value); - $value = call_user_func_array('sprintf', $vars); - } else { - // 关联索引解析 - $replace = array_keys($vars); - foreach ($replace as &$v) { - $v = "{:{$v}}"; - } - $value = str_replace($replace, $vars, $value); - } - - } - return $value; - } - - /** - * 自动侦测设置获取语言选择 - * @return string - */ - public static function detect() - { - // 自动侦测设置获取语言选择 - $langSet = ''; - if (isset($_GET[self::$langDetectVar])) { - // url中设置了语言变量 - $langSet = strtolower($_GET[self::$langDetectVar]); - Cookie::set(self::$langCookieVar, $langSet, self::$langCookieExpire); - } elseif (Cookie::get(self::$langCookieVar)) { - // 获取上次用户的选择 - $langSet = strtolower(Cookie::get(self::$langCookieVar)); - } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - // 自动侦测浏览器语言 - preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); - $langSet = strtolower($matches[1]); - Cookie::set(self::$langCookieVar, $langSet, self::$langCookieExpire); - } - if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { - // 合法的语言 - self::$range = $langSet ?: self::$range; - } - if ('zh-hans-cn' == self::$range) { - self::$range = 'zh-cn'; - } - return self::$range; - } - - /** - * 设置语言自动侦测的变量 - * @param string $var 变量名称 - * @return void - */ - public static function setLangDetectVar($var) - { - self::$langDetectVar = $var; - } - - /** - * 设置语言的cookie保存变量 - * @param string $var 变量名称 - * @return void - */ - public static function setLangCookieVar($var) - { - self::$langCookieVar = $var; - } - - /** - * 设置语言的cookie的过期时间 - * @param string $expire 过期时间 - * @return void - */ - public static function setLangCookieExpire($expire) - { - self::$langCookieExpire = $expire; - } - - /** - * 设置允许的语言列表 - * @param array $list 语言列表 - * @return void - */ - public static function setAllowLangList($list) - { - self::$allowLangList = $list; - } } diff --git a/library/think/Loader.php b/library/think/Loader.php index a0727fef..b5a783ab 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -320,7 +320,7 @@ class Loader $baseUrl = self::$prefixDirsPsr4[$name . '\\']; } elseif ('@' == $name) { //加载当前模块应用类库 - $baseUrl = App::$modulePath; + $baseUrl = App::getModulePath(); } elseif (is_dir(EXTEND_PATH . $name)) { $baseUrl = EXTEND_PATH . $name . DS; } else { @@ -536,9 +536,9 @@ class Loader { $name = str_replace(['/', '.'], '\\', $name); $array = explode('\\', $name); - $class = self::parseName(array_pop($array), 1) . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); + $class = self::parseName(array_pop($array), 1) . (App::getSuffix() || $appendSuffix ? ucfirst($layer) : ''); $path = $array ? implode('\\', $array) . '\\' : ''; - return App::$namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + return App::getNamespace() . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; } /** diff --git a/library/think/Log.php b/library/think/Log.php index 4a8b0b40..67b696bf 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -62,7 +62,7 @@ class Log throw new ClassNotFoundException('class not exists:' . $class, $class); } // 记录初始化信息 - App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + App::isDebug() && Log::record('[ LOG ] INIT ' . $type, 'info'); } /** @@ -141,7 +141,7 @@ class Log if (empty(self::$config['level'])) { // 获取全部日志 $log = self::$log; - if (!App::$debug && isset($log['debug'])) { + if (!App::isDebug() && isset($log['debug'])) { unset($log['debug']); } } else { diff --git a/library/think/Request.php b/library/think/Request.php index dafaf72f..ec8d6aca 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -11,1602 +11,7 @@ namespace think; -class Request +class Request extends Facade { - /** - * @var object 对象实例 - */ - protected static $instance; - protected $method; - /** - * @var string 域名(含协议和端口) - */ - protected $domain; - - /** - * @var string URL地址 - */ - protected $url; - - /** - * @var string 基础URL - */ - protected $baseUrl; - - /** - * @var string 当前执行的文件 - */ - protected $baseFile; - - /** - * @var string 访问的ROOT地址 - */ - protected $root; - - /** - * @var string pathinfo - */ - protected $pathinfo; - - /** - * @var string pathinfo(不含后缀) - */ - protected $path; - - /** - * @var array 当前路由信息 - */ - protected $routeInfo = []; - - /** - * @var array 当前调度信息 - */ - protected $dispatch = []; - protected $module; - protected $controller; - protected $action; - // 当前语言集 - protected $langset; - - /** - * @var array 请求参数 - */ - protected $param = []; - protected $get = []; - protected $post = []; - protected $request = []; - protected $route = []; - protected $put; - protected $session = []; - protected $file = []; - protected $cookie = []; - protected $server = []; - protected $header = []; - - /** - * @var array 资源类型 - */ - protected $mimeType = [ - 'xml' => 'application/xml,text/xml,application/x-xml', - 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', - 'js' => 'text/javascript,application/javascript,application/x-javascript', - 'css' => 'text/css', - 'rss' => 'application/rss+xml', - 'yaml' => 'application/x-yaml,text/yaml', - 'atom' => 'application/atom+xml', - 'pdf' => 'application/pdf', - 'text' => 'text/plain', - 'png' => 'image/png', - 'jpg' => 'image/jpg,image/jpeg,image/pjpeg', - 'gif' => 'image/gif', - 'csv' => 'text/csv', - 'html' => 'text/html,application/xhtml+xml,*/*', - ]; - - protected $content; - - // 全局过滤规则 - protected $filter; - // Hook扩展方法 - protected static $hook = []; - // 绑定的属性 - protected $bind = []; - // php://input - protected $input; - // 请求缓存 - protected $cache; - // 缓存是否检查 - protected $isCheckCache; - - /** - * 架构函数 - * @access protected - * @param array $options 参数 - */ - protected function __construct($options = []) - { - foreach ($options as $name => $item) { - if (property_exists($this, $name)) { - $this->$name = $item; - } - } - if (is_null($this->filter)) { - $this->filter = Config::get('default_filter'); - } - // 保存 php://input - $this->input = file_get_contents('php://input'); - } - - public function __call($method, $args) - { - if (array_key_exists($method, self::$hook)) { - array_unshift($args, $this); - return call_user_func_array(self::$hook[$method], $args); - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); - } - } - - /** - * Hook 方法注入 - * @access public - * @param string|array $method 方法名 - * @param mixed $callback callable - * @return void - */ - public static function hook($method, $callback = null) - { - if (is_array($method)) { - self::$hook = array_merge(self::$hook, $method); - } else { - self::$hook[$method] = $callback; - } - } - - /** - * 初始化 - * @access public - * @param array $options 参数 - * @return \think\Request - */ - public static function instance($options = []) - { - if (is_null(self::$instance)) { - self::$instance = new static($options); - } - return self::$instance; - } - - /** - * 创建一个URL请求 - * @access public - * @param string $uri URL地址 - * @param string $method 请求类型 - * @param array $params 请求参数 - * @param array $cookie - * @param array $files - * @param array $server - * @param string $content - * @return \think\Request - */ - public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) - { - $server['PATH_INFO'] = ''; - $server['REQUEST_METHOD'] = strtoupper($method); - $info = parse_url($uri); - if (isset($info['host'])) { - $server['SERVER_NAME'] = $info['host']; - $server['HTTP_HOST'] = $info['host']; - } - if (isset($info['scheme'])) { - if ('https' === $info['scheme']) { - $server['HTTPS'] = 'on'; - $server['SERVER_PORT'] = 443; - } else { - unset($server['HTTPS']); - $server['SERVER_PORT'] = 80; - } - } - if (isset($info['port'])) { - $server['SERVER_PORT'] = $info['port']; - $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; - } - if (isset($info['user'])) { - $server['PHP_AUTH_USER'] = $info['user']; - } - if (isset($info['pass'])) { - $server['PHP_AUTH_PW'] = $info['pass']; - } - if (!isset($info['path'])) { - $info['path'] = '/'; - } - $options = []; - $options[strtolower($method)] = $params; - $queryString = ''; - if (isset($info['query'])) { - parse_str(html_entity_decode($info['query']), $query); - if (!empty($params)) { - $params = array_replace($query, $params); - $queryString = http_build_query($query, '', '&'); - } else { - $params = $query; - $queryString = $info['query']; - } - } elseif (!empty($params)) { - $queryString = http_build_query($params, '', '&'); - } - if ($queryString) { - parse_str($queryString, $get); - $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; - } - - $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); - $server['QUERY_STRING'] = $queryString; - $options['cookie'] = $cookie; - $options['param'] = $params; - $options['file'] = $files; - $options['server'] = $server; - $options['url'] = $server['REQUEST_URI']; - $options['baseUrl'] = $info['path']; - $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); - $options['method'] = $server['REQUEST_METHOD']; - $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; - $options['content'] = $content; - self::$instance = new self($options); - return self::$instance; - } - - /** - * 设置或获取当前包含协议的域名 - * @access public - * @param string $domain 域名 - * @return string - */ - public function domain($domain = null) - { - if (!is_null($domain)) { - $this->domain = $domain; - return $this; - } elseif (!$this->domain) { - $this->domain = $this->scheme() . '://' . $this->host(); - } - return $this->domain; - } - - /** - * 设置或获取当前完整URL 包括QUERY_STRING - * @access public - * @param string|true $url URL地址 true 带域名获取 - * @return string - */ - public function url($url = null) - { - if (!is_null($url) && true !== $url) { - $this->url = $url; - return $this; - } elseif (!$this->url) { - if (IS_CLI) { - $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; - } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { - $this->url = $_SERVER['HTTP_X_REWRITE_URL']; - } elseif (isset($_SERVER['REQUEST_URI'])) { - $this->url = $_SERVER['REQUEST_URI']; - } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { - $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); - } else { - $this->url = ''; - } - } - return true === $url ? $this->domain() . $this->url : $this->url; - } - - /** - * 设置或获取当前URL 不含QUERY_STRING - * @access public - * @param string $url URL地址 - * @return string - */ - public function baseUrl($url = null) - { - if (!is_null($url) && true !== $url) { - $this->baseUrl = $url; - return $this; - } elseif (!$this->baseUrl) { - $str = $this->url(); - $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; - } - return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; - } - - /** - * 设置或获取当前执行的文件 SCRIPT_NAME - * @access public - * @param string $file 当前执行的文件 - * @return string - */ - public function baseFile($file = null) - { - if (!is_null($file) && true !== $file) { - $this->baseFile = $file; - return $this; - } elseif (!$this->baseFile) { - $url = ''; - if (!IS_CLI) { - $script_name = basename($_SERVER['SCRIPT_FILENAME']); - if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['SCRIPT_NAME']; - } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { - $url = $_SERVER['PHP_SELF']; - } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['ORIG_SCRIPT_NAME']; - } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { - $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; - } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { - $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); - } - } - $this->baseFile = $url; - } - return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; - } - - /** - * 设置或获取URL访问根地址 - * @access public - * @param string $url URL地址 - * @return string - */ - public function root($url = null) - { - if (!is_null($url) && true !== $url) { - $this->root = $url; - return $this; - } elseif (!$this->root) { - $file = $this->baseFile(); - if ($file && 0 !== strpos($this->url(), $file)) { - $file = str_replace('\\', '/', dirname($file)); - } - $this->root = rtrim($file, '/'); - } - return true === $url ? $this->domain() . $this->root : $this->root; - } - - /** - * 获取当前请求URL的pathinfo信息(含URL后缀) - * @access public - * @return string - */ - public function pathinfo() - { - if (is_null($this->pathinfo)) { - if (isset($_GET[Config::get('var_pathinfo')])) { - // 判断URL里面是否有兼容模式参数 - $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; - unset($_GET[Config::get('var_pathinfo')]); - } elseif (IS_CLI) { - // CLI模式下 index.php module/controller/action/params/... - $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; - } - - // 分析PATHINFO信息 - if (!isset($_SERVER['PATH_INFO'])) { - foreach (Config::get('pathinfo_fetch') as $type) { - if (!empty($_SERVER[$type])) { - $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? - substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; - break; - } - } - } - $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); - } - return $this->pathinfo; - } - - /** - * 获取当前请求URL的pathinfo信息(不含URL后缀) - * @access public - * @return string - */ - public function path() - { - if (is_null($this->path)) { - $suffix = Config::get('url_html_suffix'); - $pathinfo = $this->pathinfo(); - if (false === $suffix) { - // 禁止伪静态访问 - $this->path = $pathinfo; - } elseif ($suffix) { - // 去除正常的URL后缀 - $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); - } else { - // 允许任何后缀访问 - $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); - } - } - return $this->path; - } - - /** - * 当前URL的访问后缀 - * @access public - * @return string - */ - public function ext() - { - return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); - } - - /** - * 获取当前请求的时间 - * @access public - * @param bool $float 是否使用浮点类型 - * @return integer|float - */ - public function time($float = false) - { - return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; - } - - /** - * 当前请求的资源类型 - * @access public - * @return false|string - */ - public function type() - { - $accept = $this->server('HTTP_ACCEPT'); - if (empty($accept)) { - return false; - } - - foreach ($this->mimeType as $key => $val) { - $array = explode(',', $val); - foreach ($array as $k => $v) { - if (stristr($accept, $v)) { - return $key; - } - } - } - return false; - } - - /** - * 设置资源类型 - * @access public - * @param string|array $type 资源类型名 - * @param string $val 资源类型 - * @return void - */ - public function mimeType($type, $val = '') - { - if (is_array($type)) { - $this->mimeType = array_merge($this->mimeType, $type); - } else { - $this->mimeType[$type] = $val; - } - } - - /** - * 当前的请求类型 - * @access public - * @param bool $method true 获取原始请求类型 - * @return string - */ - public function method($method = false) - { - if (true === $method) { - // 获取原始请求类型 - return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); - } elseif (!$this->method) { - if (isset($_POST[Config::get('var_method')])) { - $this->method = strtoupper($_POST[Config::get('var_method')]); - $this->{$this->method}($_POST); - } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { - $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); - } else { - $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); - } - } - return $this->method; - } - - /** - * 是否为GET请求 - * @access public - * @return bool - */ - public function isGet() - { - return $this->method() == 'GET'; - } - - /** - * 是否为POST请求 - * @access public - * @return bool - */ - public function isPost() - { - return $this->method() == 'POST'; - } - - /** - * 是否为PUT请求 - * @access public - * @return bool - */ - public function isPut() - { - return $this->method() == 'PUT'; - } - - /** - * 是否为DELTE请求 - * @access public - * @return bool - */ - public function isDelete() - { - return $this->method() == 'DELETE'; - } - - /** - * 是否为HEAD请求 - * @access public - * @return bool - */ - public function isHead() - { - return $this->method() == 'HEAD'; - } - - /** - * 是否为PATCH请求 - * @access public - * @return bool - */ - public function isPatch() - { - return $this->method() == 'PATCH'; - } - - /** - * 是否为OPTIONS请求 - * @access public - * @return bool - */ - public function isOptions() - { - return $this->method() == 'OPTIONS'; - } - - /** - * 是否为cli - * @access public - * @return bool - */ - public function isCli() - { - return PHP_SAPI == 'cli'; - } - - /** - * 是否为cgi - * @access public - * @return bool - */ - public function isCgi() - { - return strpos(PHP_SAPI, 'cgi') === 0; - } - - /** - * 获取获取当前请求的参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function param($name = '', $default = null, $filter = '') - { - if (empty($this->param)) { - $method = $this->method(true); - // 自动获取请求变量 - switch ($method) { - case 'POST': - $vars = $this->post(false); - break; - case 'PUT': - case 'DELETE': - case 'PATCH': - $vars = $this->put(false); - break; - default: - $vars = []; - } - // 当前请求参数和URL地址中的参数合并 - $this->param = array_merge($this->get(false), $vars, $this->route(false)); - } - if (true === $name) { - // 获取包含文件上传信息的数组 - $file = $this->file(); - $data = array_merge($this->param, $file); - return $this->input($data, '', $default, $filter); - } - return $this->input($this->param, $name, $default, $filter); - } - - /** - * 设置获取获取路由参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function route($name = '', $default = null, $filter = '') - { - if (is_array($name)) { - $this->param = []; - return $this->route = array_merge($this->route, $name); - } - return $this->input($this->route, $name, $default, $filter); - } - - /** - * 设置获取获取GET参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function get($name = '', $default = null, $filter = '') - { - if (empty($this->get)) { - $this->get = $_GET; - } - if (is_array($name)) { - $this->param = []; - return $this->get = array_merge($this->get, $name); - } - return $this->input($this->get, $name, $default, $filter); - } - - /** - * 设置获取获取POST参数 - * @access public - * @param string $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function post($name = '', $default = null, $filter = '') - { - if (empty($this->post)) { - $content = $this->input; - if (empty($_POST) && 'application/json' == $this->contentType()) { - $this->post = (array) json_decode($content, true); - } else { - $this->post = $_POST; - } - } - if (is_array($name)) { - $this->param = []; - return $this->post = array_merge($this->post, $name); - } - return $this->input($this->post, $name, $default, $filter); - } - - /** - * 设置获取获取PUT参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function put($name = '', $default = null, $filter = '') - { - if (is_null($this->put)) { - $content = $this->input; - if ('application/json' == $this->contentType()) { - $this->put = (array) json_decode($content, true); - } else { - parse_str($content, $this->put); - } - } - if (is_array($name)) { - $this->param = []; - return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); - } - - return $this->input($this->put, $name, $default, $filter); - } - - /** - * 设置获取获取DELETE参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function delete($name = '', $default = null, $filter = '') - { - return $this->put($name, $default, $filter); - } - - /** - * 设置获取获取PATCH参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function patch($name = '', $default = null, $filter = '') - { - return $this->put($name, $default, $filter); - } - - /** - * 获取request变量 - * @param string $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function request($name = '', $default = null, $filter = '') - { - if (empty($this->request)) { - $this->request = $_REQUEST; - } - if (is_array($name)) { - $this->param = []; - return $this->request = array_merge($this->request, $name); - } - return $this->input($this->request, $name, $default, $filter); - } - - /** - * 获取session数据 - * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function session($name = '', $default = null, $filter = '') - { - if (empty($this->session)) { - $this->session = Session::get(); - } - if (is_array($name)) { - return $this->session = array_merge($this->session, $name); - } - return $this->input($this->session, $name, $default, $filter); - } - - /** - * 获取cookie参数 - * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function cookie($name = '', $default = null, $filter = '') - { - if (empty($this->cookie)) { - $this->cookie = $_COOKIE; - } - if (is_array($name)) { - return $this->cookie = array_merge($this->cookie, $name); - } - return $this->input($this->cookie, $name, $default, $filter); - } - - /** - * 获取server参数 - * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function server($name = '', $default = null, $filter = '') - { - if (empty($this->server)) { - $this->server = $_SERVER; - } - if (is_array($name)) { - return $this->server = array_merge($this->server, $name); - } - return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); - } - - /** - * 获取上传的文件信息 - * @access public - * @param string|array $name 名称 - * @return null|array|\think\File - */ - public function file($name = '') - { - if (empty($this->file)) { - $this->file = isset($_FILES) ? $_FILES : []; - } - if (is_array($name)) { - return $this->file = array_merge($this->file, $name); - } - $files = $this->file; - if (!empty($files)) { - // 处理上传文件 - $array = []; - foreach ($files as $key => $file) { - if (is_array($file['name'])) { - $item = []; - $keys = array_keys($file); - $count = count($file['name']); - for ($i = 0; $i < $count; $i++) { - if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { - continue; - } - $temp['key'] = $key; - foreach ($keys as $_key) { - $temp[$_key] = $file[$_key][$i]; - } - $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); - } - $array[$key] = $item; - } else { - if ($file instanceof File) { - $array[$key] = $file; - } else { - if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { - continue; - } - $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); - } - } - } - if (strpos($name, '.')) { - list($name, $sub) = explode('.', $name); - } - if ('' === $name) { - // 获取全部文件 - return $array; - } elseif (isset($sub) && isset($array[$name][$sub])) { - return $array[$name][$sub]; - } elseif (isset($array[$name])) { - return $array[$name]; - } - } - return; - } - - /** - * 获取环境变量 - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function env($name = '', $default = null, $filter = '') - { - if (empty($this->env)) { - $this->env = $_ENV; - } - if (is_array($name)) { - return $this->env = array_merge($this->env, $name); - } - return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); - } - - /** - * 设置或者获取当前的Header - * @access public - * @param string|array $name header名称 - * @param string $default 默认值 - * @return string - */ - public function header($name = '', $default = null) - { - if (empty($this->header)) { - $header = []; - if (function_exists('apache_request_headers') && $result = apache_request_headers()) { - $header = $result; - } else { - $server = $this->server ?: $_SERVER; - foreach ($server as $key => $val) { - if (0 === strpos($key, 'HTTP_')) { - $key = str_replace('_', '-', strtolower(substr($key, 5))); - $header[$key] = $val; - } - } - if (isset($server['CONTENT_TYPE'])) { - $header['content-type'] = $server['CONTENT_TYPE']; - } - if (isset($server['CONTENT_LENGTH'])) { - $header['content-length'] = $server['CONTENT_LENGTH']; - } - } - $this->header = array_change_key_case($header); - } - if (is_array($name)) { - return $this->header = array_merge($this->header, $name); - } - if ('' === $name) { - return $this->header; - } - $name = str_replace('_', '-', strtolower($name)); - return isset($this->header[$name]) ? $this->header[$name] : $default; - } - - /** - * 获取变量 支持过滤和默认值 - * @param array $data 数据源 - * @param string|false $name 字段名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤函数 - * @return mixed - */ - public function input($data = [], $name = '', $default = null, $filter = '') - { - if (false === $name) { - // 获取原始数据 - return $data; - } - $name = (string) $name; - if ('' != $name) { - // 解析name - if (strpos($name, '/')) { - list($name, $type) = explode('/', $name); - } else { - $type = 's'; - } - // 按.拆分成多维数组进行判断 - foreach (explode('.', $name) as $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - // 无输入数据,返回默认值 - return $default; - } - } - if (is_object($data)) { - return $data; - } - } - - // 解析过滤器 - if (is_null($filter)) { - $filter = []; - } else { - $filter = $filter ?: $this->filter; - if (is_string($filter)) { - $filter = explode(',', $filter); - } else { - $filter = (array) $filter; - } - } - - $filter[] = $default; - if (is_array($data)) { - array_walk_recursive($data, [$this, 'filterValue'], $filter); - reset($data); - } else { - $this->filterValue($data, $name, $filter); - } - - if (isset($type) && $data !== $default) { - // 强制类型转换 - $this->typeCast($data, $type); - } - return $data; - } - - /** - * 设置或获取当前的过滤规则 - * @param mixed $filter 过滤规则 - * @return mixed - */ - public function filter($filter = null) - { - if (is_null($filter)) { - return $this->filter; - } else { - $this->filter = $filter; - } - } - - /** - * 递归过滤给定的值 - * @param mixed $value 键值 - * @param mixed $key 键名 - * @param array $filters 过滤方法+默认值 - * @return mixed - */ - private function filterValue(&$value, $key, $filters) - { - $default = array_pop($filters); - foreach ($filters as $filter) { - if (is_callable($filter)) { - // 调用函数或者方法过滤 - $value = call_user_func($filter, $value); - } elseif (is_scalar($value)) { - if (strpos($filter, '/')) { - // 正则过滤 - if (!preg_match($filter, $value)) { - // 匹配不成功返回默认值 - $value = $default; - break; - } - } elseif (!empty($filter)) { - // filter函数不存在时, 则使用filter_var进行过滤 - // filter为非整形值时, 调用filter_id取得过滤id - $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); - if (false === $value) { - $value = $default; - break; - } - } - } - } - return $this->filterExp($value); - } - - /** - * 过滤表单中的表达式 - * @param string $value - * @return void - */ - public function filterExp(&$value) - { - // 过滤查询特殊字符 - if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { - $value .= ' '; - } - // TODO 其他安全过滤 - } - - /** - * 强制类型转换 - * @param string $data - * @param string $type - * @return mixed - */ - private function typeCast(&$data, $type) - { - switch (strtolower($type)) { - // 数组 - case 'a': - $data = (array) $data; - break; - // 数字 - case 'd': - $data = (int) $data; - break; - // 浮点 - case 'f': - $data = (float) $data; - break; - // 布尔 - case 'b': - $data = (boolean) $data; - break; - // 字符串 - case 's': - default: - if (is_scalar($data)) { - $data = (string) $data; - } else { - throw new \InvalidArgumentException('variable type error:' . gettype($data)); - } - } - } - - /** - * 是否存在某个请求参数 - * @access public - * @param string $name 变量名 - * @param string $type 变量类型 - * @param bool $checkEmpty 是否检测空值 - * @return mixed - */ - public function has($name, $type = 'param', $checkEmpty = false) - { - if (empty($this->$type)) { - $param = $this->$type(); - } else { - $param = $this->$type; - } - // 按.拆分成多维数组进行判断 - foreach (explode('.', $name) as $val) { - if (isset($param[$val])) { - $param = $param[$val]; - } else { - return false; - } - } - return ($checkEmpty && '' === $param) ? false : true; - } - - /** - * 获取指定的参数 - * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 - * @return mixed - */ - public function only($name, $type = 'param') - { - $param = $this->$type(); - if (is_string($name)) { - $name = explode(',', $name); - } - $item = []; - foreach ($name as $key) { - if (isset($param[$key])) { - $item[$key] = $param[$key]; - } - } - return $item; - } - - /** - * 排除指定参数获取 - * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 - * @return mixed - */ - public function except($name, $type = 'param') - { - $param = $this->$type(); - if (is_string($name)) { - $name = explode(',', $name); - } - foreach ($name as $key) { - if (isset($param[$key])) { - unset($param[$key]); - } - } - return $param; - } - - /** - * 当前是否ssl - * @access public - * @return bool - */ - public function isSsl() - { - $server = array_merge($_SERVER, $this->server); - if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { - return true; - } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { - return true; - } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { - return true; - } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { - return true; - } - return false; - } - - /** - * 当前是否Ajax请求 - * @access public - * @param bool $ajax true 获取原始ajax请求 - * @return bool - */ - public function isAjax($ajax = false) - { - $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); - $result = ('xmlhttprequest' == $value) ? true : false; - if (true === $ajax) { - return $result; - } else { - return $this->param(Config::get('var_ajax')) ? true : $result; - } - } - - /** - * 当前是否Pjax请求 - * @access public - * @param bool $pjax true 获取原始pjax请求 - * @return bool - */ - public function isPjax($pjax = false) - { - $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; - if (true === $pjax) { - return $result; - } else { - return $this->param(Config::get('var_pjax')) ? true : $result; - } - } - - /** - * 获取客户端IP地址 - * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 - * @param boolean $adv 是否进行高级模式获取(有可能被伪装) - * @return mixed - */ - public function ip($type = 0, $adv = false) - { - $type = $type ? 1 : 0; - static $ip = null; - if (null !== $ip) { - return $ip[$type]; - } - - if ($adv) { - if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); - $pos = array_search('unknown', $arr); - if (false !== $pos) { - unset($arr[$pos]); - } - $ip = trim(current($arr)); - } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { - $ip = $_SERVER['HTTP_CLIENT_IP']; - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; - } - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; - } - // IP地址合法验证 - $long = sprintf("%u", ip2long($ip)); - $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; - return $ip[$type]; - } - - /** - * 检测是否使用手机访问 - * @access public - * @return bool - */ - public function isMobile() - { - if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { - return true; - } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { - return true; - } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { - return true; - } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { - return true; - } else { - return false; - } - } - - /** - * 当前URL地址中的scheme参数 - * @access public - * @return string - */ - public function scheme() - { - return $this->isSsl() ? 'https' : 'http'; - } - - /** - * 当前请求URL地址中的query参数 - * @access public - * @return string - */ - public function query() - { - return $this->server('QUERY_STRING'); - } - - /** - * 当前请求的host - * @access public - * @return string - */ - public function host() - { - return $this->server('HTTP_HOST'); - } - - /** - * 当前请求URL地址中的port参数 - * @access public - * @return integer - */ - public function port() - { - return $this->server('SERVER_PORT'); - } - - /** - * 当前请求 SERVER_PROTOCOL - * @access public - * @return integer - */ - public function protocol() - { - return $this->server('SERVER_PROTOCOL'); - } - - /** - * 当前请求 REMOTE_PORT - * @access public - * @return integer - */ - public function remotePort() - { - return $this->server('REMOTE_PORT'); - } - - /** - * 当前请求 HTTP_CONTENT_TYPE - * @access public - * @return string - */ - public function contentType() - { - $contentType = $this->server('CONTENT_TYPE'); - if ($contentType) { - if (strpos($contentType, ';')) { - list($type) = explode(';', $contentType); - } else { - $type = $contentType; - } - return trim($type); - } - return ''; - } - - /** - * 获取当前请求的路由信息 - * @access public - * @param array $route 路由名称 - * @return array - */ - public function routeInfo($route = []) - { - if (!empty($route)) { - $this->routeInfo = $route; - } else { - return $this->routeInfo; - } - } - - /** - * 设置或者获取当前请求的调度信息 - * @access public - * @param array $dispatch 调度信息 - * @return array - */ - public function dispatch($dispatch = null) - { - if (!is_null($dispatch)) { - $this->dispatch = $dispatch; - } - return $this->dispatch; - } - - /** - * 设置或者获取当前的模块名 - * @access public - * @param string $module 模块名 - * @return string|Request - */ - public function module($module = null) - { - if (!is_null($module)) { - $this->module = $module; - return $this; - } else { - return $this->module ?: ''; - } - } - - /** - * 设置或者获取当前的控制器名 - * @access public - * @param string $controller 控制器名 - * @return string|Request - */ - public function controller($controller = null) - { - if (!is_null($controller)) { - $this->controller = $controller; - return $this; - } else { - return $this->controller ?: ''; - } - } - - /** - * 设置或者获取当前的操作名 - * @access public - * @param string $action 操作名 - * @return string|Request - */ - public function action($action = null) - { - if (!is_null($action)) { - $this->action = $action; - return $this; - } else { - return $this->action ?: ''; - } - } - - /** - * 设置或者获取当前的语言 - * @access public - * @param string $lang 语言名 - * @return string|Request - */ - public function langset($lang = null) - { - if (!is_null($lang)) { - $this->langset = $lang; - return $this; - } else { - return $this->langset ?: ''; - } - } - - /** - * 设置或者获取当前请求的content - * @access public - * @return string - */ - public function getContent() - { - if (is_null($this->content)) { - $this->content = $this->input; - } - return $this->content; - } - - /** - * 获取当前请求的php://input - * @access public - * @return string - */ - public function getInput() - { - return $this->input; - } - - /** - * 生成请求令牌 - * @access public - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 - * @return string - */ - public function token($name = '__token__', $type = 'md5') - { - $type = is_callable($type) ? $type : 'md5'; - $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); - if ($this->isAjax()) { - header($name . ': ' . $token); - } - Session::set($name, $token); - return $token; - } - - /** - * 设置当前地址的请求缓存 - * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id - * @param mixed $expire 缓存有效期 - * @param array $except 缓存排除 - * @return void - */ - public function cache($key, $expire = null, $except = []) - { - if (false !== $key && $this->isGet() && !$this->isCheckCache) { - // 标记请求缓存检查 - $this->isCheckCache = true; - if (false === $expire) { - // 关闭当前缓存 - return; - } - if ($key instanceof \Closure) { - $key = call_user_func_array($key, [$this]); - } elseif (true === $key) { - foreach ($except as $rule) { - if (0 === strpos($this->url(), $rule)) { - return; - } - } - // 自动缓存功能 - $key = md5($this->host()) . '__URL__'; - } elseif (strpos($key, '|')) { - list($key, $fun) = explode('|', $key); - } - // 特殊规则替换 - if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url())], $key); - } - - if (false !== strpos($key, ':')) { - $param = $this->param(); - foreach ($param as $item => $val) { - if (is_string($val) && false !== strpos($key, ':' . $item)) { - $key = str_replace(':' . $item, $val, $key); - } - } - } elseif (strpos($key, ']')) { - if ('[' . $this->ext() . ']' == $key) { - // 缓存某个后缀的请求 - $key = md5($this->url()); - } else { - return; - } - } - if (isset($fun)) { - $key = $fun($key); - } - - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { - // 读取缓存 - $response = Response::create()->code(304); - throw new \think\exception\HttpResponseException($response); - } elseif (Cache::has($key)) { - list($content, $header) = Cache::get($key); - $response = Response::create($content)->header($header); - throw new \think\exception\HttpResponseException($response); - } else { - $this->cache = [$key, $expire]; - } - } - } - - /** - * 读取请求缓存设置 - * @access public - * @return array - */ - public function getCache() - { - return $this->cache; - } - - /** - * 设置当前请求绑定的对象实例 - * @access public - * @param string $name 绑定的对象标识 - * @param mixed $obj 绑定的对象实例 - * @return mixed - */ - public function bind($name, $obj = null) - { - if (is_array($name)) { - $this->bind = array_merge($this->bind, $name); - } else { - $this->bind[$name] = $obj; - } - } - - public function __set($name, $value) - { - $this->bind[$name] = $value; - } - - public function __get($name) - { - return isset($this->bind[$name]) ? $this->bind[$name] : null; - } - - public function __isset($name) - { - return isset($this->bind[$name]); - } } diff --git a/library/think/Response.php b/library/think/Response.php index 656198f0..d13f4bd5 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -11,318 +11,7 @@ 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 +class Response extends Facade { - // 原始数据 - protected $data; - - // 当前的contentType - protected $contentType = 'text/html'; - - // 字符集 - protected $charset = 'utf-8'; - - //状态 - protected $code = 200; - - // 输出参数 - protected $options = []; - // header参数 - protected $header = []; - - protected $content = null; - - /** - * 架构函数 - * @access public - * @param mixed $data 输出数据 - * @param int $code - * @param array $header - * @param array $options 输出参数 - */ - public function __construct($data = '', $code = 200, array $header = [], $options = []) - { - $this->data($data); - $this->header = $header; - $this->code = $code; - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - $this->contentType($this->contentType, $this->charset); - } - - /** - * 创建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 - */ - public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) - { - $type = empty($type) ? 'null' : strtolower($type); - - $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst($type); - if (class_exists($class)) { - $response = new $class($data, $code, $header, $options); - } else { - $response = new static($data, $code, $header, $options); - } - - return $response; - } - - /** - * 发送数据到客户端 - * @access public - * @return mixed - * @throws \InvalidArgumentException - */ - public function send() - { - // 处理输出数据 - $data = $this->getContent(); - - // Trace调试注入 - if (Env::get('app_trace', Config::get('app_trace'))) { - Debug::inject($this, $data); - } - - if (200 == $this->code) { - $cache = Request::instance()->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::set($cache[0], [$data, $this->header], $cache[1]); - } - } - - if (!headers_sent() && !empty($this->header)) { - // 发送状态码 - http_response_code($this->code); - // 发送头部信息 - foreach ($this->header as $name => $val) { - header($name . ':' . $val); - } - } - - echo $data; - - if (function_exists('fastcgi_finish_request')) { - // 提高页面响应 - fastcgi_finish_request(); - } - - // 监听response_end - Hook::listen('response_end', $this); - - // 清空当次请求有效的数据 - if (!($this instanceof RedirectResponse)) { - Session::flush(); - } - } - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - */ - protected function output($data) - { - return $data; - } - - /** - * 输出的参数 - * @access public - * @param mixed $options 输出参数 - * @return $this - */ - public function options($options = []) - { - $this->options = array_merge($this->options, $options); - return $this; - } - - /** - * 输出数据设置 - * @access public - * @param mixed $data 输出数据 - * @return $this - */ - public function data($data) - { - $this->data = $data; - return $this; - } - - /** - * 设置响应头 - * @access public - * @param string|array $name 参数名 - * @param string $value 参数值 - * @return $this - */ - public function header($name, $value = null) - { - if (is_array($name)) { - $this->header = array_merge($this->header, $name); - } else { - $this->header[$name] = $value; - } - return $this; - } - - /** - * 设置页面输出内容 - * @param $content - * @return $this - */ - public function content($content) - { - if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ - $content, - '__toString', - ]) - ) { - throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); - } - - $this->content = (string) $content; - - return $this; - } - - /** - * 发送HTTP状态 - * @param integer $code 状态码 - * @return $this - */ - public function code($code) - { - $this->code = $code; - return $this; - } - - /** - * LastModified - * @param string $time - * @return $this - */ - public function lastModified($time) - { - $this->header['Last-Modified'] = $time; - return $this; - } - - /** - * Expires - * @param string $time - * @return $this - */ - public function expires($time) - { - $this->header['Expires'] = $time; - return $this; - } - - /** - * ETag - * @param string $eTag - * @return $this - */ - public function eTag($eTag) - { - $this->header['ETag'] = $eTag; - return $this; - } - - /** - * 页面缓存控制 - * @param string $cache 状态码 - * @return $this - */ - public function cacheControl($cache) - { - $this->header['Cache-control'] = $cache; - return $this; - } - - /** - * 页面输出类型 - * @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 头部名称 - * @return mixed - */ - public function getHeader($name = '') - { - if (!empty($name)) { - return isset($this->header[$name]) ? $this->header[$name] : null; - } else { - return $this->header; - } - } - - /** - * 获取原始数据 - * @return mixed - */ - public function getData() - { - return $this->data; - } - - /** - * 获取输出数据 - * @return mixed - */ - public function getContent() - { - if (null == $this->content) { - $content = $this->output($this->data); - - if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ - $content, - '__toString', - ]) - ) { - throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); - } - - $this->content = (string) $content; - } - return $this->content; - } - - /** - * 获取状态码 - * @return integer - */ - public function getCode() - { - return $this->code; - } } diff --git a/library/think/Route.php b/library/think/Route.php index 6954de68..bed6c84f 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -11,1579 +11,9 @@ namespace think; -use think\exception\HttpException; +use think\Facade; -class Route +class Route extends Facade { - // 路由规则 - private static $rules = [ - 'get' => [], - 'post' => [], - 'put' => [], - 'delete' => [], - 'patch' => [], - 'head' => [], - 'options' => [], - '*' => [], - 'alias' => [], - 'domain' => [], - 'pattern' => [], - 'name' => [], - ]; - // REST路由操作方法定义 - private static $rest = [ - 'index' => ['get', '', 'index'], - 'create' => ['get', '/create', 'create'], - 'edit' => ['get', '/:id/edit', 'edit'], - 'read' => ['get', '/:id', 'read'], - 'save' => ['post', '', 'save'], - 'update' => ['put', '/:id', 'update'], - 'delete' => ['delete', '/:id', 'delete'], - ]; - - // 不同请求类型的方法前缀 - private static $methodPrefix = [ - 'get' => 'get', - 'post' => 'post', - 'put' => 'put', - 'delete' => 'delete', - 'patch' => 'patch', - ]; - - // 子域名 - private static $subDomain = ''; - // 域名绑定 - private static $bind = []; - // 当前分组信息 - private static $group = []; - // 当前子域名绑定 - private static $domainBind; - private static $domainRule; - // 当前域名 - private static $domain; - // 当前路由执行过程中的参数 - private static $option = []; - - /** - * 注册变量规则 - * @access public - * @param string|array $name 变量名 - * @param string $rule 变量规则 - * @return void - */ - public static function pattern($name = null, $rule = '') - { - if (is_array($name)) { - self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); - } else { - self::$rules['pattern'][$name] = $rule; - } - } - - /** - * 注册子域名部署规则 - * @access public - * @param string|array $domain 子域名 - * @param mixed $rule 路由规则 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function domain($domain, $rule = '', $option = [], $pattern = []) - { - if (is_array($domain)) { - foreach ($domain as $key => $item) { - self::domain($key, $item, $option, $pattern); - } - } elseif ($rule instanceof \Closure) { - // 执行闭包 - self::setDomain($domain); - call_user_func_array($rule, []); - self::setDomain(null); - } elseif (is_array($rule)) { - self::setDomain($domain); - self::group('', function () use ($rule) { - // 动态注册域名的路由规则 - self::registerRules($rule); - }, $option, $pattern); - self::setDomain(null); - } else { - self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; - } - } - - private static function setDomain($domain) - { - self::$domain = $domain; - } - - /** - * 设置路由绑定 - * @access public - * @param mixed $bind 绑定信息 - * @param string $type 绑定类型 默认为module 支持 namespace class controller - * @return mixed - */ - public static function bind($bind, $type = 'module') - { - self::$bind = ['type' => $type, $type => $bind]; - } - - /** - * 设置或者获取路由标识 - * @access public - * @param string|array $name 路由命名标识 数组表示批量设置 - * @param array $value 路由地址及变量信息 - * @return array - */ - public static function name($name = '', $value = null) - { - if (is_array($name)) { - return self::$rules['name'] = $name; - } elseif ('' === $name) { - return self::$rules['name']; - } elseif (!is_null($value)) { - self::$rules['name'][strtolower($name)][] = $value; - } else { - $name = strtolower($name); - return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; - } - } - - /** - * 读取路由绑定 - * @access public - * @param string $type 绑定类型 - * @return mixed - */ - public static function getBind($type) - { - return isset(self::$bind[$type]) ? self::$bind[$type] : null; - } - - /** - * 导入配置文件的路由规则 - * @access public - * @param array $rule 路由规则 - * @param string $type 请求类型 - * @return void - */ - public static function import(array $rule, $type = '*') - { - // 检查域名部署 - if (isset($rule['__domain__'])) { - self::domain($rule['__domain__']); - unset($rule['__domain__']); - } - - // 检查变量规则 - if (isset($rule['__pattern__'])) { - self::pattern($rule['__pattern__']); - unset($rule['__pattern__']); - } - - // 检查路由别名 - if (isset($rule['__alias__'])) { - self::alias($rule['__alias__']); - unset($rule['__alias__']); - } - - // 检查资源路由 - if (isset($rule['__rest__'])) { - self::resource($rule['__rest__']); - unset($rule['__rest__']); - } - - self::registerRules($rule, strtolower($type)); - } - - // 批量注册路由 - protected static function registerRules($rules, $type = '*') - { - foreach ($rules as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (empty($val)) { - continue; - } - if (is_string($key) && 0 === strpos($key, '[')) { - $key = substr($key, 1, -1); - self::group($key, $val); - } elseif (is_array($val)) { - self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); - } else { - self::setRule($key, $val, $type); - } - } - } - - /** - * 注册路由规则 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) - { - $group = self::getGroup('name'); - if (!is_null($group)) { - // 路由分组 - $option = array_merge(self::getGroup('option'), $option); - $pattern = array_merge(self::getGroup('pattern'), $pattern); - } - - $type = strtolower($type); - - if (strpos($type, '|')) { - $option['method'] = $type; - $type = '*'; - } - if (is_array($rule) && empty($route)) { - foreach ($rule as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, $val[1]); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $route = $val; - } - self::setRule($key, $route, $type, isset($option1) ? $option1 : $option, isset($pattern1) ? $pattern1 : $pattern, $group); - } - } else { - self::setRule($rule, $route, $type, $option, $pattern, $group); - } - - } - - /** - * 设置路由规则 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @param string $group 所属分组 - * @return void - */ - protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') - { - if (is_array($rule)) { - $name = $rule[0]; - $rule = $rule[1]; - } elseif (is_string($route)) { - $name = $route; - } - if (!isset($option['complete_match'])) { - if (Config::get('route_complete_match')) { - $option['complete_match'] = true; - } elseif ('$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - - if ('$' == substr($rule, -1, 1)) { - $rule = substr($rule, 0, -1); - } - - if ('/' != $rule || $group) { - $rule = trim($rule, '/'); - } - $vars = self::parseVar($rule); - if (isset($name)) { - $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; - $suffix = isset($option['ext']) ? $option['ext'] : null; - self::name($name, [$key, $vars, self::$domain, $suffix]); - } - if ($group) { - if ('*' != $type) { - $option['method'] = $type; - } - if (self::$domain) { - self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - } else { - if ('*' != $type && isset(self::$rules['*'][$rule])) { - unset(self::$rules['*'][$rule]); - } - if (self::$domain) { - self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - if ('*' == $type) { - // 注册路由快捷方式 - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (self::$domain) { - self::$rules['domain'][self::$domain][$method][$rule] = true; - } else { - self::$rules[$method][$rule] = true; - } - } - } - } - } - - /** - * 设置当前执行的参数信息 - * @access public - * @param array $options 参数信息 - * @return mixed - */ - protected static function setOption($options = []) - { - self::$option[] = $options; - } - - /** - * 获取当前执行的所有参数信息 - * @access public - * @return array - */ - public static function getOption() - { - return self::$option; - } - - /** - * 获取当前的分组信息 - * @access public - * @param string $type 分组信息名称 name option pattern - * @return mixed - */ - public static function getGroup($type) - { - if (isset(self::$group[$type])) { - return self::$group[$type]; - } else { - return 'name' == $type ? null : []; - } - } - - /** - * 设置当前的路由分组 - * @access public - * @param string $name 分组名称 - * @param array $option 分组路由参数 - * @param array $pattern 分组变量规则 - * @return void - */ - public static function setGroup($name, $option = [], $pattern = []) - { - self::$group['name'] = $name; - self::$group['option'] = $option ?: []; - self::$group['pattern'] = $pattern ?: []; - } - - /** - * 注册路由分组 - * @access public - * @param string|array $name 分组名称或者参数 - * @param array|\Closure $routes 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function group($name, $routes, $option = [], $pattern = []) - { - if (is_array($name)) { - $option = $name; - $name = isset($option['name']) ? $option['name'] : ''; - } - // 分组 - $currentGroup = self::getGroup('name'); - if ($currentGroup) { - $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); - } - if (!empty($name)) { - if ($routes instanceof \Closure) { - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - if ($currentGroup != $name) { - self::$rules['*'][$name]['route'] = ''; - self::$rules['*'][$name]['var'] = self::parseVar($name); - self::$rules['*'][$name]['option'] = $option; - self::$rules['*'][$name]['pattern'] = $pattern; - } - } else { - $item = []; - foreach ($routes as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $route = $val; - } - - $options = isset($option1) ? $option1 : $option; - $patterns = isset($pattern1) ? $pattern1 : $pattern; - if ('$' == substr($key, -1, 1)) { - // 是否完整匹配 - $options['complete_match'] = true; - $key = substr($key, 0, -1); - } - $key = trim($key, '/'); - $vars = self::parseVar($key); - $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; - // 设置路由标识 - $suffix = isset($options['ext']) ? $options['ext'] : null; - self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); - } - self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; - } - - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (!isset(self::$rules[$method][$name])) { - self::$rules[$method][$name] = true; - } elseif (is_array(self::$rules[$method][$name])) { - self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); - } - } - - } elseif ($routes instanceof \Closure) { - // 闭包注册 - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - } else { - // 批量注册路由 - self::rule($routes, '', '*', $option, $pattern); - } - } - - /** - * 注册路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function any($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, '*', $option, $pattern); - } - - /** - * 注册GET路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function get($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'GET', $option, $pattern); - } - - /** - * 注册POST路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function post($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'POST', $option, $pattern); - } - - /** - * 注册PUT路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function put($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'PUT', $option, $pattern); - } - - /** - * 注册DELETE路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function delete($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'DELETE', $option, $pattern); - } - - /** - * 注册PATCH路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function patch($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'PATCH', $option, $pattern); - } - - /** - * 注册资源路由 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function resource($rule, $route = '', $option = [], $pattern = []) - { - if (is_array($rule)) { - foreach ($rule as $key => $val) { - if (is_array($val)) { - list($val, $option, $pattern) = array_pad($val, 3, []); - } - self::resource($key, $val, $option, $pattern); - } - } else { - if (strpos($rule, '.')) { - // 注册嵌套资源路由 - $array = explode('.', $rule); - $last = array_pop($array); - $item = []; - foreach ($array as $val) { - $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); - } - $rule = implode('/', $item) . '/' . $last; - } - // 注册资源路由 - foreach (self::$rest as $key => $val) { - if ((isset($option['only']) && !in_array($key, $option['only'])) - || (isset($option['except']) && in_array($key, $option['except']))) { - continue; - } - if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { - $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); - } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { - $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); - } - $item = ltrim($rule . $val[1], '/'); - $option['rest'] = $key; - self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); - } - } - } - - /** - * 注册控制器路由 操作方法对应不同的请求后缀 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function controller($rule, $route = '', $option = [], $pattern = []) - { - foreach (self::$methodPrefix as $type => $val) { - self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); - } - } - - /** - * 注册别名路由 - * @access public - * @param string|array $rule 路由别名 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @return void - */ - public static function alias($rule = null, $route = '', $option = []) - { - if (is_array($rule)) { - self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); - } else { - self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; - } - } - - /** - * 设置不同请求类型下面的方法前缀 - * @access public - * @param string $method 请求类型 - * @param string $prefix 类型前缀 - * @return void - */ - public static function setMethodPrefix($method, $prefix = '') - { - if (is_array($method)) { - self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); - } else { - self::$methodPrefix[strtolower($method)] = $prefix; - } - } - - /** - * rest方法定义和修改 - * @access public - * @param string $name 方法名称 - * @param array|bool $resource 资源 - * @return void - */ - public static function rest($name, $resource = []) - { - if (is_array($name)) { - self::$rest = $resource ? $name : array_merge(self::$rest, $name); - } else { - self::$rest[$name] = $resource; - } - } - - /** - * 注册未匹配路由规则后的处理 - * @access public - * @param string $route 路由地址 - * @param string $method 请求类型 - * @param array $option 路由参数 - * @return void - */ - public static function miss($route, $method = '*', $option = []) - { - self::rule('__miss__', $route, $method, $option, []); - } - - /** - * 注册一个自动解析的URL路由 - * @access public - * @param string $route 路由地址 - * @return void - */ - public static function auto($route) - { - self::rule('__auto__', $route, '*', [], []); - } - - /** - * 获取或者批量设置路由定义 - * @access public - * @param mixed $rules 请求类型或者路由定义数组 - * @return array - */ - public static function rules($rules = '') - { - if (is_array($rules)) { - self::$rules = $rules; - } elseif ($rules) { - return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; - } else { - $rules = self::$rules; - unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); - return $rules; - } - } - - /** - * 检测子域名部署 - * @access public - * @param Request $request Request请求对象 - * @param array $currentRules 当前路由规则 - * @param string $method 请求类型 - * @return void - */ - public static function checkDomain($request, &$currentRules, $method = 'get') - { - // 域名规则 - $rules = self::$rules['domain']; - // 开启子域名部署 支持二级和三级域名 - if (!empty($rules)) { - $host = $request->host(); - if (isset($rules[$host])) { - // 完整域名或者IP配置 - $item = $rules[$host]; - } else { - $rootDomain = Config::get('url_domain_root'); - if ($rootDomain) { - // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); - } else { - $domain = explode('.', $host, -2); - } - // 子域名配置 - if (!empty($domain)) { - // 当前子域名 - $subDomain = implode('.', $domain); - self::$subDomain = $subDomain; - $domain2 = array_pop($domain); - if ($domain) { - // 存在三级域名 - $domain3 = array_pop($domain); - } - if ($subDomain && isset($rules[$subDomain])) { - // 子域名配置 - $item = $rules[$subDomain]; - } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { - // 泛三级域名 - $item = $rules['*.' . $domain2]; - $panDomain = $domain3; - } elseif (isset($rules['*']) && !empty($domain2)) { - // 泛二级域名 - if ('www' != $domain2) { - $item = $rules['*']; - $panDomain = $domain2; - } - } - } - } - if (!empty($item)) { - if (isset($panDomain)) { - // 保存当前泛域名 - $request->route(['__domain__' => $panDomain]); - } - if (isset($item['[bind]'])) { - // 解析子域名部署规则 - list($rule, $option, $pattern) = $item['[bind]']; - if (!empty($option['https']) && !$request->isSsl()) { - // https检测 - throw new HttpException(404, 'must use https request:' . $host); - } - - if (strpos($rule, '?')) { - // 传入其它参数 - $array = parse_url($rule); - $result = $array['path']; - parse_str($array['query'], $params); - if (isset($panDomain)) { - $pos = array_search('*', $params); - if (false !== $pos) { - // 泛域名作为参数 - $params[$pos] = $panDomain; - } - } - $_GET = array_merge($_GET, $params); - } else { - $result = $rule; - } - - if (0 === strpos($result, '\\')) { - // 绑定到命名空间 例如 \app\index\behavior - self::$bind = ['type' => 'namespace', 'namespace' => $result]; - } elseif (0 === strpos($result, '@')) { - // 绑定到类 例如 @app\index\controller\User - self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; - } else { - // 绑定到模块/控制器 例如 index/user - self::$bind = ['type' => 'module', 'module' => $result]; - } - self::$domainBind = true; - } else { - self::$domainRule = $item; - $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; - } - } - } - } - - /** - * 检测URL路由 - * @access public - * @param Request $request Request请求对象 - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $checkDomain 是否检测域名规则 - * @return false|array - */ - public static function check($request, $url, $depr = '/', $checkDomain = false) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace($depr, '|', $url); - - if (strpos($url, '|') && isset(self::$rules['alias'][strstr($url, '|', true)])) { - // 检测路由别名 - $result = self::checkRouteAlias($request, $url, $depr); - if (false !== $result) { - return $result; - } - } - $method = strtolower($request->method()); - // 获取当前请求类型的路由规则 - $rules = self::$rules[$method]; - // 检测域名部署 - if ($checkDomain) { - self::checkDomain($request, $rules, $method); - } - // 检测URL绑定 - $return = self::checkUrlBind($url, $rules, $depr); - if (false !== $return) { - return $return; - } - if ('|' != $url) { - $url = rtrim($url, '|'); - } - $item = str_replace('|', '/', $url); - if (isset($rules[$item])) { - // 静态路由规则检测 - $rule = $rules[$item]; - if (true === $rule) { - $rule = self::getRouteExpress($item); - } - if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { - self::setOption($rule['option']); - return self::parseRule($item, $rule['route'], $url, $rule['option']); - } - } - - // 路由规则检测 - if (!empty($rules)) { - return self::checkRoute($request, $rules, $url, $depr); - } - return false; - } - - private static function getRouteExpress($key) - { - return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; - } - - /** - * 检测路由规则 - * @access private - * @param Request $request - * @param array $rules 路由规则 - * @param string $url URL地址 - * @param string $depr URL分割符 - * @param string $group 路由分组名 - * @param array $options 路由参数(分组) - * @return mixed - */ - private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) - { - foreach ($rules as $key => $item) { - if (true === $item) { - $item = self::getRouteExpress($key); - } - if (!isset($item['rule'])) { - continue; - } - $rule = $item['rule']; - $route = $item['route']; - $vars = $item['var']; - $option = $item['option']; - $pattern = $item['pattern']; - - // 检查参数有效性 - if (!self::checkOption($option, $request)) { - continue; - } - - if (isset($option['ext'])) { - // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); - } - - if (is_array($rule)) { - // 分组路由 - $pos = strpos(str_replace('<', ':', $key), ':'); - if (false !== $pos) { - $str = substr($key, 0, $pos); - } else { - $str = $key; - } - if (is_string($str) && $str && 0 !== strpos(str_replace('|', '/', $url), $str)) { - continue; - } - self::setOption($option); - $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); - if (false !== $result) { - return $result; - } - } elseif ($route) { - if ('__miss__' == $rule || '__auto__' == $rule) { - // 指定特殊路由 - $var = trim($rule, '__'); - ${$var} = $item; - continue; - } - if ($group) { - $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); - } - - self::setOption($option); - if (isset($options['bind_model']) && isset($option['bind_model'])) { - $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); - } - $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); - if (false !== $result) { - return $result; - } - } - } - if (isset($auto)) { - // 自动解析URL地址 - return self::parseUrl($auto['route'] . '/' . $url, $depr); - } elseif (isset($miss)) { - // 未匹配所有路由的路由规则处理 - return self::parseRule('', $miss['route'], $url, $miss['option']); - } - return false; - } - - /** - * 检测路由别名 - * @access private - * @param Request $request - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkRouteAlias($request, $url, $depr) - { - $array = explode('|', $url); - $alias = array_shift($array); - $item = self::$rules['alias'][$alias]; - - if (is_array($item)) { - list($rule, $option) = $item; - $action = $array[0]; - if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { - // 允许操作 - return false; - } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { - // 排除操作 - return false; - } - if (isset($option['method'][$action])) { - $option['method'] = $option['method'][$action]; - } - } else { - $rule = $item; - } - $bind = implode('|', $array); - // 参数有效性检查 - if (isset($option) && !self::checkOption($option, $request)) { - // 路由不匹配 - return false; - } elseif (0 === strpos($rule, '\\')) { - // 路由到类 - return self::bindToClass($bind, substr($rule, 1), $depr); - } elseif (0 === strpos($rule, '@')) { - // 路由到控制器类 - return self::bindToController($bind, substr($rule, 1), $depr); - } else { - // 路由到模块/控制器 - return self::bindToModule($bind, $rule, $depr); - } - } - - /** - * 检测URL绑定 - * @access private - * @param string $url URL地址 - * @param array $rules 路由规则 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkUrlBind(&$url, &$rules, $depr = '/') - { - if (!empty(self::$bind)) { - $type = self::$bind['type']; - $bind = self::$bind[$type]; - // 记录绑定信息 - App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); - // 如果有URL绑定 则进行绑定检测 - switch ($type) { - case 'class': - // 绑定到类 - return self::bindToClass($url, $bind, $depr); - case 'controller': - // 绑定到控制器类 - return self::bindToController($url, $bind, $depr); - case 'namespace': - // 绑定到命名空间 - return self::bindToNamespace($url, $bind, $depr); - } - } - return false; - } - - /** - * 绑定到类 - * @access public - * @param string $url URL地址 - * @param string $class 类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToClass($url, $class, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'method', 'method' => [$class, $action]]; - } - - /** - * 绑定到命名空间 - * @access public - * @param string $url URL地址 - * @param string $namespace 命名空间 - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToNamespace($url, $namespace, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 3); - $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); - $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); - if (!empty($array[2])) { - self::parseUrlParams($array[2]); - } - return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method]]; - } - - /** - * 绑定到控制器类 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器名 (支持带模块名 index/user ) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToController($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'controller', 'controller' => $controller . '/' . $action]; - } - - /** - * 绑定到模块/控制器 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToModule($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'module', 'module' => $controller . '/' . $action]; - } - - /** - * 路由参数有效性检查 - * @access private - * @param array $option 路由参数 - * @param Request $request Request对象 - * @return bool - */ - private static function checkOption($option, $request) - { - if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) - || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 - || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 - || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 - || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 - || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) - || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 - || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 - || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 - || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 - || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 - ) { - return false; - } - return true; - } - - /** - * 检测路由规则 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $url URL地址 - * @param array $pattern 变量规则 - * @param array $option 路由参数 - * @param string $depr URL分隔符(全局) - * @return array|false - */ - private static function checkRule($rule, $route, $url, $pattern, $option, $depr) - { - // 检查完整规则定义 - if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { - return false; - } - // 检查路由的参数分隔符 - if (isset($option['param_depr'])) { - $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); - } - - $len1 = substr_count($url, '|'); - $len2 = substr_count($rule, '/'); - // 多余参数是否合并 - $merge = !empty($option['merge_extra_vars']) ? true : false; - if ($merge && $len1 > $len2) { - $url = str_replace('|', $depr, $url); - $url = implode('|', explode($depr, $url, $len2 + 1)); - } - - if ($len1 >= $len2 || strpos($rule, '[')) { - if (!empty($option['complete_match'])) { - // 完整匹配 - if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { - return false; - } - } - $pattern = array_merge(self::$rules['pattern'], $pattern); - if (false !== $match = self::match($url, $rule, $pattern, $merge)) { - // 匹配到路由规则 - return self::parseRule($rule, $route, $url, $option, $match, $merge); - } - } - return false; - } - - /** - * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... - * @access public - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $autoSearch 是否自动深度搜索控制器 - * @return array - */ - public static function parseUrl($url, $depr = '/', $autoSearch = false) - { - - if (isset(self::$bind['module'])) { - $bind = str_replace('/', $depr, self::$bind['module']); - // 如果有模块/控制器绑定 - $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); - } - $url = str_replace($depr, '|', $url); - list($path, $var) = self::parseUrlPath($url); - $route = [null, null, null]; - if (isset($path)) { - // 解析模块 - $module = Config::get('app_multi_module') ? array_shift($path) : null; - if ($autoSearch) { - // 自动搜索控制器 - $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); - $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; - $item = []; - $find = false; - foreach ($path as $val) { - $item[] = $val; - $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; - $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; - if (is_file($file)) { - $find = true; - break; - } else { - $dir .= DS . Loader::parseName($val); - } - } - if ($find) { - $controller = implode('.', $item); - $path = array_slice($path, count($item)); - } else { - $controller = array_shift($path); - } - } else { - // 解析控制器 - $controller = !empty($path) ? array_shift($path) : null; - } - // 解析操作 - $action = !empty($path) ? array_shift($path) : null; - // 解析额外参数 - self::parseUrlParams(empty($path) ? '' : implode('|', $path)); - // 封装路由 - $route = [$module, $controller, $action]; - // 检查地址是否被定义过路由 - $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); - $name2 = ''; - if (empty($module) || isset($bind) && $module == $bind) { - $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); - } - - if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { - throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); - } - } - return ['type' => 'module', 'module' => $route]; - } - - /** - * 解析URL的pathinfo参数和变量 - * @access private - * @param string $url URL地址 - * @return array - */ - private static function parseUrlPath($url) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace('|', '/', $url); - $url = trim($url, '/'); - $var = []; - if (false !== strpos($url, '?')) { - // [模块/控制器/操作?]参数1=值1&参数2=值2... - $info = parse_url($url); - $path = explode('/', $info['path']); - parse_str($info['query'], $var); - } elseif (strpos($url, '/')) { - // [模块/控制器/操作] - $path = explode('/', $url); - } else { - $path = [$url]; - } - return [$path, $var]; - } - - /** - * 检测URL和规则路由是否匹配 - * @access private - * @param string $url URL地址 - * @param string $rule 路由规则 - * @param array $pattern 变量规则 - * @return array|false - */ - private static function match($url, $rule, $pattern) - { - $m2 = explode('/', $rule); - $m1 = explode('|', $url); - - $var = []; - foreach ($m2 as $key => $val) { - // val中定义了多个变量 - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - $value = []; - $replace = []; - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; - } else { - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; - } - $value[] = $name; - } - $val = str_replace($matches[0], $replace, $val); - if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { - array_shift($match); - foreach ($value as $k => $name) { - if (isset($match[$k])) { - $var[$name] = $match[$k]; - } - } - continue; - } else { - return false; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $val = substr($val, 1, -1); - $optional = true; - } else { - $optional = false; - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - if (!$optional && !isset($m1[$key])) { - return false; - } - if (isset($m1[$key]) && isset($pattern[$name])) { - // 检查变量规则 - if ($pattern[$name] instanceof \Closure) { - $result = call_user_func_array($pattern[$name], [$m1[$key]]); - if (false === $result) { - return false; - } - } elseif (!preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { - return false; - } - } - $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; - } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { - return false; - } - } - // 成功匹配后返回URL中的动态变量数组 - return $var; - } - - /** - * 解析规则路由 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $pathinfo URL地址 - * @param array $option 路由参数 - * @param array $matches 匹配的变量 - * @return array - */ - private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = []) - { - $request = Request::instance(); - // 解析路由规则 - if ($rule) { - $rule = explode('/', $rule); - // 获取URL地址中的参数 - $paths = explode('|', $pathinfo); - foreach ($rule as $item) { - $fun = ''; - if (0 === strpos($item, '[:')) { - $item = substr($item, 1, -1); - } - if (0 === strpos($item, ':')) { - $var = substr($item, 1); - $matches[$var] = array_shift($paths); - } else { - // 过滤URL中的静态变量 - array_shift($paths); - } - } - } else { - $paths = explode('|', $pathinfo); - } - - // 获取路由地址规则 - if (is_string($route) && isset($option['prefix'])) { - // 路由地址前缀 - $route = $option['prefix'] . $route; - } - // 替换路由地址中的变量 - if (is_string($route) && !empty($matches)) { - foreach ($matches as $key => $val) { - if (false !== strpos($route, ':' . $key)) { - $route = str_replace(':' . $key, $val, $route); - } - } - } - - // 绑定模型数据 - if (isset($option['bind_model'])) { - $bind = []; - foreach ($option['bind_model'] as $key => $val) { - if ($val instanceof \Closure) { - $result = call_user_func_array($val, [$matches]); - } else { - if (is_array($val)) { - $fields = explode('&', $val[1]); - $model = $val[0]; - $exception = isset($val[2]) ? $val[2] : true; - } else { - $fields = ['id']; - $model = $val; - $exception = true; - } - $where = []; - $match = true; - foreach ($fields as $field) { - if (!isset($matches[$field])) { - $match = false; - break; - } else { - $where[$field] = $matches[$field]; - } - } - if ($match) { - $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); - $result = $query->failException($exception)->find(); - } - } - if (!empty($result)) { - $bind[$key] = $result; - } - } - $request->bind($bind); - } - - // 解析额外参数 - self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); - // 记录匹配的路由信息 - $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); - - // 检测路由after行为 - if (!empty($option['after_behavior'])) { - if ($option['after_behavior'] instanceof \Closure) { - $result = call_user_func_array($option['after_behavior'], []); - } else { - foreach ((array) $option['after_behavior'] as $behavior) { - $result = Hook::exec($behavior, ''); - if (!is_null($result)) { - break; - } - } - } - // 路由规则重定向 - if ($result instanceof Response) { - return ['type' => 'response', 'response' => $result]; - } elseif (is_array($result)) { - return $result; - } - } - - if ($route instanceof \Closure) { - // 执行闭包 - $result = ['type' => 'function', 'function' => $route]; - } elseif (0 === strpos($route, '/') || strpos($route, '://')) { - // 路由到重定向地址 - $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; - } elseif (false !== strpos($route, '\\')) { - // 路由到方法 - list($path, $var) = self::parseUrlPath($route); - $route = str_replace('/', '@', implode('/', $path)); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = ['type' => 'method', 'method' => $method, 'var' => $var]; - } elseif (0 === strpos($route, '@')) { - // 路由到控制器 - $route = substr($route, 1); - list($route, $var) = self::parseUrlPath($route); - $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; - $request->action(array_pop($route)); - $request->controller($route ? array_pop($route) : Config::get('default_controller')); - $request->module($route ? array_pop($route) : Config::get('default_module')); - App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); - } else { - // 路由到模块/控制器/操作 - $result = self::parseModule($route); - } - // 开启请求缓存 - if ($request->isGet() && isset($option['cache'])) { - $cache = $option['cache']; - if (is_array($cache)) { - list($key, $expire) = $cache; - } else { - $key = str_replace('|', '/', $pathinfo); - $expire = $cache; - } - $request->cache($key, $expire); - } - return $result; - } - - /** - * 解析URL地址为 模块/控制器/操作 - * @access private - * @param string $url URL地址 - * @return array - */ - private static function parseModule($url) - { - list($path, $var) = self::parseUrlPath($url); - $action = array_pop($path); - $controller = !empty($path) ? array_pop($path) : null; - $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; - $method = Request::instance()->method(); - if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { - // 操作方法前缀支持 - $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; - } - // 设置当前请求的路由变量 - Request::instance()->route($var); - // 路由到模块/控制器/操作 - return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false]; - } - - /** - * 解析URL地址中的参数Request对象 - * @access private - * @param string $rule 路由规则 - * @param array $var 变量 - * @return void - */ - private static function parseUrlParams($url, &$var = []) - { - if ($url) { - if (Config::get('url_param_type')) { - $var += explode('|', $url); - } else { - preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { - $var[$match[1]] = strip_tags($match[2]); - }, $url); - } - } - // 设置当前请求的参数 - Request::instance()->route($var); - } - - // 分析路由规则中的变量 - private static function parseVar($rule) - { - // 提取路由规则中的变量 - $var = []; - foreach (explode('/', $rule) as $val) { - $optional = false; - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $optional = true; - } else { - $optional = false; - } - $var[$name] = $optional ? 2 : 1; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $optional = true; - $val = substr($val, 1, -1); - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - $var[$name] = $optional ? 2 : 1; - } - } - return $var; - } } diff --git a/library/think/Session.php b/library/think/Session.php index 4b66284c..9c6e2430 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -11,355 +11,7 @@ namespace think; -use think\exception\ClassNotFoundException; - -class Session +class Session extends Facade { - protected static $prefix = ''; - protected static $init = null; - - /** - * 设置或者获取session作用域(前缀) - * @param string $prefix - * @return string|void - */ - public static function prefix($prefix = '') - { - if (empty($prefix) && null !== $prefix) { - return self::$prefix; - } else { - self::$prefix = $prefix; - } - } - - /** - * session初始化 - * @param array $config - * @return void - * @throws \think\Exception - */ - public static function init(array $config = []) - { - if (empty($config)) { - $config = Config::get('session'); - } - // 记录初始化信息 - App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); - $isDoStart = false; - if (isset($config['use_trans_sid'])) { - ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); - } - - // 启动session - if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { - ini_set('session.auto_start', 0); - $isDoStart = true; - } - - if (isset($config['prefix'])) { - self::$prefix = $config['prefix']; - } - if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { - session_id($_REQUEST[$config['var_session_id']]); - } elseif (isset($config['id']) && !empty($config['id'])) { - session_id($config['id']); - } - if (isset($config['name'])) { - session_name($config['name']); - } - if (isset($config['path'])) { - session_save_path($config['path']); - } - if (isset($config['domain'])) { - ini_set('session.cookie_domain', $config['domain']); - } - if (isset($config['expire'])) { - ini_set('session.gc_maxlifetime', $config['expire']); - ini_set('session.cookie_lifetime', $config['expire']); - } - if (isset($config['secure'])) { - ini_set('session.cookie_secure', $config['secure']); - } - if (isset($config['httponly'])) { - ini_set('session.cookie_httponly', $config['httponly']); - } - if (isset($config['use_cookies'])) { - ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); - } - if (isset($config['cache_limiter'])) { - session_cache_limiter($config['cache_limiter']); - } - if (isset($config['cache_expire'])) { - session_cache_expire($config['cache_expire']); - } - if (!empty($config['type'])) { - // 读取session驱动 - $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); - - // 检查驱动类 - if (!class_exists($class) || !session_set_save_handler(new $class($config))) { - throw new ClassNotFoundException('error session handler:' . $class, $class); - } - } - if ($isDoStart) { - session_start(); - self::$init = true; - } else { - self::$init = false; - } - } - - /** - * session自动启动或者初始化 - * @return void - */ - public static function boot() - { - if (is_null(self::$init)) { - self::init(); - } elseif (false === self::$init) { - if (PHP_SESSION_ACTIVE != session_status()) { - session_start(); - } - self::$init = true; - } - } - - /** - * session设置 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function set($name, $value = '', $prefix = null) - { - empty(self::$init) && self::boot(); - - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (strpos($name, '.')) { - // 二维数组赋值 - list($name1, $name2) = explode('.', $name); - if ($prefix) { - $_SESSION[$prefix][$name1][$name2] = $value; - } else { - $_SESSION[$name1][$name2] = $value; - } - } elseif ($prefix) { - $_SESSION[$prefix][$name] = $value; - } else { - $_SESSION[$name] = $value; - } - } - - /** - * session获取 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) - * @return mixed - */ - public static 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; - } - } - return $value; - } - - /** - * session获取并删除 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) - * @return mixed - */ - public static function pull($name, $prefix = null) - { - $result = self::get($name, $prefix); - if ($result) { - self::delete($name, $prefix); - return $result; - } else { - return; - } - } - - /** - * session设置 下一次请求有效 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function flash($name, $value) - { - self::set($name, $value); - if (!self::has('__flash__.__time__')) { - self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); - } - self::push('__flash__', $name); - } - - /** - * 清空当前请求的session数据 - * @return void - */ - public static function flush() - { - if (self::$init) { - $item = self::get('__flash__'); - - if (!empty($item)) { - $time = $item['__time__']; - if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { - unset($item['__time__']); - self::delete($item); - self::set('__flash__', []); - } - } - } - } - - /** - * 删除session数据 - * @param string|array $name session名称 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function delete($name, $prefix = null) - { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (is_array($name)) { - foreach ($name as $key) { - self::delete($key, $prefix); - } - } elseif (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - if ($prefix) { - unset($_SESSION[$prefix][$name1][$name2]); - } else { - unset($_SESSION[$name1][$name2]); - } - } else { - if ($prefix) { - unset($_SESSION[$prefix][$name]); - } else { - unset($_SESSION[$name]); - } - } - } - - /** - * 清空session数据 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function clear($prefix = null) - { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if ($prefix) { - unset($_SESSION[$prefix]); - } else { - $_SESSION = []; - } - } - - /** - * 判断session数据 - * @param string $name session名称 - * @param string|null $prefix - * @return bool - */ - public static 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]); - } - } - - /** - * 添加数据到一个session数组 - * @param string $key - * @param mixed $value - * @return void - */ - public static function push($key, $value) - { - $array = self::get($key); - if (is_null($array)) { - $array = []; - } - $array[] = $value; - self::set($key, $array); - } - - /** - * 启动session - * @return void - */ - public static function start() - { - session_start(); - self::$init = true; - } - - /** - * 销毁session - * @return void - */ - public static function destroy() - { - if (!empty($_SESSION)) { - $_SESSION = []; - } - session_unset(); - session_destroy(); - self::$init = null; - } - - /** - * 重新生成session_id - * @param bool $delete 是否删除关联会话文件 - * @return void - */ - private static function regenerate($delete = false) - { - session_regenerate_id($delete); - } - /** - * 暂停session - * @return void - */ - public static function pause() - { - // 暂停session - session_write_close(); - self::$init = false; - } } diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 688507a8..a71bffb2 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -16,8 +16,10 @@ namespace think\cache; */ abstract class Driver { - protected $handler = null; - protected $options = []; + protected $handler = null; + protected $readTimes = 0; + protected $writeTimes = 0; + protected $options = []; protected $tag; /** @@ -206,4 +208,14 @@ abstract class Driver { return $this->handler; } + + public function getReadTimes() + { + return $this->readTimes; + } + + public function getWriteTimes() + { + return $this->writeTimes; + } } diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 838025e0..d23f1ad7 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -102,6 +102,7 @@ class File extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $filename = $this->getCacheKey($name); if (!is_file($filename)) { return $default; @@ -136,6 +137,7 @@ class File extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -201,6 +203,7 @@ class File extends Driver */ public function rm($name) { + $this->writeTimes++; return $this->unlink($this->getCacheKey($name)); } @@ -221,6 +224,7 @@ class File extends Driver $this->rm('tag_' . md5($tag)); return true; } + $this->writeTimes++; $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); foreach ($files as $path) { if (is_dir($path)) { diff --git a/library/think/cache/driver/Lite.php b/library/think/cache/driver/Lite.php index 57727651..8ebf6797 100644 --- a/library/think/cache/driver/Lite.php +++ b/library/think/cache/driver/Lite.php @@ -73,6 +73,7 @@ class Lite extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $filename = $this->getCacheKey($name); if (is_file($filename)) { // 判断是否过期 @@ -98,6 +99,7 @@ class Lite extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -160,6 +162,7 @@ class Lite extends Driver */ public function rm($name) { + $this->writeTimes++; return unlink($this->getCacheKey($name)); } @@ -180,6 +183,7 @@ class Lite extends Driver $this->rm('tag_' . md5($tag)); return true; } + $this->writeTimes++; array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); } } diff --git a/library/think/cache/driver/Memcache.php b/library/think/cache/driver/Memcache.php index d41939d8..acc31832 100644 --- a/library/think/cache/driver/Memcache.php +++ b/library/think/cache/driver/Memcache.php @@ -75,6 +75,7 @@ class Memcache extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $result = $this->handler->get($this->getCacheKey($name)); return false !== $result ? $result : $default; } @@ -89,6 +90,7 @@ class Memcache extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -112,6 +114,7 @@ class Memcache extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return $this->handler->increment($key, $step); } @@ -125,6 +128,7 @@ class Memcache extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); $value = $this->handler->get($key) - $step; $res = $this->handler->set($key, $value); @@ -143,6 +147,7 @@ class Memcache extends Driver */ public function rm($name, $ttl = false) { + $this->writeTimes++; $key = $this->getCacheKey($name); return false === $ttl ? $this->handler->delete($key) : @@ -166,6 +171,7 @@ class Memcache extends Driver $this->rm('tag_' . md5($tag)); return true; } + $this->writeTimes++; return $this->handler->flush(); } } diff --git a/library/think/cache/driver/Memcached.php b/library/think/cache/driver/Memcached.php index 35fafd07..d12c9555 100644 --- a/library/think/cache/driver/Memcached.php +++ b/library/think/cache/driver/Memcached.php @@ -86,6 +86,7 @@ class Memcached extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $result = $this->handler->get($this->getCacheKey($name)); return false !== $result ? $result : $default; } @@ -100,6 +101,7 @@ class Memcached extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -124,6 +126,7 @@ class Memcached extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return $this->handler->increment($key, $step); } @@ -137,6 +140,7 @@ class Memcached extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); $value = $this->handler->get($key) - $step; $res = $this->handler->set($key, $value); @@ -155,6 +159,7 @@ class Memcached extends Driver */ public function rm($name, $ttl = false) { + $this->writeTimes++; $key = $this->getCacheKey($name); return false === $ttl ? $this->handler->delete($key) : @@ -176,6 +181,7 @@ class Memcached extends Driver $this->rm('tag_' . md5($tag)); return true; } + $this->writeTimes++; return $this->handler->flush(); } } diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 360f515a..9ae74b89 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -79,6 +79,7 @@ class Redis extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $value = $this->handler->get($this->getCacheKey($name)); if (is_null($value)) { return $default; @@ -98,6 +99,7 @@ class Redis extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -125,6 +127,7 @@ class Redis extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return $this->handler->incrby($key, $step); } @@ -138,6 +141,7 @@ class Redis extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return $this->handler->decrby($key, $step); } @@ -150,6 +154,7 @@ class Redis extends Driver */ public function rm($name) { + $this->writeTimes++; return $this->handler->delete($this->getCacheKey($name)); } @@ -170,6 +175,7 @@ class Redis extends Driver $this->rm('tag_' . md5($tag)); return true; } + $this->writeTimes++; return $this->handler->flushDB(); } diff --git a/library/think/cache/driver/Sqlite.php b/library/think/cache/driver/Sqlite.php index 76c592d8..63af88f1 100644 --- a/library/think/cache/driver/Sqlite.php +++ b/library/think/cache/driver/Sqlite.php @@ -79,6 +79,7 @@ class Sqlite extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $name = $this->getCacheKey($name); $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; $result = sqlite_query($this->handler, $sql); @@ -103,6 +104,7 @@ class Sqlite extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; $name = $this->getCacheKey($name); $value = sqlite_escape_string(serialize($value)); if (is_null($expire)) { @@ -168,6 +170,7 @@ class Sqlite extends Driver */ public function rm($name) { + $this->writeTimes++; $name = $this->getCacheKey($name); $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; sqlite_query($this->handler, $sql); @@ -188,6 +191,7 @@ class Sqlite extends Driver sqlite_query($this->handler, $sql); return true; } + $this->writeTimes++; $sql = 'DELETE FROM ' . $this->options['table']; sqlite_query($this->handler, $sql); return true; diff --git a/library/think/cache/driver/Wincache.php b/library/think/cache/driver/Wincache.php index 5be8d0df..669c6ccc 100644 --- a/library/think/cache/driver/Wincache.php +++ b/library/think/cache/driver/Wincache.php @@ -48,6 +48,7 @@ class Wincache extends Driver */ public function has($name) { + $this->readTimes++; $key = $this->getCacheKey($name); return wincache_ucache_exists($key); } @@ -61,6 +62,7 @@ class Wincache extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $key = $this->getCacheKey($name); return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; } @@ -75,6 +77,7 @@ class Wincache extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -98,6 +101,7 @@ class Wincache extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return wincache_ucache_inc($key, $step); } @@ -111,6 +115,7 @@ class Wincache extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return wincache_ucache_dec($key, $step); } @@ -123,6 +128,7 @@ class Wincache extends Driver */ public function rm($name) { + $this->writeTimes++; return wincache_ucache_delete($this->getCacheKey($name)); } @@ -142,6 +148,7 @@ class Wincache extends Driver $this->rm('tag_' . md5($tag)); return true; } else { + $this->writeTimes++; return wincache_ucache_clear(); } } diff --git a/library/think/cache/driver/Xcache.php b/library/think/cache/driver/Xcache.php index 317a4ee3..389e534a 100644 --- a/library/think/cache/driver/Xcache.php +++ b/library/think/cache/driver/Xcache.php @@ -61,6 +61,7 @@ class Xcache extends Driver */ public function get($name, $default = false) { + $this->readTimes++; $key = $this->getCacheKey($name); return xcache_isset($key) ? xcache_get($key) : $default; } @@ -75,6 +76,7 @@ class Xcache extends Driver */ public function set($name, $value, $expire = null) { + $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } @@ -98,6 +100,7 @@ class Xcache extends Driver */ public function inc($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return xcache_inc($key, $step); } @@ -111,6 +114,7 @@ class Xcache extends Driver */ public function dec($name, $step = 1) { + $this->writeTimes++; $key = $this->getCacheKey($name); return xcache_dec($key, $step); } @@ -123,6 +127,7 @@ class Xcache extends Driver */ public function rm($name) { + $this->writeTimes++; return xcache_unset($this->getCacheKey($name)); } @@ -143,6 +148,7 @@ class Xcache extends Driver $this->rm('tag_' . md5($tag)); return true; } + $this->writeTimes++; if (function_exists('xcache_unset_by_prefix')) { return xcache_unset_by_prefix($this->options['prefix']); } else { diff --git a/library/think/debug/Html.php b/library/think/debug/Html.php index f8651aa9..fb03b897 100644 --- a/library/think/debug/Html.php +++ b/library/think/debug/Html.php @@ -15,8 +15,8 @@ use think\Cache; use think\Config; use think\Db; use think\Debug; +use think\manager\ResponseManager; use think\Request; -use think\Response; /** * 页面Trace调试 @@ -38,11 +38,11 @@ class Html /** * 调试输出接口 * @access public - * @param Response $response Response对象 + * @param ResponseManager $response ResponseManager对象 * @param array $log 日志信息 * @return bool */ - public function output(Response $response, array $log = []) + public function output(ResponseManager $response, array $log = []) { $request = Request::instance(); $contentType = $response->getHeader('Content-Type'); @@ -67,7 +67,7 @@ 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', + '缓存信息' => Cache::getReadTimes() . ' reads,' . Cache::getWriteTimes() . ' writes', '配置加载' => count(Config::get()), ]; diff --git a/library/think/exception/Handle.php b/library/think/exception/Handle.php index 8b6e6c40..f6f10be8 100644 --- a/library/think/exception/Handle.php +++ b/library/think/exception/Handle.php @@ -36,7 +36,7 @@ class Handle { if (!$this->isIgnoreReport($exception)) { // 收集异常数据 - if (App::$debug) { + if (App::isDebug()) { $data = [ 'file' => $exception->getFile(), 'line' => $exception->getLine(), @@ -87,7 +87,7 @@ class Handle */ public function renderForConsole(Output $output, Exception $e) { - if (App::$debug) { + if (App::isDebug()) { $output->setVerbosity(Output::VERBOSITY_DEBUG); } $output->renderException($e); @@ -101,7 +101,7 @@ class Handle { $status = $e->getStatusCode(); $template = Config::get('http_exception_template'); - if (!App::$debug && !empty($template[$status])) { + if (!App::isDebug() && !empty($template[$status])) { return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); } else { return $this->convertExceptionToResponse($e); @@ -115,7 +115,7 @@ class Handle protected function convertExceptionToResponse(Exception $exception) { // 收集异常数据 - if (App::$debug) { + if (App::isDebug()) { // 调试模式,获取详细的错误信息 $data = [ 'name' => get_class($exception), @@ -162,7 +162,7 @@ class Handle include Config::get('exception_tmpl'); // 获取并清空缓存 $content = ob_get_clean(); - $response = new Response($content, 'html'); + $response = Response::init($content, 'html'); if ($exception instanceof HttpException) { $statusCode = $exception->getStatusCode(); diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index b639fd04..e979da6b 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -55,7 +55,7 @@ class File $depr = $depr ? "---------------------------------------------------------------\r\n" : ''; $info = ''; - if (App::$debug) { + if (App::isDebug()) { // 获取基本信息 if (isset($_SERVER['HTTP_HOST'])) { $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; @@ -92,7 +92,7 @@ class File $info .= $level; } } - if (App::$debug) { + if (App::isDebug()) { $info = "{$server} {$remote} {$method} {$uri}\r\n" . $info; } return error_log("[{$now}] {$info}\r\n{$depr}", 3, $destination); diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index 08fd8ace..da88df34 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -66,7 +66,7 @@ class Socket return false; } $trace = []; - if (App::$debug) { + if (App::isDebug()) { $runtime = round(microtime(true) - THINK_START_TIME, 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; diff --git a/library/think/manager/AppManager.php b/library/think/manager/AppManager.php new file mode 100644 index 00000000..4b622eee --- /dev/null +++ b/library/think/manager/AppManager.php @@ -0,0 +1,601 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\Config; +use think\Env; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\exception\RouteNotFoundException; +use think\Hook; +use think\Lang; +use think\Loader; +use think\Log; +use think\Request; +use think\Response; +use think\ResponseManager; +use think\Route; + +/** + * App 应用管理 + * @author liu21st + */ +class AppManager +{ + /** + * @var bool 是否初始化过 + */ + protected $init = false; + + /** + * @var string 当前模块路径 + */ + protected $modulePath; + + /** + * @var bool 应用调试模式 + */ + protected $debug = true; + + /** + * @var string 应用类库命名空间 + */ + protected $namespace = 'app'; + + /** + * @var bool 应用类库后缀 + */ + protected $suffix = false; + + /** + * @var bool 应用路由检测 + */ + protected $routeCheck; + + /** + * @var bool 严格路由检测 + */ + protected $routeMust; + + protected $dispatch; + protected $file = []; + + public function isDebug() + { + return $this->debug; + } + + public function getModulePath() + { + return $this->modulePath; + } + + public function setModulePath($path) + { + $this->modulePath = $path; + } + + public function getNamespace() + { + return $this->namespace; + } + + public function getSuffix() + { + return $this->suffix; + } + /** + * 执行应用程序 + * @access public + * @param Request $request Request对象 + * @return Response + * @throws Exception + */ + public function run(Request $request = null) + { + is_null($request) && $request = Request::instance(); + + try { + $config = $this->initCommon(); + if (defined('BIND_MODULE')) { + // 模块/控制器绑定 + BIND_MODULE && Route::bind(BIND_MODULE); + } elseif ($config['auto_bind_module']) { + // 入口自动绑定 + $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { + Route::bind($name); + } + } + + $request->filter($config['default_filter']); + + if ($config['lang_switch_on']) { + // 开启多语言机制 检测当前语言 + Lang::detect(); + } else { + // 读取默认语言 + Lang::range($config['default_lang']); + } + $request->langset(Lang::range()); + // 加载系统语言包 + Lang::load([ + THINK_PATH . 'lang' . DS . $request->langset() . EXT, + APP_PATH . 'lang' . DS . $request->langset() . EXT, + ]); + + // 获取应用调度信息 + $dispatch = $this->dispatch; + if (empty($dispatch)) { + // 进行URL路由检测 + $dispatch = self::routeCheck($request, $config); + } + // 记录当前调度信息 + $request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->debug) { + Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); + Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); + Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); + } + + // 监听app_begin + Hook::listen('app_begin', $dispatch); + // 请求缓存检查 + $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); + + switch ($dispatch['type']) { + case 'redirect': + // 执行重定向跳转 + $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']); + break; + case 'module': + // 模块/控制器/操作 + $data = $this->module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null); + break; + case 'controller': + // 执行控制器操作 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']); + break; + case 'method': + // 执行回调方法 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = $this->invokeMethod($dispatch['method'], $vars); + break; + case 'function': + // 执行闭包 + $data = $this->invokeFunction($dispatch['function']); + break; + case 'response': + $data = $dispatch['response']; + break; + default: + throw new \InvalidArgumentException('dispatch type not support'); + } + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + + // 清空类的实例化 + Loader::clearInstance(); + + // 输出数据到客户端 + if ($data instanceof ResponseManager) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $request->isAjax(); + $type = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type'); + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + + // 监听app_end + Hook::listen('app_end', $response); + + return $response; + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param array|string $dispatch 调度信息 + * @param string $type 调度类型 + * @return void + */ + public function dispatch($dispatch, $type = 'module') + { + $this->dispatch = ['type' => $type, $type => $dispatch]; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|array|\Closure $function 函数或者闭包 + * @param array $vars 变量 + * @return mixed + */ + public function invokeFunction($function, $vars = []) + { + $reflect = new \ReflectionFunction($function); + $args = self::bindParams($reflect, $vars); + // 记录执行信息 + $this->debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + return $reflect->invokeArgs($args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param string|array $method 方法 + * @param array $vars 变量 + * @return mixed + */ + public function invokeMethod($method, $vars = []) + { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); + $reflect = new \ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new \ReflectionMethod($method); + } + $args = self::bindParams($reflect, $vars); + + $this->debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 变量 + * @return mixed + */ + public function invokeClass($class, $vars = []) + { + $reflect = new \ReflectionClass($class); + $constructor = $reflect->getConstructor(); + if ($constructor) { + $args = self::bindParams($constructor, $vars); + } else { + $args = []; + } + return $reflect->newInstanceArgs($args); + } + + /** + * 绑定参数 + * @access public + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 变量 + * @return array + */ + private function bindParams($reflect, $vars = []) + { + if (empty($vars)) { + // 自动获取请求变量 + if (Config::get('url_param_type')) { + $vars = Request::instance()->route(); + } else { + $vars = Request::instance()->param(); + } + } + $args = []; + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + if ($reflect->getNumberOfParameters() > 0) { + $params = $reflect->getParameters(); + foreach ($params as $param) { + $name = $param->getName(); + $class = $param->getClass(); + if ($class) { + $className = $class->getName(); + $bind = Request::instance()->$name; + if ($bind instanceof $className) { + $args[] = $bind; + } else { + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + if ($method->isPublic() && $method->isStatic()) { + $args[] = $className::invoke(Request::instance()); + continue; + } + } + $args[] = method_exists($className, 'instance') ? $className::instance() : new $className; + } + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $args[] = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new \InvalidArgumentException('method param miss:' . $name); + } + } + } + return $args; + } + + /** + * 执行模块 + * @access public + * @param array $result 模块/控制器/操作 + * @param array $config 配置参数 + * @param bool $convert 是否自动转换控制器和操作名 + * @return mixed + */ + public function module($result, $config, $convert = null) + { + if (is_string($result)) { + $result = explode('/', $result); + } + $request = Request::instance(); + if ($config['app_multi_module']) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); + $bind = Route::getBind('module'); + $available = false; + if ($bind) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + if (empty($result[0])) { + $module = $bindModule; + $available = true; + } elseif ($module == $bindModule) { + $available = true; + } + } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $request->module($module); + $config = self::init($module); + // 模块请求缓存检查 + $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } else { + // 单一模块部署 + $module = ''; + $request->module($module); + } + // 当前模块路径 + $this->modulePath = APP_PATH . ($module ? $module . DS : ''); + + // 是否自动转换控制器和操作名 + $convert = is_bool($convert) ? $convert : $config['url_convert']; + // 获取控制器名 + $controller = strip_tags($result[1] ?: $config['default_controller']); + $controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $actionName = strip_tags($result[2] ?: $config['default_action']); + $actionName = $convert ? strtolower($actionName) : $actionName; + + // 设置当前请求的控制器、操作 + $request->controller(Loader::parseName($controller, 1))->action($actionName); + + // 监听module_init + Hook::listen('module_init', $request); + + $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']); + if (is_null($instance)) { + throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); + } + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + + $vars = []; + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); + } + + /** + * 初始化应用 + */ + public function initCommon() + { + if (empty($this->init)) { + // 初始化应用 + $config = self::init(); + $this->suffix = $config['class_suffix']; + + // 应用调试模式 + $this->debug = Env::get('app_debug', Config::get('app_debug')); + if (!$this->debug) { + ini_set('display_errors', 'Off'); + } elseif (!IS_CLI) { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册应用命名空间 + $this->namespace = $config['app_namespace']; + Loader::addNamespace($config['app_namespace'], APP_PATH); + if (!empty($config['root_namespace'])) { + Loader::addNamespace($config['root_namespace']); + } + + // 加载额外文件 + if (!empty($config['extra_file_list'])) { + foreach ($config['extra_file_list'] as $file) { + $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; + if (is_file($file) && !isset($this->file[$file])) { + include $file; + $this->file[$file] = true; + } + } + } + + // 设置系统时区 + date_default_timezone_set($config['default_timezone']); + + // 监听app_init + Hook::listen('app_init'); + + $this->init = true; + } + return Config::get(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return array + */ + private function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DS : ''; + + // 加载初始化文件 + if (is_file(APP_PATH . $module . 'init' . EXT)) { + include APP_PATH . $module . 'init' . EXT; + } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { + include RUNTIME_PATH . $module . 'init' . EXT; + } else { + $path = APP_PATH . $module; + // 加载模块配置 + $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + Config::load($filename, 'database'); + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if (strpos($file, CONF_EXT)) { + $filename = $dir . DS . $file; + Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + // 加载应用状态配置 + if ($config['app_status']) { + $config = Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + Hook::import(include CONF_PATH . $module . 'tags' . EXT); + } + + // 加载公共文件 + if (is_file($path . 'common' . EXT)) { + include $path . 'common' . EXT; + } + + // 加载当前模块语言包 + if ($module) { + Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); + } + } + return Config::get(); + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @param \think\Request $request + * @param array $config + * @return array + * @throws \think\Exception + */ + public function routeCheck($request, array $config) + { + $path = $request->path(); + $depr = $config['pathinfo_depr']; + $result = false; + // 路由检测 + $check = !is_null($this->routeCheck) ? $this->routeCheck : $config['url_route_on']; + if ($check) { + // 开启路由 + if (is_file(RUNTIME_PATH . 'route.php')) { + // 读取路由缓存 + $rules = include RUNTIME_PATH . 'route.php'; + if (is_array($rules)) { + Route::rules($rules); + } + } else { + $files = $config['route_config_file']; + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + // 导入路由配置 + $rules = include CONF_PATH . $file . CONF_EXT; + if (is_array($rules)) { + Route::import($rules); + } + } + } + } + + // 路由检测(根据路由定义返回不同的URL调度) + $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); + $must = !is_null($this->routeMust) ? $this->routeMust : $config['url_route_must']; + if ($must && false === $result) { + // 路由无效 + throw new RouteNotFoundException(); + } + } + if (false === $result) { + // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 + $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); + } + return $result; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $route 是否需要检测路由 + * @param bool $must 是否强制检测路由 + * @return void + */ + public function route($route, $must = false) + { + $this->routeCheck = $route; + $this->routeMust = $must; + } +} diff --git a/library/think/manager/CacheManager.php b/library/think/manager/CacheManager.php new file mode 100644 index 00000000..c403633c --- /dev/null +++ b/library/think/manager/CacheManager.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\App; +use think\cache\Driver; +use think\Config; +use think\Log; + +class CacheManager +{ + protected $instance = []; + + /** + * 操作句柄 + * @var object + * @access protected + */ + protected $handler; + + /** + * 连接缓存 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + 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($this->instance[$name])) { + $class = false !== strpos($type, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type); + + // 记录初始化信息 + App::isDebug() && Log::record('[ CACHE ] INIT ' . $type, 'info'); + if (true === $name) { + return new $class($options); + } else { + $this->instance[$name] = new $class($options); + } + } + $this->handler = $this->instance[$name]; + return $this->handler; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @return void + */ + public function init(array $options = []) + { + if (is_null($this->handler)) { + // 自动初始化缓存 + if (!empty($options)) { + $this->connect($options); + } elseif ('complex' == Config::get('cache.type')) { + $this->connect(Config::get('cache.default')); + } else { + $this->connect(Config::get('cache')); + } + } + return $this->handler; + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public function store($name = '') + { + if ('' !== $name && 'complex' == Config::get('cache.type')) { + $this->connect(Config::get('cache.' . $name), strtolower($name)); + } + return $this->handler; + } + + public function __call($method, $args) + { + return call_user_func_array([$this->init(), $method], $args); + } + +} diff --git a/library/think/manager/ConfigManager.php b/library/think/manager/ConfigManager.php new file mode 100644 index 00000000..fb1b1e91 --- /dev/null +++ b/library/think/manager/ConfigManager.php @@ -0,0 +1,170 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +class ConfigManager +{ + // 配置参数 + private $config = []; + // 参数作用域 + private $range = '_sys_'; + + // 设定配置参数的作用域 + public function range($range) + { + $this->range = $range; + if (!isset($this->config[$range])) { + $this->config[$range] = []; + } + } + + /** + * 解析配置文件或内容 + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public function parse($config, $type = '', $name = '', $range = '') + { + $range = $range ?: $this->range; + if (empty($type)) { + $type = pathinfo($config, PATHINFO_EXTENSION); + } + $class = false !== strpos($type, '\\') ? $type : '\\think\\config\\driver\\' . ucwords($type); + return $this->set((new $class())->parse($config), $name, $range); + } + + /** + * 加载配置文件(PHP格式) + * @param string $file 配置文件名 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public function load($file, $name = '', $range = '') + { + $range = $range ?: $this->range; + if (!isset($this->config[$range])) { + $this->config[$range] = []; + } + if (is_file($file)) { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + if ('php' == $type) { + return $this->set(include $file, $name, $range); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return $this->set(yaml_parse_file($file), $name, $range); + } else { + return $this->parse($file, $type, $name, $range); + } + } else { + return $this->config[$range]; + } + } + + /** + * 检测配置是否存在 + * @param string $name 配置参数名(支持二级配置 .号分割) + * @param string $range 作用域 + * @return bool + */ + public function has($name, $range = '') + { + $range = $range ?: $this->range; + + if (!strpos($name, '.')) { + return isset($this->config[$range][strtolower($name)]); + } else { + // 二维数组设置和获取支持 + $name = explode('.', $name); + return isset($this->config[$range][strtolower($name[0])][$name[1]]); + } + } + + /** + * 获取配置参数 为空则获取所有配置 + * @param string $name 配置参数名(支持二级配置 .号分割) + * @param string $range 作用域 + * @return mixed + */ + public function get($name = null, $range = '') + { + $range = $range ?: $this->range; + // 无参数时获取所有 + if (empty($name) && isset($this->config[$range])) { + return $this->config[$range]; + } + + if (!strpos($name, '.')) { + $name = strtolower($name); + return isset($this->config[$range][$name]) ? $this->config[$range][$name] : null; + } else { + // 二维数组设置和获取支持 + $name = explode('.', $name); + $name[0] = strtolower($name[0]); + return isset($this->config[$range][$name[0]][$name[1]]) ? $this->config[$range][$name[0]][$name[1]] : null; + } + } + + /** + * 设置配置参数 name为数组则为批量设置 + * @param string|array $name 配置参数名(支持二级配置 .号分割) + * @param mixed $value 配置值 + * @param string $range 作用域 + * @return mixed + */ + public function set($name, $value = null, $range = '') + { + $range = $range ?: $this->range; + if (!isset($this->config[$range])) { + $this->config[$range] = []; + } + if (is_string($name)) { + if (!strpos($name, '.')) { + $this->config[$range][strtolower($name)] = $value; + } else { + // 二维数组设置和获取支持 + $name = explode('.', $name); + $this->config[$range][strtolower($name[0])][$name[1]] = $value; + } + return; + } elseif (is_array($name)) { + // 批量设置 + if (!empty($value)) { + $this->config[$range][$value] = isset($this->config[$range][$value]) ? + array_merge($this->config[$range][$value], $name) : + $this->config[$range][$value] = $name; + return $this->config[$range][$value]; + } else { + return $this->config[$range] = array_merge($this->config[$range], array_change_key_case($name)); + } + } else { + // 为空直接返回 已有配置 + return $this->config[$range]; + } + } + + /** + * 重置配置参数 + */ + public function reset($range = '') + { + $range = $range ?: $this->range; + if (true === $range) { + $this->config = []; + } else { + $this->config[$range] = []; + } + } +} diff --git a/library/think/manager/CookieManager.php b/library/think/manager/CookieManager.php new file mode 100644 index 00000000..cf8afe4f --- /dev/null +++ b/library/think/manager/CookieManager.php @@ -0,0 +1,213 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\Config; + +class CookieManager +{ + protected $config = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ]; + + protected $init; + + /** + * Cookie初始化 + * @param array $config + * @return void + */ + public function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('cookie'); + } + $this->config = array_merge($this->config, array_change_key_case($config)); + if (!empty($this->config['httponly'])) { + ini_set('session.cookie_httponly', 1); + } + $this->init = true; + } + + /** + * 设置或者获取cookie作用域(前缀) + * @param string $prefix + * @return string|void + */ + public function prefix($prefix = '') + { + if (empty($prefix)) { + return $this->config['prefix']; + } + $this->config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * + * @return mixed + * @internal param mixed $options cookie参数 + */ + public function set($name, $value = '', $option = null) + { + !isset($this->init) && $this->init(); + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + $config = array_merge($this->config, array_change_key_case($option)); + } else { + $config = $this->config; + } + $name = $config['prefix'] . $name; + // 设置cookie + if (is_array($value)) { + array_walk_recursive($value, $this->jsonFormatProtect, 'encode'); + $value = 'think:' . json_encode($value); + } + $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0; + if ($config['setcookie']) { + setcookie($name, $value, $expire, $config['path'], $config['domain'], $config['secure'], $config['httponly']); + } + $_COOKIE[$name] = $value; + } + + /** + * 永久保存Cookie数据 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + $option['expire'] = 315360000; + $this->set($name, $value, $option); + } + + /** + * 判断Cookie数据 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return bool + */ + public function has($name, $prefix = null) + { + !isset($this->init) && $this->init(); + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $name = $prefix . $name; + return isset($_COOKIE[$name]); + } + + /** + * Cookie获取 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return mixed + */ + public function get($name, $prefix = null) + { + !isset($this->init) && $this->init(); + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $name = $prefix . $name; + if (isset($_COOKIE[$name])) { + $value = $_COOKIE[$name]; + if (0 === strpos($value, 'think:')) { + $value = substr($value, 6); + $value = json_decode($value, true); + array_walk_recursive($value, $this->jsonFormatProtect, 'decode'); + } + return $value; + } else { + return; + } + } + + /** + * Cookie删除 + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return mixed + */ + public function delete($name, $prefix = null) + { + !isset($this->init) && $this->init(); + $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']); + } + // 删除指定cookie + unset($_COOKIE[$name]); + } + + /** + * Cookie清空 + * @param string|null $prefix cookie前缀 + * @return mixed + */ + public function clear($prefix = null) + { + // 清除指定前缀的所有cookie + if (empty($_COOKIE)) { + return; + } + !isset($this->init) && $this->init(); + // 要删除的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']); + } + unset($_COOKIE[$key]); + } + } + } + return; + } + + private function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } + +} diff --git a/library/think/manager/LangManager.php b/library/think/manager/LangManager.php new file mode 100644 index 00000000..fa9fb8c0 --- /dev/null +++ b/library/think/manager/LangManager.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\App; +use think\Cookie; +use think\Log; + +class LangManager +{ + // 语言数据 + private $lang = []; + // 语言作用域 + private $range = 'zh-cn'; + // 语言自动侦测的变量 + protected $langDetectVar = 'lang'; + // 语言Cookie变量 + protected $langCookieVar = 'think_var'; + // 语言Cookie的过期时间 + protected $langCookieExpire = 3600; + // 允许语言列表 + protected $allowLangList = []; + + // 设定当前的语言 + public function range($range = '') + { + if ('' == $range) { + return $this->range; + } else { + $this->range = $range; + } + } + + /** + * 设置语言定义(不区分大小写) + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public function set($name, $value = null, $range = '') + { + $range = $range ?: $this->range; + // 批量定义 + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + if (is_array($name)) { + return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; + } else { + return $this->lang[$range][strtolower($name)] = $value; + } + } + + /** + * 加载语言定义(不区分大小写) + * @param string $file 语言文件 + * @param string $range 语言作用域 + * @return mixed + */ + public function load($file, $range = '') + { + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + // 批量定义 + if (is_string($file)) { + $file = [$file]; + } + $lang = []; + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + App::isDebug() && Log::record('[ LANG ] ' . $_file, 'info'); + $_lang = include $_file; + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + if (!empty($lang)) { + $this->lang[$range] = $lang + $this->lang[$range]; + } + return $this->lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function has($name, $range = '') + { + $range = $range ?: $this->range; + return isset($this->lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function get($name = null, $vars = [], $range = '') + { + $range = $range ?: $this->range; + // 空参数返回所有定义 + if (empty($name)) { + return $this->lang[$range]; + } + $key = strtolower($name); + $value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name; + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + + } + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @return string + */ + public function detect() + { + // 自动侦测设置获取语言选择 + $langSet = ''; + if (isset($_GET[$this->langDetectVar])) { + // url中设置了语言变量 + $langSet = strtolower($_GET[$this->langDetectVar]); + Cookie::set($this->langCookieVar, $langSet, $this->langCookieExpire); + } elseif (Cookie::get($this->langCookieVar)) { + // 获取上次用户的选择 + $langSet = strtolower(Cookie::get($this->langCookieVar)); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + Cookie::set($this->langCookieVar, $langSet, $this->langCookieExpire); + } + if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) { + // 合法的语言 + $this->range = $langSet ?: $this->range; + } + if ('zh-hans-cn' == $this->range) { + $this->range = 'zh-cn'; + } + return $this->range; + } + + /** + * 设置语言自动侦测的变量 + * @param string $var 变量名称 + * @return void + */ + public function setLangDetectVar($var) + { + $this->langDetectVar = $var; + } + + /** + * 设置语言的cookie保存变量 + * @param string $var 变量名称 + * @return void + */ + public function setLangCookieVar($var) + { + $this->langCookieVar = $var; + } + + /** + * 设置语言的cookie的过期时间 + * @param string $expire 过期时间 + * @return void + */ + public function setLangCookieExpire($expire) + { + $this->langCookieExpire = $expire; + } + + /** + * 设置允许的语言列表 + * @param array $list 语言列表 + * @return void + */ + public function setAllowLangList($list) + { + $this->allowLangList = $list; + } +} diff --git a/library/think/manager/RequestManager.php b/library/think/manager/RequestManager.php new file mode 100644 index 00000000..5635284c --- /dev/null +++ b/library/think/manager/RequestManager.php @@ -0,0 +1,1604 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\Config; + +class RequestManager +{ + /** + * @var object 对象实例 + */ + protected $instance; + + protected $method; + /** + * @var string 域名(含协议和端口) + */ + protected $domain; + + /** + * @var string URL地址 + */ + protected $url; + + /** + * @var string 基础URL + */ + protected $baseUrl; + + /** + * @var string 当前执行的文件 + */ + protected $baseFile; + + /** + * @var string 访问的ROOT地址 + */ + protected $root; + + /** + * @var string pathinfo + */ + protected $pathinfo; + + /** + * @var string pathinfo(不含后缀) + */ + protected $path; + + /** + * @var array 当前路由信息 + */ + protected $routeInfo = []; + + /** + * @var array 当前调度信息 + */ + protected $dispatch = []; + protected $module; + protected $controller; + protected $action; + // 当前语言集 + protected $langset; + + /** + * @var array 请求参数 + */ + protected $param = []; + protected $get = []; + protected $post = []; + protected $request = []; + protected $route = []; + protected $put; + protected $session = []; + protected $file = []; + protected $cookie = []; + protected $server = []; + protected $header = []; + + /** + * @var array 资源类型 + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'png' => 'image/png', + 'jpg' => 'image/jpg,image/jpeg,image/pjpeg', + 'gif' => 'image/gif', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + protected $content; + + // 全局过滤规则 + protected $filter; + // Hook扩展方法 + protected static $hook = []; + // 绑定的属性 + protected $bind = []; + // php://input + protected $input; + // 请求缓存 + protected $cache; + // 缓存是否检查 + protected $isCheckCache; + + /** + * 架构函数 + * @access public + * @param array $options 参数 + */ + public function __construct($options = []) + { + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + if (is_null($this->filter)) { + $this->filter = Config::get('default_filter'); + } + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function __call($method, $args) + { + if (array_key_exists($method, $this->hook)) { + array_unshift($args, $this); + return call_user_func_array($this->hook[$method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public function hook($method, $callback = null) + { + if (is_array($method)) { + $this->hook = array_merge($this->hook, $method); + } else { + $this->hook[$method] = $callback; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + if (!isset($info['path'])) { + $info['path'] = '/'; + } + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($query, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + return $this; + } + + /** + * 设置或获取当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return string + */ + public function domain($domain = null) + { + if (!is_null($domain)) { + $this->domain = $domain; + return $this; + } elseif (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + return $this->domain; + } + + /** + * 设置或获取当前完整URL 包括QUERY_STRING + * @access public + * @param string|true $url URL地址 true 带域名获取 + * @return string + */ + public function url($url = null) + { + if (!is_null($url) && true !== $url) { + $this->url = $url; + return $this; + } elseif (!$this->url) { + if (IS_CLI) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $this->url = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $this->url = $_SERVER['REQUEST_URI']; + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { + $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + } else { + $this->url = ''; + } + } + return true === $url ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置或获取当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return string + */ + public function baseUrl($url = null) + { + if (!is_null($url) && true !== $url) { + $this->baseUrl = $url; + return $this; + } elseif (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param string $file 当前执行的文件 + * @return string + */ + public function baseFile($file = null) + { + if (!is_null($file) && true !== $file) { + $this->baseFile = $file; + return $this; + } elseif (!$this->baseFile) { + $url = ''; + if (!IS_CLI) { + $script_name = basename($_SERVER['SCRIPT_FILENAME']); + if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { + $url = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['ORIG_SCRIPT_NAME']; + } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { + $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; + } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { + $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + } + } + $this->baseFile = $url; + } + return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置或获取URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string + */ + public function root($url = null) + { + if (!is_null($url) && true !== $url) { + $this->root = $url; + return $this; + } elseif (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + return true === $url ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[Config::get('var_pathinfo')])) { + // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; + unset($_GET[Config::get('var_pathinfo')]); + } elseif (IS_CLI) { + // CLI模式下 index.php module/controller/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } + + // 分析PATHINFO信息 + if (!isset($_SERVER['PATH_INFO'])) { + foreach (Config::get('pathinfo_fetch') as $type) { + if (!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + } + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = Config::get('url_html_suffix'); + $pathinfo = $this->pathinfo(); + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $method true 获取原始请求类型 + * @return string + */ + public function method($method = false) + { + if (true === $method) { + // 获取原始请求类型 + return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); + } elseif (!$this->method) { + if (isset($_POST[Config::get('var_method')])) { + $this->method = strtoupper($_POST[Config::get('var_method')]); + $this->{$this->method}($_POST); + } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } else { + $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); + } + } + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取获取当前请求的参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (empty($this->param)) { + $method = $this->method(true); + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->get(false), $vars, $this->route(false)); + } + if (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); + } + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置获取获取路由参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + $this->param = []; + return $this->route = array_merge($this->route, $name); + } + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 设置获取获取GET参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (empty($this->get)) { + $this->get = $_GET; + } + if (is_array($name)) { + $this->param = []; + return $this->get = array_merge($this->get, $name); + } + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 设置获取获取POST参数 + * @access public + * @param string $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $content = $this->input; + if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } + } + if (is_array($name)) { + $this->param = []; + return $this->post = array_merge($this->post, $name); + } + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 设置获取获取PUT参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_null($this->put)) { + $content = $this->input; + if (false !== strpos($this->contentType(), 'application/json')) { + $this->put = (array) json_decode($content, true); + } else { + parse_str($content, $this->put); + } + } + if (is_array($name)) { + $this->param = []; + return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); + } + + return $this->input($this->put, $name, $default, $filter); + } + + /** + * 设置获取获取DELETE参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 设置获取获取PATCH参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @param string $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + if (is_array($name)) { + $this->param = []; + return $this->request = array_merge($this->request, $name); + } + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function session($name = '', $default = null, $filter = '') + { + if (empty($this->session)) { + $this->session = Session::get(); + } + if (is_array($name)) { + return $this->session = array_merge($this->session, $name); + } + return $this->input($this->session, $name, $default, $filter); + } + + /** + * 获取cookie参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = $_COOKIE; + } + if (is_array($name)) { + return $this->cookie = array_merge($this->cookie, $name); + } + return $this->input($this->cookie, $name, $default, $filter); + } + + /** + * 获取server参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function server($name = '', $default = null, $filter = '') + { + if (empty($this->server)) { + $this->server = $_SERVER; + } + if (is_array($name)) { + return $this->server = array_merge($this->server, $name); + } + return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 获取上传的文件信息 + * @access public + * @param string|array $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + if (is_array($name)) { + return $this->file = array_merge($this->file, $name); + } + $files = $this->file; + if (!empty($files)) { + // 处理上传文件 + $array = []; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + for ($i = 0; $i < $count; $i++) { + if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { + continue; + } + $temp['key'] = $key; + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { + continue; + } + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + } + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + return; + } + + /** + * 获取环境变量 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function env($name = '', $default = null, $filter = '') + { + if (empty($this->env)) { + $this->env = $_ENV; + } + if (is_array($name)) { + return $this->env = array_merge($this->env, $name); + } + return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string|array $name header名称 + * @param string $default 默认值 + * @return string + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server ?: $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + $this->header = array_change_key_case($header); + } + if (is_array($name)) { + return $this->header = array_merge($this->header, $name); + } + if ('' === $name) { + return $this->header; + } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } else { + $type = 's'; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + // 无输入数据,返回默认值 + return $default; + } + } + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter)) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } else { + $this->filter = $filter; + } + } + + /** + * 递归过滤给定的值 + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + return $this->filterExp($value); + } + + /** + * 过滤表单中的表达式 + * @param string $value + * @return void + */ + public function filterExp(&$value) + { + // 过滤查询特殊字符 + if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { + $value .= ' '; + } + // TODO 其他安全过滤 + } + + /** + * 强制类型转换 + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + default: + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + $item = []; + foreach ($name as $key) { + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } + } + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + $server = array_merge($_SERVER, $this->server); + if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + return true; + } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + return true; + } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + return true; + } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + return true; + } + return false; + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); + $result = ('xmlhttprequest' == $value) ? true : false; + if (true === $ajax) { + return $result; + } else { + return $this->param(Config::get('var_ajax')) ? true : $result; + } + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { + return $result; + } else { + return $this->param(Config::get('var_pjax')) ? true : $result; + } + } + + /** + * 获取客户端IP地址 + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = false) + { + $type = $type ? 1 : 0; + static $ip = null; + if (null !== $ip) { + return $ip[$type]; + } + + if ($adv) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + // IP地址合法验证 + $long = sprintf("%u", ip2long($ip)); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + return true; + } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + return true; + } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + return true; + } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } else { + return false; + } + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 当前请求的host + * @access public + * @return string + */ + public function host() + { + return $this->server('HTTP_HOST'); + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return integer + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo($route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } else { + return $this->routeInfo; + } + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param array $dispatch 调度信息 + * @return array + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + return $this->dispatch; + } + + /** + * 设置或者获取当前的模块名 + * @access public + * @param string $module 模块名 + * @return string|Request + */ + public function module($module = null) + { + if (!is_null($module)) { + $this->module = $module; + return $this; + } else { + return $this->module ?: ''; + } + } + + /** + * 设置或者获取当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return string|Request + */ + public function controller($controller = null) + { + if (!is_null($controller)) { + $this->controller = $controller; + return $this; + } else { + return $this->controller ?: ''; + } + } + + /** + * 设置或者获取当前的操作名 + * @access public + * @param string $action 操作名 + * @return string|Request + */ + public function action($action = null) + { + if (!is_null($action)) { + $this->action = $action; + return $this; + } else { + return $this->action ?: ''; + } + } + + /** + * 设置或者获取当前的语言 + * @access public + * @param string $lang 语言名 + * @return string|Request + */ + public function langset($lang = null) + { + if (!is_null($lang)) { + $this->langset = $lang; + return $this; + } else { + return $this->langset ?: ''; + } + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = 'md5') + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + if ($this->isAjax()) { + header($name . ': ' . $token); + } + Session::set($name, $token); + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @return void + */ + public function cache($key, $expire = null, $except = []) + { + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === strpos($this->url(), $rule)) { + return; + } + } + // 自动缓存功能 + $key = md5($this->host()) . '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url())], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + if (isset($fun)) { + $key = $fun($key); + } + + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + // 读取缓存 + $response = Response::create()->code(304); + throw new \think\exception\HttpResponseException($response); + } elseif (Cache::has($key)) { + list($content, $header) = Cache::get($key); + $response = Response::create($content)->header($header); + throw new \think\exception\HttpResponseException($response); + } else { + $this->cache = [$key, $expire]; + } + } + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置当前请求绑定的对象实例 + * @access public + * @param string $name 绑定的对象标识 + * @param mixed $obj 绑定的对象实例 + * @return mixed + */ + public function bind($name, $obj = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $obj; + } + } + + public function __set($name, $value) + { + $this->bind[$name] = $value; + } + + public function __get($name) + { + return isset($this->bind[$name]) ? $this->bind[$name] : null; + } + + public function __isset($name) + { + return isset($this->bind[$name]); + } +} diff --git a/library/think/manager/ResponseManager.php b/library/think/manager/ResponseManager.php new file mode 100644 index 00000000..a76eba91 --- /dev/null +++ b/library/think/manager/ResponseManager.php @@ -0,0 +1,333 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\Config; +use think\Debug; +use think\Env; +use think\Hook; +use think\Request; +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; +use think\Session; + +class ResponseManager +{ + + // 原始数据 + protected $data; + + // 当前的contentType + protected $contentType = 'text/html'; + + // 字符集 + protected $charset = 'utf-8'; + + //状态 + protected $code = 200; + + // 输出参数 + protected $options = []; + // header参数 + protected $header = []; + + protected $content = null; + + /** + * 架构函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function init($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + $this->header = $header; + $this->code = $code; + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->contentType($this->contentType, $this->charset); + return $this; + } + + /** + * 创建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 + */ + public function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $type = empty($type) ? 'null' : strtolower($type); + + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst($type); + if (class_exists($class)) { + return new $class($data, $code, $header, $options); + } else { + return $this->init($data, $code, $header, $options); + } + } + + /** + * 发送数据到客户端 + * @access public + * @return mixed + * @throws \InvalidArgumentException + */ + public function send() + { + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if (Env::get('app_trace', Config::get('app_trace'))) { + Debug::inject($this, $data); + } + + if (200 == $this->code) { + $cache = Request::instance()->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::set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + header($name . ':' . $val); + } + } + + echo $data; + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($options = []) + { + $this->options = array_merge($this->options, $options); + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + return $this; + } + + /** + * 设置响应头 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + return $this; + } + + /** + * 设置页面输出内容 + * @param $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + return $this; + } + + /** + * LastModified + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + return $this; + } + + /** + * Expires + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + return $this; + } + + /** + * ETag + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + return $this; + } + + /** + * 页面缓存控制 + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + return $this; + } + + /** + * 页面输出类型 + * @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 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } + } + + /** + * 获取原始数据 + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @return mixed + */ + public function getContent() + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + return $this->content; + } + + /** + * 获取状态码 + * @return integer + */ + public function getCode() + { + return $this->code; + } +} diff --git a/library/think/manager/RouteManager.php b/library/think/manager/RouteManager.php new file mode 100644 index 00000000..7b6cf5a6 --- /dev/null +++ b/library/think/manager/RouteManager.php @@ -0,0 +1,1608 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\Config; +use think\exception\HttpException; +use think\Loader; +use think\Request; + +class RouteManager +{ + + // 路由规则 + private $rules = [ + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => [], + ]; + + // REST路由操作方法定义 + private $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '/:id/edit', 'edit'], + 'read' => ['get', '/:id', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/:id', 'update'], + 'delete' => ['delete', '/:id', 'delete'], + ]; + + // 不同请求类型的方法前缀 + private $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + // 子域名 + private $subDomain = ''; + // 域名绑定 + private $bind = []; + // 当前分组信息 + private $group = []; + // 当前子域名绑定 + private $domainBind; + private $domainRule; + // 当前域名 + private $domain; + // 当前路由执行过程中的参数 + private $option = []; + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return void + */ + public function pattern($name = null, $rule = '') + { + if (is_array($name)) { + $this->rules['pattern'] = array_merge($this->rules['pattern'], $name); + } else { + $this->rules['pattern'][$name] = $rule; + } + return $this; + } + + /** + * 注册子域名部署规则 + * @access public + * @param string|array $domain 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function domain($domain, $rule = '', $option = [], $pattern = []) + { + if (is_array($domain)) { + foreach ($domain as $key => $item) { + $this->domain($key, $item, $option, $pattern); + } + } elseif ($rule instanceof \Closure) { + // 执行闭包 + $this->setDomain($domain); + call_user_func_array($rule, []); + $this->setDomain(null); + } elseif (is_array($rule)) { + $this->setDomain($domain); + $this->group('', function () use ($rule) { + // 动态注册域名的路由规则 + $this->registerRules($rule); + }, $option, $pattern); + $this->setDomain(null); + } else { + $this->rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; + } + return $this; + } + + private function setDomain($domain) + { + $this->domain = $domain; + } + + /** + * 设置路由绑定 + * @access public + * @param mixed $bind 绑定信息 + * @param string $type 绑定类型 默认为module 支持 namespace class controller + * @return mixed + */ + public function bind($bind, $type = 'module') + { + $this->bind = ['type' => $type, $type => $bind]; + return $this; + } + + /** + * 设置或者获取路由标识 + * @access public + * @param string|array $name 路由命名标识 数组表示批量设置 + * @param array $value 路由地址及变量信息 + * @return array + */ + public function name($name = '', $value = null) + { + if (is_array($name)) { + $this->rules['name'] = $name; + return $this; + } elseif ('' === $name) { + return $this->rules['name']; + } elseif (!is_null($value)) { + $this->rules['name'][strtolower($name)][] = $value; + return $this; + } else { + $name = strtolower($name); + return isset($this->rules['name'][$name]) ? $this->rules['name'][$name] : null; + } + } + + /** + * 读取路由绑定 + * @access public + * @param string $type 绑定类型 + * @return mixed + */ + public function getBind($type) + { + return isset($this->bind[$type]) ? $this->bind[$type] : null; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rule 路由规则 + * @param string $type 请求类型 + * @return void + */ + public function import(array $rule, $type = '*') + { + // 检查域名部署 + if (isset($rule['__domain__'])) { + $this->domain($rule['__domain__']); + unset($rule['__domain__']); + } + + // 检查变量规则 + if (isset($rule['__pattern__'])) { + $this->pattern($rule['__pattern__']); + unset($rule['__pattern__']); + } + + // 检查路由别名 + if (isset($rule['__alias__'])) { + $this->alias($rule['__alias__']); + unset($rule['__alias__']); + } + + // 检查资源路由 + if (isset($rule['__rest__'])) { + $this->resource($rule['__rest__']); + unset($rule['__rest__']); + } + + $this->registerRules($rule, strtolower($type)); + return $this; + } + + // 批量注册路由 + protected function registerRules($rules, $type = '*') + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (empty($val)) { + continue; + } + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + $this->group($key, $val); + } elseif (is_array($val)) { + $this->setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + $this->setRule($key, $val, $type); + } + } + + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) + { + $group = $this->getGroup('name'); + if (!is_null($group)) { + // 路由分组 + $option = array_merge($this->getGroup('option'), $option); + $pattern = array_merge($this->getGroup('pattern'), $pattern); + } + + $type = strtolower($type); + + if (strpos($type, '|')) { + $option['method'] = $type; + $type = '*'; + } + if (is_array($rule) && empty($route)) { + foreach ($rule as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, $val[1]); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $route = $val; + } + $this->setRule($key, $route, $type, isset($option1) ? $option1 : $option, isset($pattern1) ? $pattern1 : $pattern, $group); + } + } else { + $this->setRule($rule, $route, $type, $option, $pattern, $group); + } + return $this; + } + + /** + * 设置路由规则 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param string $group 所属分组 + * @return void + */ + protected function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') + { + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } + if (!isset($option['complete_match'])) { + if (Config::get('route_complete_match')) { + $option['complete_match'] = true; + } elseif ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { + $rule = trim($rule, '/'); + } + $vars = $this->parseVar($rule); + if (isset($name)) { + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + $this->name($name, [$key, $vars, $this->domain, $suffix]); + } + if ($group) { + if ('*' != $type) { + $option['method'] = $type; + } + if ($this->domain) { + $this->rules['domain'][$this->domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + $this->rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + } else { + if ('*' != $type && isset($this->rules['*'][$rule])) { + unset($this->rules['*'][$rule]); + } + if ($this->domain) { + $this->rules['domain'][$this->domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + $this->rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + if ('*' == $type) { + // 注册路由快捷方式 + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if ($this->domain) { + $this->rules['domain'][$this->domain][$method][$rule] = true; + } else { + $this->rules[$method][$rule] = true; + } + } + } + } + } + + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected function setOption($options = []) + { + $this->option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public function getOption() + { + return $this->option; + } + + /** + * 获取当前的分组信息 + * @access public + * @param string $type 分组信息名称 name option pattern + * @return mixed + */ + public function getGroup($type) + { + if (isset($this->group[$type])) { + return $this->group[$type]; + } else { + return 'name' == $type ? null : []; + } + } + + /** + * 设置当前的路由分组 + * @access public + * @param string $name 分组名称 + * @param array $option 分组路由参数 + * @param array $pattern 分组变量规则 + * @return void + */ + public function setGroup($name, $option = [], $pattern = []) + { + $this->group['name'] = $name; + $this->group['option'] = $option ?: []; + $this->group['pattern'] = $pattern ?: []; + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $routes 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function group($name, $routes, $option = [], $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + // 分组 + $currentGroup = $this->getGroup('name'); + if ($currentGroup) { + $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); + } + if (!empty($name)) { + if ($routes instanceof \Closure) { + $currentOption = $this->getGroup('option'); + $currentPattern = $this->getGroup('pattern'); + $this->setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + $this->setGroup($currentGroup, $currentOption, $currentPattern); + if ($currentGroup != $name) { + $this->rules['*'][$name]['route'] = ''; + $this->rules['*'][$name]['var'] = $this->parseVar($name); + $this->rules['*'][$name]['option'] = $option; + $this->rules['*'][$name]['pattern'] = $pattern; + } + } else { + $item = []; + foreach ($routes as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $route = $val; + } + + $options = isset($option1) ? $option1 : $option; + $patterns = isset($pattern1) ? $pattern1 : $pattern; + if ('$' == substr($key, -1, 1)) { + // 是否完整匹配 + $options['complete_match'] = true; + $key = substr($key, 0, -1); + } + $key = trim($key, '/'); + $vars = $this->parseVar($key); + $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; + // 设置路由标识 + $suffix = isset($options['ext']) ? $options['ext'] : null; + $this->name($route, [$name . ($key ? '/' . $key : ''), $vars, $this->domain, $suffix]); + } + $this->rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; + } + + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (!isset($this->rules[$method][$name])) { + $this->rules[$method][$name] = true; + } elseif (is_array($this->rules[$method][$name])) { + $this->rules[$method][$name] = array_merge($this->rules['*'][$name], $this->rules[$method][$name]); + } + } + + } elseif ($routes instanceof \Closure) { + // 闭包注册 + $currentOption = $this->getGroup('option'); + $currentPattern = $this->getGroup('pattern'); + $this->setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + $this->setGroup($currentGroup, $currentOption, $currentPattern); + } else { + // 批量注册路由 + $this->rule($routes, '', '*', $option, $pattern); + } + return $this; + } + + /** + * 注册路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function any($rule, $route = '', $option = [], $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function get($rule, $route = '', $option = [], $pattern = []) + { + return $this->rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function post($rule, $route = '', $option = [], $pattern = []) + { + return $this->rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function put($rule, $route = '', $option = [], $pattern = []) + { + return $this->rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function delete($rule, $route = '', $option = [], $pattern = []) + { + return $this->rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function patch($rule, $route = '', $option = [], $pattern = []) + { + return $this->rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function resource($rule, $route = '', $option = [], $pattern = []) + { + if (is_array($rule)) { + foreach ($rule as $key => $val) { + if (is_array($val)) { + list($val, $option, $pattern) = array_pad($val, 3, []); + } + $this->resource($key, $val, $option, $pattern); + } + } else { + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + foreach ($array as $val) { + $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + } + $rule = implode('/', $item) . '/' . $last; + } + // 注册资源路由 + foreach ($this->rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { + $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); + } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { + $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); + } + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; + $this->rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); + } + } + return $this; + } + + /** + * 注册控制器路由 操作方法对应不同的请求后缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function controller($rule, $route = '', $option = [], $pattern = []) + { + foreach ($this->methodPrefix as $type => $val) { + $this->$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + } + return $this; + } + + /** + * 注册别名路由 + * @access public + * @param string|array $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return void + */ + public function alias($rule = null, $route = '', $option = []) + { + if (is_array($rule)) { + $this->rules['alias'] = array_merge($this->rules['alias'], $rule); + } else { + $this->rules['alias'][$rule] = $option ? [$route, $option] : $route; + } + return $this; + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return void + */ + public function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + $this->methodPrefix = array_merge($this->methodPrefix, array_change_key_case($method)); + } else { + $this->methodPrefix[strtolower($method)] = $prefix; + } + return $this; + } + + /** + * rest方法定义和修改 + * @access public + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return void + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return void + */ + public function miss($route, $method = '*', $option = []) + { + $this->rule('__miss__', $route, $method, $option, []); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return void + */ + public function auto($route) + { + $this->rule('__auto__', $route, '*', [], []); + } + + /** + * 获取或者批量设置路由定义 + * @access public + * @param mixed $rules 请求类型或者路由定义数组 + * @return array + */ + public function rules($rules = '') + { + if (is_array($rules)) { + $this->rules = $rules; + } elseif ($rules) { + return true === $rules ? $this->rules : $this->rules[strtolower($rules)]; + } else { + $rules = $this->rules; + unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); + return $rules; + } + } + + /** + * 检测子域名部署 + * @access public + * @param Request $request Request请求对象 + * @param array $currentRules 当前路由规则 + * @param string $method 请求类型 + * @return void + */ + public function checkDomain($request, &$currentRules, $method = 'get') + { + // 域名规则 + $rules = $this->rules['domain']; + // 开启子域名部署 支持二级和三级域名 + if (!empty($rules)) { + $host = $request->host(); + if (isset($rules[$host])) { + // 完整域名或者IP配置 + $item = $rules[$host]; + } else { + $rootDomain = Config::get('url_domain_root'); + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); + } else { + $domain = explode('.', $host, -2); + } + // 子域名配置 + if (!empty($domain)) { + // 当前子域名 + $subDomain = implode('.', $domain); + $this->subDomain = $subDomain; + $domain2 = array_pop($domain); + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + if ($subDomain && isset($rules[$subDomain])) { + // 子域名配置 + $item = $rules[$subDomain]; + } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $rules['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($rules['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $rules['*']; + $panDomain = $domain2; + } + } + } + } + if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } + if (isset($item['[bind]'])) { + // 解析子域名部署规则 + list($rule, $option, $pattern) = $item['[bind]']; + if (!empty($option['https']) && !$request->isSsl()) { + // https检测 + throw new HttpException(404, 'must use https request:' . $host); + } + + if (strpos($rule, '?')) { + // 传入其它参数 + $array = parse_url($rule); + $result = $array['path']; + parse_str($array['query'], $params); + if (isset($panDomain)) { + $pos = array_search('*', $params); + if (false !== $pos) { + // 泛域名作为参数 + $params[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET, $params); + } else { + $result = $rule; + } + + if (0 === strpos($result, '\\')) { + // 绑定到命名空间 例如 \app\index\behavior + $this->bind = ['type' => 'namespace', 'namespace' => $result]; + } elseif (0 === strpos($result, '@')) { + // 绑定到类 例如 @app\index\controller\User + $this->bind = ['type' => 'class', 'class' => substr($result, 1)]; + } else { + // 绑定到模块/控制器 例如 index/user + $this->bind = ['type' => 'module', 'module' => $result]; + } + $this->domainBind = true; + } else { + $this->domainRule = $item; + $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; + } + } + } + } + + /** + * 检测URL路由 + * @access public + * @param Request $request Request请求对象 + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $checkDomain 是否检测域名规则 + * @return false|array + */ + public function check($request, $url, $depr = '/', $checkDomain = false) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace($depr, '|', $url); + + if (isset($this->rules['alias'][$url]) || isset($this->rules['alias'][strstr($url, '|', true)])) { + // 检测路由别名 + $result = $this->checkRouteAlias($request, $url, $depr); + if (false !== $result) { + return $result; + } + } + $method = strtolower($request->method()); + // 获取当前请求类型的路由规则 + $rules = $this->rules[$method]; + // 检测域名部署 + if ($checkDomain) { + $this->checkDomain($request, $rules, $method); + } + // 检测URL绑定 + $return = $this->checkUrlBind($url, $rules, $depr); + if (false !== $return) { + return $return; + } + if ('|' != $url) { + $url = rtrim($url, '|'); + } + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { + // 静态路由规则检测 + $rule = $rules[$item]; + if (true === $rule) { + $rule = $this->getRouteExpress($item); + } + if (!empty($rule['route']) && $this->checkOption($rule['option'], $request)) { + $this->setOption($rule['option']); + return $this->parseRule($item, $rule['route'], $url, $rule['option']); + } + } + + // 路由规则检测 + if (!empty($rules)) { + return $this->checkRoute($request, $rules, $url, $depr); + } + return false; + } + + private function getRouteExpress($key) + { + return $this->domainRule ? $this->domainRule['*'][$key] : $this->rules['*'][$key]; + } + + /** + * 检测路由规则 + * @access private + * @param Request $request + * @param array $rules 路由规则 + * @param string $url URL地址 + * @param string $depr URL分割符 + * @param string $group 路由分组名 + * @param array $options 路由参数(分组) + * @return mixed + */ + private function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) + { + foreach ($rules as $key => $item) { + if (true === $item) { + $item = $this->getRouteExpress($key); + } + if (!isset($item['rule'])) { + continue; + } + $rule = $item['rule']; + $route = $item['route']; + $vars = $item['var']; + $option = $item['option']; + $pattern = $item['pattern']; + + // 检查参数有效性 + if (!$this->checkOption($option, $request)) { + continue; + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + } + + if (is_array($rule)) { + // 分组路由 + $pos = strpos(str_replace('<', ':', $key), ':'); + if (false !== $pos) { + $str = substr($key, 0, $pos); + } else { + $str = $key; + } + if (is_string($str) && $str && 0 !== strpos(str_replace('|', '/', $url), $str)) { + continue; + } + $this->setOption($option); + $result = $this->checkRoute($request, $rule, $url, $depr, $key, $option); + if (false !== $result) { + return $result; + } + } elseif ($route) { + if ('__miss__' == $rule || '__auto__' == $rule) { + // 指定特殊路由 + $var = trim($rule, '__'); + ${$var} = $item; + continue; + } + if ($group) { + $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + $this->setOption($option); + if (isset($options['bind_model']) && isset($option['bind_model'])) { + $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); + } + $result = $this->checkRule($rule, $route, $url, $pattern, $option, $depr); + if (false !== $result) { + return $result; + } + } + } + if (isset($auto)) { + // 自动解析URL地址 + return $this->parseUrl($auto['route'] . '/' . $url, $depr); + } elseif (isset($miss)) { + // 未匹配所有路由的路由规则处理 + return $this->parseRule('', $miss['route'], $url, $miss['option']); + } + return false; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return mixed + */ + private function checkRouteAlias($request, $url, $depr) + { + $array = explode('|', $url); + $alias = array_shift($array); + $item = $this->rules['alias'][$alias]; + + if (is_array($item)) { + list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } + } else { + $rule = $item; + } + $bind = implode('|', $array); + // 参数有效性检查 + if (isset($option) && !$this->checkOption($option, $request)) { + // 路由不匹配 + return false; + } elseif (0 === strpos($rule, '\\')) { + // 路由到类 + return $this->bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return $this->bindToController($bind, substr($rule, 1), $depr); + } else { + // 路由到模块/控制器 + return $this->bindToModule($bind, $rule, $depr); + } + } + + /** + * 检测URL绑定 + * @access private + * @param string $url URL地址 + * @param array $rules 路由规则 + * @param string $depr URL分隔符 + * @return mixed + */ + private function checkUrlBind(&$url, &$rules, $depr = '/') + { + if (!empty($this->bind)) { + $type = $this->bind['type']; + $bind = $this->bind[$type]; + // 记录绑定信息 + App::isDebug() && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); + // 如果有URL绑定 则进行绑定检测 + switch ($type) { + case 'class': + // 绑定到类 + return $this->bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return $this->bindToController($url, $bind, $depr); + case 'namespace': + // 绑定到命名空间 + return $this->bindToNamespace($url, $bind, $depr); + } + } + return false; + } + + /** + * 绑定到类 + * @access public + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public function bindToClass($url, $class, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + $this->parseUrlParams($array[1]); + } + return ['type' => 'method', 'method' => [$class, $action]]; + } + + /** + * 绑定到命名空间 + * @access public + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @param string $depr URL分隔符 + * @return array + */ + public function bindToNamespace($url, $namespace, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); + if (!empty($array[2])) { + $this->parseUrlParams($array[2]); + } + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method]]; + } + + /** + * 绑定到控制器类 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @param string $depr URL分隔符 + * @return array + */ + public function bindToController($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + $this->parseUrlParams($array[1]); + } + return ['type' => 'controller', 'controller' => $controller . '/' . $action]; + } + + /** + * 绑定到模块/控制器 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public function bindToModule($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + $this->parseUrlParams($array[1]); + } + return ['type' => 'module', 'module' => $controller . '/' . $action]; + } + + /** + * 路由参数有效性检查 + * @access private + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + private function checkOption($option, $request) + { + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], $this->subDomain])) // 域名检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 + || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 + ) { + return false; + } + return true; + } + + /** + * 检测路由规则 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $pattern 变量规则 + * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) + * @return array|false + */ + private function checkRule($rule, $route, $url, $pattern, $option, $depr) + { + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + } + + $len1 = substr_count($url, '|'); + $len2 = substr_count($rule, '/'); + // 多余参数是否合并 + $merge = !empty($option['merge_extra_vars']) ? true : false; + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } + + if ($len1 >= $len2 || strpos($rule, '[')) { + if (!empty($option['complete_match'])) { + // 完整匹配 + if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { + return false; + } + } + $pattern = array_merge($this->rules['pattern'], $pattern); + if (false !== $match = $this->match($url, $rule, $pattern, $merge)) { + // 匹配到路由规则 + return $this->parseRule($rule, $route, $url, $option, $match, $merge); + } + } + return false; + } + + /** + * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... + * @access public + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $autoSearch 是否自动深度搜索控制器 + * @return array + */ + public function parseUrl($url, $depr = '/', $autoSearch = false) + { + + if (isset($this->bind['module'])) { + $bind = str_replace('/', $depr, $this->bind['module']); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + $url = str_replace($depr, '|', $url); + list($path, $var) = $this->parseUrlPath($url); + $route = [null, null, null]; + if (isset($path)) { + // 解析模块 + $module = Config::get('app_multi_module') ? array_shift($path) : null; + if ($autoSearch) { + // 自动搜索控制器 + $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); + $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; + $item = []; + $find = false; + foreach ($path as $val) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= DS . Loader::parseName($val); + } + } + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + // 解析额外参数 + $this->parseUrlParams(empty($path) ? '' : implode('|', $path)); + // 封装路由 + $route = [$module, $controller, $action]; + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset($this->rules['name'][$name]) || isset($this->rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + } + return ['type' => 'module', 'module' => $route]; + } + + /** + * 解析URL的pathinfo参数和变量 + * @access private + * @param string $url URL地址 + * @return array + */ + private function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } elseif (false !== strpos($url, '=')) { + // 参数1=值1&参数2=值2... + parse_str($url, $var); + } else { + $path = [$url]; + } + return [$path, $var]; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param string $rule 路由规则 + * @param array $pattern 变量规则 + * @return array|false + */ + private function match($url, $rule, $pattern) + { + $m2 = explode('/', $rule); + $m1 = explode('|', $url); + + $var = []; + foreach ($m2 as $key => $val) { + // val中定义了多个变量 + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + $value = []; + $replace = []; + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; + } else { + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; + } + $value[] = $name; + } + $val = str_replace($matches[0], $replace, $val); + if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; + } + } + continue; + } else { + return false; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $val = substr($val, 1, -1); + $optional = true; + } else { + $optional = false; + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + if (!$optional && !isset($m1[$key])) { + return false; + } + if (isset($m1[$key]) && isset($pattern[$name])) { + // 检查变量规则 + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } + } + $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + return false; + } + } + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 解析规则路由 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $pathinfo URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return array + */ + private function parseRule($rule, $route, $pathinfo, $option = [], $matches = []) + { + $request = Request::instance(); + // 解析路由规则 + if ($rule) { + $rule = explode('/', $rule); + // 获取URL地址中的参数 + $paths = explode('|', $pathinfo); + foreach ($rule as $item) { + $fun = ''; + if (0 === strpos($item, '[:')) { + $item = substr($item, 1, -1); + } + if (0 === strpos($item, ':')) { + $var = substr($item, 1); + $matches[$var] = array_shift($paths); + } else { + // 过滤URL中的静态变量 + array_shift($paths); + } + } + } else { + $paths = explode('|', $pathinfo); + } + + // 获取路由地址规则 + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 绑定模型数据 + if (isset($option['bind_model'])) { + $bind = []; + foreach ($option['bind_model'] as $key => $val) { + if ($val instanceof \Closure) { + $result = call_user_func_array($val, [$matches]); + } else { + if (is_array($val)) { + $fields = explode('&', $val[1]); + $model = $val[0]; + $exception = isset($val[2]) ? $val[2] : true; + } else { + $fields = ['id']; + $model = $val; + $exception = true; + } + $where = []; + $match = true; + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[$field] = $matches[$field]; + } + } + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + if (!empty($result)) { + $bind[$key] = $result; + } + } + $request->bind($bind); + } + + // 解析额外参数 + $this->parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); + // 记录匹配的路由信息 + $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); + + // 检测路由after行为 + if (!empty($option['after_behavior'])) { + if ($option['after_behavior'] instanceof \Closure) { + $result = call_user_func_array($option['after_behavior'], []); + } else { + foreach ((array) $option['after_behavior'] as $behavior) { + $result = Hook::exec($behavior, ''); + if (!is_null($result)) { + break; + } + } + } + // 路由规则重定向 + if ($result instanceof Response) { + return ['type' => 'response', 'response' => $result]; + } elseif (is_array($result)) { + return $result; + } + } + + if ($route instanceof \Closure) { + // 执行闭包 + $result = ['type' => 'function', 'function' => $route]; + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + list($path, $var) = $this->parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $route = substr($route, 1); + list($route, $var) = $this->parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::setModulePath(APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '')); + } else { + // 路由到模块/控制器/操作 + $result = $this->parseModule($route); + } + // 开启请求缓存 + if ($request->isGet() && isset($option['cache'])) { + $cache = $option['cache']; + if (is_array($cache)) { + list($key, $expire) = $cache; + } else { + $key = str_replace('|', '/', $pathinfo); + $expire = $cache; + } + $request->cache($key, $expire); + } + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access private + * @param string $url URL地址 + * @return array + */ + private function parseModule($url) + { + list($path, $var) = $this->parseUrlPath($url); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = Request::instance()->method(); + if (Config::get('use_action_prefix') && !empty($this->methodPrefix[$method])) { + // 操作方法前缀支持 + $action = 0 !== strpos($action, $this->methodPrefix[$method]) ? $this->methodPrefix[$method] . $action : $action; + } + // 设置当前请求的路由变量 + Request::instance()->route($var); + // 路由到模块/控制器/操作 + return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false]; + } + + /** + * 解析URL地址中的参数Request对象 + * @access private + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + private function parseUrlParams($url, &$var = []) + { + if ($url) { + if (Config::get('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + // 设置当前请求的参数 + Request::instance()->route($var); + } + + // 分析路由规则中的变量 + private function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + foreach (explode('/', $rule) as $val) { + $optional = false; + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $optional = true; + } else { + $optional = false; + } + $var[$name] = $optional ? 2 : 1; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $optional = true; + $val = substr($val, 1, -1); + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + $var[$name] = $optional ? 2 : 1; + } + } + return $var; + } +} diff --git a/library/think/manager/SessionManager.php b/library/think/manager/SessionManager.php new file mode 100644 index 00000000..95776840 --- /dev/null +++ b/library/think/manager/SessionManager.php @@ -0,0 +1,368 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\App; +use think\Config; +use think\exception\ClassNotFoundException; +use think\Log; + +class SessionManager +{ + protected $prefix = ''; + protected $init = null; + + /** + * 设置或者获取session作用域(前缀) + * @param string $prefix + * @return string|void + */ + public function prefix($prefix = '') + { + if (empty($prefix) && null !== $prefix) { + return $this->prefix; + } else { + $this->prefix = $prefix; + } + } + + /** + * session初始化 + * @param array $config + * @return void + * @throws \think\Exception + */ + public function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('session'); + } + // 记录初始化信息 + App::isDebug() && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + if (isset($config['name'])) { + session_name($config['name']); + } + if (isset($config['path'])) { + session_save_path($config['path']); + } + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + if ($isDoStart) { + session_start(); + $this->init = true; + } else { + $this->init = false; + } + } + + /** + * session自动启动或者初始化 + * @return void + */ + public function boot() + { + if (is_null($this->init)) { + $this->init(); + } elseif (false === $this->init) { + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } + $this->init = true; + } + } + + /** + * session设置 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function set($name, $value = '', $prefix = null) + { + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + } + + /** + * session获取 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public function get($name = '', $prefix = null) + { + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->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; + } + } + return $value; + } + + /** + * session获取并删除 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public function pull($name, $prefix = null) + { + $result = $this->get($name, $prefix); + if ($result) { + $this->delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function flash($name, $value) + { + $this->set($name, $value); + if (!$this->has('__flash__.__time__')) { + $this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + $this->push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public function flush() + { + if ($this->init) { + $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 作用域(前缀) + * @return void + */ + public function delete($name, $prefix = null) + { + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if (is_array($name)) { + foreach ($name as $key) { + $this->delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function clear($prefix = null) + { + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public function has($name, $prefix = null) + { + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + 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]); + } + } + + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + $this->set($key, $array); + } + + /** + * 启动session + * @return void + */ + public function start() + { + session_start(); + $this->init = true; + } + + /** + * 销毁session + * @return void + */ + public function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + session_unset(); + session_destroy(); + $this->init = null; + } + + /** + * 重新生成session_id + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + private function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @return void + */ + public function pause() + { + // 暂停session + session_write_close(); + $this->init = false; + } +} diff --git a/library/think/manager/UrlManager.php b/library/think/manager/UrlManager.php new file mode 100644 index 00000000..60ea3d4b --- /dev/null +++ b/library/think/manager/UrlManager.php @@ -0,0 +1,327 @@ + +// +---------------------------------------------------------------------- + +namespace think\manager; + +use think\Config; +use think\Request; +use think\Route; + +class UrlManager +{ + // 生成URL地址的root + protected $root; + protected $bindCheck; + + /** + * 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 是否显示域名 或者直接传入域名 + * @return string + */ + 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); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + // 解析参数 + if (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { + $rule = Route::name($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars)) { + // 匹配路由命名标识 + $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!empty($match[1])) { + $domain = $match[1]; + } + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url, $domain); + } + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 检测URL绑定 + if (!$this->bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if (0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } + } + } + // 还原URL分隔符 + $depr = Config::get('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + // URL后缀 + $suffix = in_array($url, ['/', '']) ? '' : $this->parseSuffix($suffix); + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if (Config::get('url_common_param')) { + $vars = urldecode(http_build_query($vars)); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = Config::get('url_param_type'); + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + // 检测域名 + $domain = $this->parseDomain($url, $domain); + // URL组装 + $url = $domain . rtrim($this->root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + + $this->bindCheck = false; + return $url; + } + + // 直接解析URL地址 + protected static function parseUrl($url, &$domain) + { + $request = Request::instance(); + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $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; + } + } + $this->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 . '/' : ''; + + $controller = Loader::parseName($request->controller()); + if ('' == $url) { + // 空字符串输出当前的 模块/控制器/操作 + $url = $module . $controller . '/' . $request->action(); + } else { + $path = explode('/', $url); + $action = Config::get('url_convert') ? strtolower(array_pop($path)) : array_pop($path); + $controller = empty($path) ? $controller : (Config::get('url_convert') ? Loader::parseName(array_pop($path)) : array_pop($path)); + $module = empty($path) ? $module : array_pop($path) . '/'; + $url = $module . $controller . '/' . $action; + } + } + return $url; + } + + // 检测域名 + protected static function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); + if (true === $domain) { + // 自动判断域名 + $domain = $request->host(); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { + foreach ($domains as $key => $rule) { + $rule = is_array($rule) ? $rule[0] : $rule; + if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { + $url = ltrim($url, $rule); + $domain = $key; + // 生成对应子域名 + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } + } + } + } + } + + } else { + if (empty($rootDomain)) { + $host = $request->host(); + $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; + } + if (!strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + } + return ($request->isSsl() ? 'https://' : 'http://') . $domain; + } + + // 解析URL后缀 + protected static function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? Config::get('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 = []) + { + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix) = $item; + if (empty($pattern)) { + return [$url, $domain, $suffix]; + } + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $vars[$key], $url); + unset($vars[$key]); + $result = [$url, $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $result = [$url, $domain, $suffix]; + } else { + break; + } + } + if (isset($result)) { + return $result; + } + } + return false; + } + + // 指定当前生成URL地址的root + public static function root($root) + { + $this->root = $root; + Request::instance()->root($root); + } +} diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 468d3611..3edf9e8a 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -69,7 +69,7 @@ class Php throw new TemplateNotFoundException('template not exists:' . $template, $template); } // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + App::isDebug() && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); if (isset($data['template'])) { $__template__ = $template; extract($data, EXTR_OVERWRITE); @@ -108,7 +108,7 @@ class Php private function parseTemplate($template) { if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; + $this->config['view_path'] = App::getModulePath() . 'view' . DS; } $request = Request::instance(); diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 39a14ca3..d9d4845f 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -40,7 +40,7 @@ class Think { $this->config = array_merge($this->config, $config); if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; + $this->config['view_path'] = App::getModulePath() . 'view' . DS; } $this->template = new Template($this->config); @@ -80,7 +80,7 @@ class Think throw new TemplateNotFoundException('template not exists:' . $template, $template); } // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + App::isDebug() && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); $this->template->fetch($template, $data, $config); } diff --git a/tpl/think_exception.tpl b/tpl/think_exception.tpl index e80c57b2..b9c9b491 100644 --- a/tpl/think_exception.tpl +++ b/tpl/think_exception.tpl @@ -286,7 +286,7 @@
- +
@@ -414,7 +414,7 @@ V { 十年磨一剑-为API开发设计的高性能框架 }
- + '; + return '

:)

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

'; } } -- Gitee From c0848996408c89a07b9d1f53fbb170340c91497c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 12 Jul 2017 10:30:34 +0800 Subject: [PATCH 0381/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=9A=84=E9=87=8D=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index dd614410..fce6f40d 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -166,6 +166,7 @@ abstract class Connection if (true === $name || !isset(self::$instance[$name])) { // 解析连接参数 支持数组和字符串 $options = self::parseConfig($config); + if (empty($options['type'])) { throw new \InvalidArgumentException('Undefined db type'); } @@ -175,10 +176,10 @@ abstract class Connection Facade::make('app')->log('[ DB ] INIT ' . $options['type']); if (true === $name) { - return new $class($options); - } else { - self::$instance[$name] = new $class($options); + $name = md5(serialize($config)); } + + self::$instance[$name] = new $class($options); } return self::$instance[$name]; -- Gitee From ab8df12dc07df00f9b5cfa899e7d1a15a3a79dcd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 12 Jul 2017 11:52:55 +0800 Subject: [PATCH 0382/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BValidate=E7=B1=BB?= =?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=8A=A0mobile=E9=AA=8C=E8=AF=81=E5=9B=BD?= =?UTF-8?q?=E5=86=85=E6=89=8B=E6=9C=BA=E5=8F=B7=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 38337ae9..5f67ff52 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -617,6 +617,10 @@ class Validate // 只允许汉字、字母、数字和下划线_及破折号- $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); break; + case 'mobile': + // 国内手机号码 + $result = $this->regex($value, '/^1[3|4|5|7|8][0-9]\d{8}$/'); + break; case 'activeUrl': // 是否为有效的网址 $result = checkdnsrr($value); @@ -928,7 +932,7 @@ class Validate */ protected function behavior($value, $rule, $data) { - return Hook::exec($rule, '', $data); + return Facade::make('hook')->exec($rule, $data); } /** -- Gitee From 7336d08069115be0e79cd88680376615a1681431 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 12 Jul 2017 12:47:31 +0800 Subject: [PATCH 0383/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88$App=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 88 ++++++++++++++++++++++++++++++-------- library/think/View.php | 13 +++--- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index abaecc6d..83ebb050 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -145,6 +145,7 @@ class Template return $this->data; } else { $data = $this->data; + foreach (explode('.', $name) as $key => $val) { if (isset($data[$val])) { $data = $data[$val]; @@ -176,9 +177,12 @@ class Template $this->config($config); } + $cache = Facade::make('cache'); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 读取渲染缓存 - $cacheContent = Cache::get($this->config['cache_id']); + $cacheContent = $cache->get($this->config['cache_id']); + if (false !== $cacheContent) { echo $cacheContent; return; @@ -208,7 +212,7 @@ class Template if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 缓存页面输出 - Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + $cache->set($this->config['cache_id'], $content, $this->config['cache_time']); } echo $content; @@ -259,10 +263,12 @@ class Template } else { // 开启布局 $this->config['layout_on'] = true; + // 名称必须为字符串 if (is_string($name)) { $this->config['layout_name'] = $name; } + if (!empty($replace)) { $this->config['layout_item'] = $replace; } @@ -297,6 +303,7 @@ class Template // 读取第一行 preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + if (!isset($matches[1])) { return false; } @@ -329,7 +336,7 @@ class Template { if ($cacheId && $this->config['display_cache']) { // 缓存页面输出 - return Cache::has($cacheId); + return Facade::make('cache')->has($cacheId); } return false; @@ -352,6 +359,7 @@ class Template } else { // 读取布局模板 $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + if ($layoutFile) { // 替换布局的主体内容 $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); @@ -401,16 +409,22 @@ class Template if (empty($content)) { return; } + // 替换literal标签内容 $this->parseLiteral($content); + // 解析继承 $this->parseExtend($content); + // 解析布局 $this->parseLayout($content); + // 检查include语法 $this->parseInclude($content); + // 替换包含文件中literal标签内容 $this->parseLiteral($content); + // 检查PHP语法 $this->parsePhp($content); @@ -421,6 +435,7 @@ class Template // 当TAGLIB_LOAD配置为true时才会进行检测 if ($this->config['taglib_load']) { $tagLibs = $this->getIncludeTagLib($content); + if (!empty($tagLibs)) { // 对导入的TagLib进行解析 foreach ($tagLibs as $tagLibName) { @@ -432,6 +447,7 @@ class Template // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 if ($this->config['taglib_pre_load']) { $tagLibs = explode(',', $this->config['taglib_pre_load']); + foreach ($tagLibs as $tag) { $this->parseTagLib($tag, $content); } @@ -487,9 +503,11 @@ class Template $content = str_replace($matches[0], '', $content); // 解析Layout标签 $array = $this->parseAttr($matches[0]); + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { // 读取布局模板 $layoutFile = $this->parseTemplateFile($array['name']); + if ($layoutFile) { $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; // 替换布局的主体内容 @@ -518,15 +536,19 @@ class Template $array = $this->parseAttr($match[0]); $file = $array['file']; unset($array['file']); + // 分析模板文件名并读取内容 $parseStr = $this->parseTemplateName($file); + foreach ($array as $k => $v) { // 以$开头字符串转换成模板变量 if (0 === strpos($v, '$')) { $v = $this->get(substr($v, 1)); } + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); } + $content = str_replace($match[0], $parseStr, $content); // 再次对包含文件进行模板分析 $func($parseStr); @@ -537,8 +559,6 @@ class Template // 替换模板中的include标签 $func($content); - - return; } /** @@ -559,15 +579,19 @@ class Template $array[$matches['name']] = 1; // 读取继承模板 $extend = $this->parseTemplateName($matches['name']); + // 递归检查继承 $func($extend); + // 取得block标签内容 $blocks = array_merge($blocks, $this->parseBlock($template)); + return; } } else { // 取得顶层模板block标签内容 $baseBlocks = $this->parseBlock($template, true); + if (empty($extend)) { // 无extend标签但有block标签的情况 $extend = $template; @@ -576,6 +600,7 @@ class Template }; $func($content); + if (!empty($extend)) { if ($baseBlocks) { $children = []; @@ -592,14 +617,18 @@ class Template if (isset($blocks[$name])) { // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + if (!empty($val['parent'])) { // 如果不是最顶层的block标签 $parent = $val['parent']; + if (isset($blocks[$parent])) { $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); } + $blocks[$name]['content'] = $replace; $children[$parent][] = $name; + continue; } } elseif (!empty($val['parent'])) { @@ -618,8 +647,6 @@ class Template $content = $extend; unset($blocks, $baseBlocks); } - - return; } /** @@ -636,6 +663,7 @@ class Template if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { if (!$restore) { $count = count($this->literal); + // 替换literal标签 foreach ($matches as $match) { $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); @@ -647,14 +675,13 @@ class Template foreach ($matches as $match) { $content = str_replace($match[0], $this->literal[$match[1]], $content); } + // 清空literal记录 $this->literal = []; } unset($matches); } - - return; } /** @@ -675,15 +702,17 @@ class Template foreach ($matches as $match) { if (empty($match['name'][0])) { if (count($right) > 0) { - $tag = array_pop($right); - $start = $tag['offset'] + strlen($tag['tag']); - $length = $match[0][1] - $start; + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + $result[$tag['name']] = [ 'begin' => $tag['tag'], 'content' => substr($content, $start, $length), 'end' => $match[0][0], 'parent' => count($right) ? end($right)['name'] : '', ]; + $keys[$tag['name']] = $match[0][1]; } } else { @@ -697,6 +726,7 @@ class Template } unset($right, $matches); + if ($sort) { // 按block标签结束符在模板中的位置排序 array_multisort($keys, $result); @@ -719,6 +749,7 @@ class Template if (preg_match($this->getRegex('taglib'), $content, $matches)) { // 替换TagLib标签 $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); } @@ -744,6 +775,7 @@ class Template } $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); return; @@ -798,18 +830,21 @@ class Template if (false !== $pos = strpos($str, '?')) { $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); $name = $array[0]; + $this->parseVar($name); $this->parseVarFunction($name); $str = trim(substr($str, $pos + 1)); $this->parseVar($str); $first = substr($str, 0, 1); + if (strpos($name, ')')) { // $name为对象或是自动识别,或者含有函数 if (isset($array[1])) { $this->parseVar($array[2]); $name .= $array[1] . $array[2]; } + switch ($first) { case '?': $str = ''; @@ -827,6 +862,7 @@ class Template } else { $_name = ''; } + // $name为数组 switch ($first) { case '?': @@ -886,12 +922,12 @@ class Template $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; break; } + $content = str_replace($match[0], $str, $content); } + unset($matches); } - - return; } /** @@ -910,6 +946,7 @@ class Template while ($matches[0]) { $match = array_pop($matches[0]); + //如果已经解析过该变量字串,则直接返回变量值 if (isset($_varParseList[$match[0]])) { $parseStr = $_varParseList[$match[0]]; @@ -917,6 +954,7 @@ class Template if (strpos($match[0], '.')) { $vars = explode('.', $match[0]); $first = array_shift($vars); + if ('$Think' == $first) { // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 $parseStr = $this->parseThinkVar($vars); @@ -931,6 +969,7 @@ class Template } else { $params = ''; } + $parseStr = 'app(\'request\')->' . $method . '(' . $params . ')'; } else { switch ($this->config['tpl_var_identify']) { @@ -947,14 +986,14 @@ class Template } else { $parseStr = str_replace(':', '->', $match[0]); } + $_varParseList[$match[0]] = $parseStr; } + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); } unset($matches); } - - return; } /** @@ -971,23 +1010,29 @@ class Template } static $_varFunctionList = []; - $_key = md5($varStr); + + $_key = md5($varStr); //如果已经解析过该变量字串,则直接返回变量值 if (isset($_varFunctionList[$_key])) { $varStr = $_varFunctionList[$_key]; } else { $varArray = explode('|', $varStr); + // 取得变量名称 $name = array_shift($varArray); + // 对变量使用函数 $length = count($varArray); + // 取得模板禁止使用函数列表 $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + for ($i = 0; $i < $length; $i++) { $args = explode('=', $varArray[$i], 2); // 模板函数过滤 $fun = trim($args[0]); + switch ($fun) { case 'default': // 特殊模板函数 if (false === strpos($name, '(')) { @@ -1013,11 +1058,10 @@ class Template } } } + $_varFunctionList[$_key] = $name; $varStr = $name; } - - return; } /** @@ -1138,23 +1182,27 @@ class Template if (strpos($template, '@')) { list($module, $template) = explode('@', $template); } + if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $this->config['view_depr'], $template); } else { $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); } + if ($this->config['view_base']) { $module = isset($module) ? $module : Facade::make('request')->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { $path = isset($module) ? Facade::make('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; } + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); } if (is_file($template)) { // 记录模板文件的更新时间 $this->includeFile[$template] = filemtime($template); + return $template; } else { throw new TemplateNotFoundException('template not exists:' . $template, $template); @@ -1173,6 +1221,7 @@ class Template if ('tag' == $tagName) { $begin = $this->config['tpl_begin']; $end = $this->config['tpl_end']; + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; } else { @@ -1182,6 +1231,7 @@ class Template $begin = $this->config['taglib_begin']; $end = $this->config['taglib_end']; $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + switch ($tagName) { case 'block': if ($single) { diff --git a/library/think/View.php b/library/think/View.php index 26c78b04..354b302f 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -35,8 +35,9 @@ class View $this->engine($engine); // 基础替换字符串 - $request = Facade::make('request'); - $root = $request->rootUrl(); + $request = Facade::make('request'); + $root = $request->rootUrl(); + $baseReplace = [ '__URL__' => $request->root() . '/' . $request->module() . '/' . Loader::parseName($request->controller()), '__ROOT__' => $root, @@ -106,6 +107,7 @@ class View if (isset($options['type'])) { unset($options['type']); } + $this->engine = new $class($options); return $this; @@ -140,17 +142,13 @@ class View // 模板变量 $vars = array_merge(self::$var, $this->data, $vars); - if (!isset($vars['App'])) { - // 应用对象模板变量 - $vars['App'] = Facade::make('app'); - } - // 页面缓存 ob_start(); ob_implicit_flush(0); // 渲染输出 $method = $renderContent ? 'display' : 'fetch'; + $this->engine->$method($template, $vars, $config); // 获取并清空缓存 @@ -161,6 +159,7 @@ class View // 允许用户自定义模板的字符串替换 $replace = array_merge($this->replace, $replace); + if (!empty($replace)) { $content = strtr($content, $replace); } -- Gitee From 8924e412a1656a2330f77c13a41980b7bc94be89 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 12 Jul 2017 15:22:51 +0800 Subject: [PATCH 0384/1384] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 8 +++++--- library/think/db/Connection.php | 5 +++-- library/think/db/builder/Pgsql.php | 3 +++ library/think/db/builder/Sqlsrv.php | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 0056590a..d0db239b 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -27,7 +27,7 @@ abstract class Builder protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; - protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; @@ -797,9 +797,10 @@ abstract class Builder * @access public * @param Query $query 查询对象 * @param array $dataSet 数据集 + * @param bool $replace 是否replace * @return string */ - public function insertAll(Query $query, $dataSet) + public function insertAll(Query $query, $dataSet, $replace = false) { $options = $query->getOptions(); @@ -839,8 +840,9 @@ abstract class Builder } return str_replace( - ['%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ + $replace ? 'REPLACE' : 'INSERT', $this->parseTable($query, $options['table']), implode(' , ', $fields), implode(' UNION ALL ', $values), diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index fce6f40d..fc761774 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -861,9 +861,10 @@ abstract class Connection * @access public * @param Query $query 查询对象 * @param mixed $dataSet 数据集 + * @param bool $replace 是否replace * @return integer|string */ - public function insertAll(Query $query, $dataSet = []) + public function insertAll(Query $query, $dataSet = [], $replace = false) { if (!is_array(reset($dataSet))) { return false; @@ -872,7 +873,7 @@ abstract class Connection $options = $query->getOptions(); // 生成SQL语句 - $sql = $this->builder->insertAll($query, $dataSet); + $sql = $this->builder->insertAll($query, $dataSet, $replace); $bind = $query->getBind(); diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index d05e0b76..e9b6d48c 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -20,6 +20,9 @@ use think\db\Query; class Pgsql extends Builder { + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + /** * limit分析 * @access protected diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index ed39230a..d76a7b19 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -23,6 +23,8 @@ class Sqlsrv extends Builder protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; /** * order分析 -- Gitee From 3203f0dbc04fadbfe1c5cb585b8929b25ef128e3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 12 Jul 2017 15:30:47 +0800 Subject: [PATCH 0385/1384] =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/connector/Pgsql.php | 8 ++++++++ library/think/db/connector/Sqlsrv.php | 1 + 2 files changed, 9 insertions(+) diff --git a/library/think/db/connector/Pgsql.php b/library/think/db/connector/Pgsql.php index 631a58dc..ab191229 100644 --- a/library/think/db/connector/Pgsql.php +++ b/library/think/db/connector/Pgsql.php @@ -21,6 +21,14 @@ 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 diff --git a/library/think/db/connector/Sqlsrv.php b/library/think/db/connector/Sqlsrv.php index 7d9c3d72..c26e9aca 100644 --- a/library/think/db/connector/Sqlsrv.php +++ b/library/think/db/connector/Sqlsrv.php @@ -23,6 +23,7 @@ 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, ]; -- Gitee From 33b2c9981ae1fb6b81e46dce2e8052097778dd9f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 11:30:58 +0800 Subject: [PATCH 0386/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=E7=9A=84remove=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 5f67ff52..63510ab9 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -261,7 +261,11 @@ class Validate { if (is_array($field)) { foreach ($field as $key => $rule) { - $this->remove($key, $rule); + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } } } else { if (is_string($rule)) { -- Gitee From 80835a76a19debac7487638d968fe95240b2dfe1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 12:25:38 +0800 Subject: [PATCH 0387/1384] =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=B7=BB=E5=8A=A0htmlentities=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=20=E5=A6=82=E6=9E=9C=E4=B8=8D=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=88=99=E4=BD=BF=E7=94=A8raw=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 83ebb050..3d28fabe 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -1005,8 +1005,8 @@ class Template */ public function parseVarFunction(&$varStr) { - if (false == strpos($varStr, '|')) { - return; + if (false === strpos($varStr, '|raw')) { + $varStr .= '|htmlentities'; } static $_varFunctionList = []; @@ -1034,6 +1034,8 @@ class Template $fun = trim($args[0]); switch ($fun) { + case 'raw': + continue; case 'default': // 特殊模板函数 if (false === strpos($name, '(')) { $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; -- Gitee From 647c8453fb0da811e5a429bfa97d0370ff0c7ba6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 14:26:27 +0800 Subject: [PATCH 0388/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=20=E5=A2=9E=E5=8A=A0=E5=86=85=E7=BD=AE?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 53 ++++++++++++++++++++++--------- library/think/template/TagLib.php | 4 +-- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 3d28fabe..694ab398 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -1000,12 +1000,15 @@ class Template * 对模板中使用了函数的变量进行解析 * 格式 {$varname|function1|function2=arg1,arg2} * @access public - * @param string $varStr 变量字符串 + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 * @return void */ - public function parseVarFunction(&$varStr) + public function parseVarFunction(&$varStr, $autoescape = true) { - if (false === strpos($varStr, '|raw')) { + if (!$autoescape && false === strpos($varStr, '|')) { + return; + } elseif ($autoescape && false === strpos($varStr, '|raw')) { $varStr .= '|htmlentities'; } @@ -1020,7 +1023,7 @@ class Template $varArray = explode('|', $varStr); // 取得变量名称 - $name = array_shift($varArray); + $name = trim(array_shift($varArray)); // 对变量使用函数 $length = count($varArray); @@ -1030,12 +1033,34 @@ class Template for ($i = 0; $i < $length; $i++) { $args = explode('=', $varArray[$i], 2); + // 模板函数过滤 $fun = trim($args[0]); + if (in_array($fun, $template_deny_funs)) { + continue; + } switch ($fun) { case 'raw': continue; + case 'date': + $name = 'date_format(date_create(' . $name . '),' . $args[1] . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; case 'default': // 特殊模板函数 if (false === strpos($name, '(')) { $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; @@ -1044,18 +1069,16 @@ class Template } break; default: // 通用模板函数 - if (!in_array($fun, $template_deny_funs)) { - if (isset($args[1])) { - if (strstr($args[1], '###')) { - $args[1] = str_replace('###', $name, $args[1]); - $name = "$fun($args[1])"; - } else { - $name = "$fun($name,$args[1])"; - } + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; } else { - if (!empty($args[0])) { - $name = "$fun($name)"; - } + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; } } } diff --git a/library/think/template/TagLib.php b/library/think/template/TagLib.php index fec25c96..c4e317f9 100644 --- a/library/think/template/TagLib.php +++ b/library/think/template/TagLib.php @@ -312,7 +312,7 @@ class TagLib /** * 自动识别构建变量 * @access public - * @param string $name 变量描述 + * @param string $name 变量描述 * @return string */ public function autoBuildVar(&$name) @@ -334,7 +334,7 @@ class TagLib } $this->tpl->parseVar($name); - $this->tpl->parseVarFunction($name); + $this->tpl->parseVarFunction($name, false); return $name; } -- Gitee From ca4b67c3075f59b211599fadb5e38e261594968b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 14:57:06 +0800 Subject: [PATCH 0389/1384] =?UTF-8?q?=E4=B8=89=E5=85=83=E8=BF=90=E7=AE=97?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=B8=8D=E4=BD=BF=E7=94=A8=E8=BD=AC=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Template.php b/library/think/Template.php index 694ab398..0d0baa1d 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -832,7 +832,7 @@ class Template $name = $array[0]; $this->parseVar($name); - $this->parseVarFunction($name); + //$this->parseVarFunction($name); $str = trim(substr($str, $pos + 1)); $this->parseVar($str); -- Gitee From 7300a2969c6b71e78f6201bc23d61ef2c8aeb565 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 15:24:46 +0800 Subject: [PATCH 0390/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=89=E5=85=83?= =?UTF-8?q?=E8=BF=90=E7=AE=97=E7=9A=84=E8=BD=AC=E4=B9=89=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 0d0baa1d..1720ffdf 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -847,6 +847,7 @@ class Template switch ($first) { case '?': + $this->parseVarFunction($name); $str = ''; break; case '=': @@ -867,7 +868,7 @@ class Template switch ($first) { case '?': // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = ''; + $str = 'parseVarFunction($name) . ' : ' . substr($str, 1) . '; ?>'; break; case '=': // {$varname?='xxx'} $varname为真时才输出xxx @@ -880,7 +881,14 @@ class Template default: if (strpos($str, ':')) { // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b - $str = ''; + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + $str = ''; + } else { $str = ''; } @@ -1007,7 +1015,7 @@ class Template public function parseVarFunction(&$varStr, $autoescape = true) { if (!$autoescape && false === strpos($varStr, '|')) { - return; + return $varStr; } elseif ($autoescape && false === strpos($varStr, '|raw')) { $varStr .= '|htmlentities'; } @@ -1087,6 +1095,7 @@ class Template $_varFunctionList[$_key] = $name; $varStr = $name; } + return $varStr; } /** -- Gitee From 828469149b0af7fd1ddea746e27d35974d70459e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 15:30:25 +0800 Subject: [PATCH 0391/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0default=5Ffilter?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=BC=95=E6=93=8E=E9=85=8D=E7=BD=AE=E5=8F=82?= =?UTF-8?q?=E6=95=B0=20=E9=BB=98=E8=AE=A4=E4=BD=BF=E7=94=A8htmlentities?= =?UTF-8?q?=E8=BD=AC=E4=B9=89=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Template.php b/library/think/Template.php index 1720ffdf..b822dcd5 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -50,6 +50,7 @@ class Template 'cache_id' => '', // 模板缓存ID 'tpl_replace_string' => [], 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 ]; private $literal = []; @@ -1017,7 +1018,7 @@ class Template if (!$autoescape && false === strpos($varStr, '|')) { return $varStr; } elseif ($autoescape && false === strpos($varStr, '|raw')) { - $varStr .= '|htmlentities'; + $varStr .= '|' . $this->config['default_filter']; } static $_varFunctionList = []; -- Gitee From e684adb1205acd39198e506142bea5ffd86da323 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 13 Jul 2017 18:17:01 +0800 Subject: [PATCH 0392/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=89=E5=85=83?= =?UTF-8?q?=E8=BF=90=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index b822dcd5..195219d2 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -865,19 +865,26 @@ class Template $_name = ''; } + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + // $name为数组 switch ($first) { case '?': // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = 'parseVarFunction($name) . ' : ' . substr($str, 1) . '; ?>'; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; case '=': // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; + $str = ''; break; case ':': // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = ''; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; default: if (strpos($str, ':')) { @@ -891,7 +898,7 @@ class Template $str = ''; } else { - $str = ''; + $str = ''; } } } -- Gitee From 52063fbf66f3f953577c1783e0a76820ea8cc99d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 14 Jul 2017 12:07:19 +0800 Subject: [PATCH 0393/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=99=A8=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 195219d2..da9689d5 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -1024,7 +1024,7 @@ class Template { if (!$autoescape && false === strpos($varStr, '|')) { return $varStr; - } elseif ($autoescape && false === strpos($varStr, '|raw')) { + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { $varStr .= '|' . $this->config['default_filter']; } @@ -1056,7 +1056,7 @@ class Template continue; } - switch ($fun) { + switch (strtolower($fun)) { case 'raw': continue; case 'date': -- Gitee From ee16c223f549d4aaaca87c759a6ffad9c43d7392 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 15 Jul 2017 22:58:30 +0800 Subject: [PATCH 0394/1384] =?UTF-8?q?App=E7=B1=BB=E5=A2=9E=E5=8A=A0contain?= =?UTF-8?q?er=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base.php | 2 +- composer.json | 4 ++++ lang/zh-cn.php | 2 +- library/think/App.php | 9 +++++++++ library/think/paginator/Collection.php | 6 +++--- start.php | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/base.php b/base.php index 51b09934..3e72b838 100644 --- a/base.php +++ b/base.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/composer.json b/composer.json index edcda4a1..245b2699 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,10 @@ { "name": "liu21st", "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" } ], "require": { diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 50284060..e959df1a 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- diff --git a/library/think/App.php b/library/think/App.php index bb380b4c..3800ef9f 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -732,6 +732,15 @@ class App implements \ArrayAccess return $this->beginMem; } + /** + * 获取容器实例 + * @return Container + */ + public function container() + { + return $this->container; + } + public function __set($name, $value) { $this->container->bind($name, $value); diff --git a/library/think/paginator/Collection.php b/library/think/paginator/Collection.php index 50803ba8..d3e9c93b 100644 --- a/library/think/paginator/Collection.php +++ b/library/think/paginator/Collection.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- @@ -42,7 +42,7 @@ class Collection extends \think\Collection { return new static($items, $paginator); } - + public function toArray() { if ($this->paginator) { @@ -56,7 +56,7 @@ class Collection extends \think\Collection 'total' => $total, 'per_page' => $this->listRows(), 'current_page' => $this->currentPage(), - 'data' => parent::toArray() + 'data' => parent::toArray(), ]; } else { return parent::toArray(); diff --git a/start.php b/start.php index 8c8fa388..8dca43ea 100644 --- a/start.php +++ b/start.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- -// | 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 ) // +---------------------------------------------------------------------- -- Gitee From 7633786a7e2c4df9c937adc3d15dc34fbaad6bdb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 16 Jul 2017 10:36:17 +0800 Subject: [PATCH 0395/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BModel=E7=B1=BB?= =?UTF-8?q?=E5=92=8CRoute=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/Model.php | 39 ++++++++++++----------- library/think/Route.php | 25 ++++++--------- library/think/model/concern/Attribute.php | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/convention.php b/convention.php index 37e417c9..8718afcf 100644 --- a/convention.php +++ b/convention.php @@ -272,6 +272,8 @@ return [ 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 'sql_explain' => false, + // 查询对象 + 'query' => '\\think\\db\\Query', ], //分页配置 diff --git a/library/think/Model.php b/library/think/Model.php index 209fd4cd..6cf98598 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -32,7 +32,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess private $updateWhere; // 数据库配置 - protected $connection; + protected $connection = []; // 数据库查询类 protected $query; // 当前模型名称 @@ -70,11 +70,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录原始数据 $this->origin = $this->data; + $config = Facade::make('config'); + if (empty($this->name)) { // 当前模型名 $name = str_replace('\\', '/', static::class); $this->name = basename($name); - if (Facade::make('config')->get('class_suffix')) { + if ($config->get('class_suffix')) { $suffix = basename(dirname($name)); $this->name = substr($this->name, 0, -strlen($suffix)); } @@ -82,16 +84,26 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = Facade::make('config')->get('database.auto_timestamp'); + $this->autoWriteTimestamp = $config->get('database.auto_timestamp'); } if (is_null($this->dateFormat)) { // 设置时间戳格式 - $this->dateFormat = Facade::make('config')->get('database.datetime_format'); + $this->dateFormat = $config->get('database.datetime_format'); } if (is_null($this->resultSetType)) { - $this->resultSetType = Facade::make('config')->get('database.resultset_type'); + $this->resultSetType = $config->get('database.resultset_type'); + } + + if (is_null($this->query)) { + // 设置查询对象 + $this->query = $config->get('database.query'); + } + + if (!empty($this->connection) && is_array($this->connection)) { + // 设置模型的数据库连接 + $this->connection = array_merge($config->pull('database'), $this->connection); } // 执行初始化操作 @@ -118,21 +130,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected function buildQuery() { - // 合并数据库配置 - if (!empty($this->connection)) { - if (is_array($this->connection)) { - $connection = array_merge(Facade::make('config')->pull('database'), $this->connection); - } else { - $connection = $this->connection; - } - } else { - $connection = []; - } - // 设置当前模型 确保查询返回模型对象 - $class = $this->query ?: Facade::make('config')->get('database.query'); + $class = $this->query; $query = new $class(); - $query->connect($connection)->model($this); + $query->connect($this->connection)->model($this); // 设置当前数据表和模型名 if (!empty($this->table)) { @@ -449,6 +450,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $where = $this->getWhere(); $result = $this->db(false)->where($where)->setInc($field, $step, $lazyTime); + if (true !== $result) { $this->data[$field] += $step; } @@ -471,6 +473,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $where = $this->getWhere(); $result = $this->db(false)->where($where)->setDec($field, $step, $lazyTime); + if (true !== $result) { $this->data[$field] -= $step; } diff --git a/library/think/Route.php b/library/think/Route.php index 9f22aa3c..d49738f3 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -740,20 +740,10 @@ class Route */ public function check($url, $depr = '/', $must = false) { - $url = str_replace($depr, '|', $url); - $method = strtolower($this->request->method()); + // 自动检测域名路由 $host = $this->request->host(); - $domain = false; - - if (count($this->domains) > 1) { - // 自动检测域名路由 - $domain = $this->checkDomain($host); - } - - if (false === $domain) { - // 检测当前完整域名 - $domain = $this->domains[$host]; - } + $domain = $this->checkDomain($host); + $url = str_replace($depr, '|', $url); $result = $domain->check($this->request, $url, $depr); @@ -778,7 +768,7 @@ class Route * 检测域名的路由规则 * @access public * @param string $host 当前主机地址 - * @return Domain|false + * @return Domain */ protected function checkDomain($host) { @@ -795,7 +785,7 @@ class Route // 子域名配置 $item = false; - if (!empty($domain)) { + if (!empty($domain) && count($domains) > 1) { // 当前子域名 $subDomain = implode('.', $domain); $domain2 = array_pop($domain); @@ -826,6 +816,11 @@ class Route } } + if (false === $item) { + // 检测当前完整域名 + $item = $domains[$host]; + } + return $item; } diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index dbaf5663..fe6bb6a9 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -16,7 +16,7 @@ use think\Loader; trait Attribute { - // 数据表主键 复合主键使用数组定义 不设置则自动获取 + // 数据表主键 复合主键使用数组定义 protected $pk = 'id'; // 数据表字段信息 留空则自动获取 protected $field = []; -- Gitee From 668a46c0cfa5776f0ddc62c2915b494927d9c7f2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 16 Jul 2017 20:25:41 +0800 Subject: [PATCH 0396/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BCache=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=E5=BC=BA=E5=88=B6=E9=87=8D=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cache.php | 8 +++++--- library/think/Cookie.php | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/library/think/Cache.php b/library/think/Cache.php index 594fa227..828ca712 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -40,6 +40,7 @@ class Cache public function connect(array $options = [], $name = false) { $type = !empty($options['type']) ? $options['type'] : 'File'; + if (false === $name) { $name = md5(serialize($options)); } @@ -49,11 +50,12 @@ class Cache // 记录初始化信息 $this->app->log('[ CACHE ] INIT ' . $type); + if (true === $name) { - return new $class($options); - } else { - $this->instance[$name] = new $class($options); + $name = md5(serialize($options)); } + + $this->instance[$name] = new $class($options); } return $this->instance[$name]; diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 3880e8e2..f70fbd36 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -87,6 +87,7 @@ class Cookie } elseif (is_string($option)) { parse_str($option, $option); } + $config = array_merge($this->config, array_change_key_case($option)); } else { $config = $this->config; @@ -169,6 +170,7 @@ class Cookie } } elseif (isset($_COOKIE[$key])) { $value = $_COOKIE[$key]; + if (0 === strpos($value, 'think:')) { $value = substr($value, 6); $value = json_decode($value, true); -- Gitee From 096b29e7142916a57bbafea70fe6bfe8ade64cbd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 17 Jul 2017 14:25:37 +0800 Subject: [PATCH 0397/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0whereBetweenTime?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=94=A8=E4=BA=8E=E6=97=B6=E9=97=B4=E5=8C=BA?= =?UTF-8?q?=E9=97=B4=E7=9A=84=E5=BF=AB=E9=80=9F=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index a4ca0a1f..3d4f0aa9 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1819,6 +1819,26 @@ class Query return $this; } + /** + * 查询日期或者时间范围 + * @access public + * @param string $field 日期字段名 + * @param string $startTime 开始时间 + * @param string $endTime 结束时间 + * @return $this + */ + public function whereBetweenTime($field, $startTime, $endTime = null) + { + if (is_null($endTime)) { + $time = is_string($startTime) ? strtotime($startTime) : $startTime; + $endTime = strtotime('+1 day', $time); + } + + $this->where($field, 'between time', [$startTime, $endTime]); + + return $this; + } + /** * 获取当前数据表的主键 * @access public -- Gitee From e4b7138a9a43415a47b05c1e8e37081651bbed27 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 17 Jul 2017 14:58:13 +0800 Subject: [PATCH 0398/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93result=5Ftype=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 17 +++++------------ library/think/db/Query.php | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index fc761774..38fb383e 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -91,8 +91,6 @@ abstract class Connection 'slave_no' => '', // 是否严格检查字段是否存在 'fields_strict' => true, - // 数据返回类型 - 'result_type' => PDO::FETCH_ASSOC, // 数据集返回类型 'resultset_type' => '', // 自动写入时间戳字段 @@ -466,11 +464,6 @@ abstract class Connection // 记录当前字段属性大小写设置 $this->attrCase = $params[PDO::ATTR_CASE]; - // 数据返回类型 - if (isset($config['result_type'])) { - $this->fetchType = $config['result_type']; - } - try { if (empty($config['dsn'])) { $config['dsn'] = $this->parseDsn($config); @@ -529,7 +522,7 @@ abstract class Connection * @param array $bind 参数绑定 * @param bool $master 是否在主服务器读操作 * @param bool $pdo 是否返回PDO对象 - * @return mixed + * @return array * @throws BindParamException * @throws PDOException */ @@ -669,8 +662,8 @@ abstract class Connection /** * 查找单条记录 * @access public - * @param Query $query 查询对象 - * @return array|null|\PDOStatement|string|Model + * @param Query $query 查询对象 + * @return array|null|\PDOStatement|string * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException @@ -754,8 +747,8 @@ abstract class Connection /** * 查找记录 * @access public - * @param Query $query 查询对象 - * @return Collection|false|\PDOStatement|string + * @param Query $query 查询对象 + * @return array|\PDOStatement|string * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3d4f0aa9..68f5505c 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2198,7 +2198,7 @@ class Query * 查找记录 * @access public * @param array|string|Query|\Closure $data - * @return Collection|false|\PDOStatement|string + * @return Collection|array|\PDOStatement|string * @throws DbException * @throws ModelNotFoundException * @throws DataNotFoundException -- Gitee From 0888bba1fc8a987cfab61b3db16ca479a10e7c82 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 17 Jul 2017 15:59:57 +0800 Subject: [PATCH 0399/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E5=8C=B9=E9=85=8D=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/route/Domain.php | 5 +++-- library/think/route/Rule.php | 11 +++++++++++ library/think/route/RuleGroup.php | 7 ++++--- library/think/route/RuleItem.php | 25 +++++++++++++------------ 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 3800ef9f..95bfcdf7 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -392,7 +392,7 @@ class App implements \ArrayAccess $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); // 路由检测(根据路由定义返回不同的URL调度) - return $this->route->check($path, $depr, $must); + return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match')); } /** diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 02526000..46137940 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -25,9 +25,10 @@ class Domain extends RuleGroup * @param Request $request 请求对象 * @param string $url 访问地址 * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/') + public function check($request, $url, $depr = '/', $completeMatch = false) { // 检测别名路由 if ($this->router->getAlias($url) || $this->router->getAlias(strstr($url, '|', true))) { @@ -45,7 +46,7 @@ class Domain extends RuleGroup return $result; } - return parent::check($request, $url, $depr); + return parent::check($request, $url, $depr, $completeMatch); } /** diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index fba10f69..d342cb8c 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -303,6 +303,17 @@ abstract class Rule return $this->option('pjax', $pjax); } + /** + * 设置路由完整匹配 + * @access public + * @param bool $match + * @return $this + */ + public function completeMatch($match = true) + { + return $this->option('complete_match', $match); + } + /** * 设置路由规则全局有效 * @access public diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index b49919b3..23739d86 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -57,9 +57,10 @@ class RuleGroup extends Rule * @param Request $request 请求对象 * @param string $url 访问地址 * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/') + public function check($request, $url, $depr = '/', $completeMatch = false) { // 检查参数有效性 if (!$this->checkOption($this->option, $request)) { @@ -73,7 +74,7 @@ class RuleGroup extends Rule if (isset($rules[$url])) { // 快速定位 $item = $rules[$url]; - $result = $item->check($request, $url, $depr); + $result = $item->check($request, $url, $depr, $completeMatch); if (false !== $result) { return $result; @@ -82,7 +83,7 @@ class RuleGroup extends Rule // 遍历分组路由 foreach ($rules as $key => $item) { - $result = $item->check($request, $url, $depr); + $result = $item->check($request, $url, $depr, $completeMatch); if (false !== $result) { return $result; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 177f7874..a0b14480 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -101,9 +101,10 @@ class RuleItem extends Rule * @param Request $request 请求对象 * @param string $url 访问地址 * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch */ - public function check($request, $url, $depr = '/') + public function check($request, $url, $depr = '/', $completeMatch = false) { // 检查参数有效性 if (!$this->checkOption($this->option, $request)) { @@ -115,21 +116,19 @@ class RuleItem extends Rule $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); } - return $this->checkRule($request, $url, $depr); + return $this->checkRule($request, $url, $depr, $completeMatch); } /** * 检测路由规则 * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param Request $request 请求对象 * @param string $url URL地址 - * @param array $pattern 变量规则 - * @param array $option 路由参数 * @param string $depr URL分隔符(全局) + * @param bool $completeMatch 路由是否完全匹配 * @return array|false */ - private function checkRule($request, $url, $depr) + private function checkRule($request, $url, $depr, $completeMatch = false) { // 检查完整规则定义 if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { @@ -152,12 +151,14 @@ class RuleItem extends Rule $url = implode('|', explode($depr, $url, $len2 + 1)); } + if (isset($this->option['complete_match'])) { + $completeMatch = $this->option['complete_match']; + } + if ($len1 >= $len2 || strpos($this->name, '[')) { - if (!empty($this->option['complete_match'])) { - // 完整匹配 - if (!$merge && $len1 != $len2 && (false === strpos($this->name, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->name, '['))) { - return false; - } + // 完整匹配 + if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->name, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->name, '[')))) { + return false; } $pattern = array_merge($this->group->getPattern(), $this->pattern); -- Gitee From 66247e160bfc21f8703ff92dae38c34490975e75 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 17 Jul 2017 16:02:52 +0800 Subject: [PATCH 0400/1384] =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 63510ab9..76b0018a 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -35,12 +35,13 @@ class Validate protected static $typeMsg = [ 'require' => ':attribute不能为空', 'number' => ':attribute必须是数字', + 'integer' => ':attribute必须是整数', 'float' => ':attribute必须是浮点数', 'boolean' => ':attribute必须是布尔值', 'email' => ':attribute格式不符', 'array' => ':attribute必须是数组', 'accepted' => ':attribute必须是yes、on或者1', - 'date' => ':attribute格式不符合', + 'date' => ':attribute不是一个有效的日期或时间格式', 'file' => ':attribute不是有效的上传文件', 'image' => ':attribute不是有效的图像文件', 'alpha' => ':attribute只能是字母', -- Gitee From b75158e806186ed3f2ceaa8af9a3aa5401771f15 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 07:02:24 +0800 Subject: [PATCH 0401/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index d7536bf7..036da138 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -53,14 +53,15 @@ class Resource extends RuleGroup * @param Request $request 请求对象 * @param string $url 访问地址 * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch */ - public function check($request, $url, $depr = '/') + public function check($request, $url, $depr = '/', $completeMatch = false) { // 生成资源路由的路由规则 $this->buildResourceRule($this->rule, $this->option); - return parent::check($request, $url, $depr); + return parent::check($request, $url, $depr, $completeMatch); } /** -- Gitee From cad9cc1abd4b370a458ec1bd9b58142c11833ac4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 07:10:05 +0800 Subject: [PATCH 0402/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index d49738f3..a377ec30 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -735,21 +735,22 @@ class Route * @param string $url URL地址 * @param string $depr URL分隔符 * @param bool $must 是否强制路由 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch * @throws RouteNotFoundException */ - public function check($url, $depr = '/', $must = false) + public function check($url, $depr = '/', $must = false, $completeMatch = false) { // 自动检测域名路由 $host = $this->request->host(); $domain = $this->checkDomain($host); $url = str_replace($depr, '|', $url); - $result = $domain->check($this->request, $url, $depr); + $result = $domain->check($this->request, $url, $depr, $completeMatch); if (false === $result && !empty($this->cross)) { // 检测跨越路由 - $result = $this->cross->check($this->request, $url, $depr); + $result = $this->cross->check($this->request, $url, $depr, $completeMatch); } if (false !== $result) { -- Gitee From c1a7b566a23cea95e105ccbd49eca5dad13b0e0e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 11:33:11 +0800 Subject: [PATCH 0403/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=9A=84=E5=AE=8C=E6=95=B4=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 23739d86..c3676d25 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -71,6 +71,10 @@ class RuleGroup extends Rule $method = strtolower($request->method()); $rules = array_merge($this->rules['*'], $this->rules[$method]); + if (isset($this->option['complete_match'])) { + $completeMatch = $this->option['complete_match']; + } + if (isset($rules[$url])) { // 快速定位 $item = $rules[$url]; -- Gitee From af62edc0e26e9e8da41c64c697dc2a567190d72d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 13:23:04 +0800 Subject: [PATCH 0404/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=92=8C=E5=88=86=E7=BB=84=E7=9A=84=E5=8F=82=E6=95=B0=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 5 ++--- library/think/route/Domain.php | 17 +++++++++++++++ library/think/route/Resource.php | 4 +++- library/think/route/Rule.php | 4 +++- library/think/route/RuleGroup.php | 35 +++++++++++++++++++++++++++++-- library/think/route/RuleItem.php | 31 ++++++++++++++------------- 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index a377ec30..1037b8d1 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -459,8 +459,7 @@ class Route $parentGroup = $this->group; // 创建分组实例 - $option = array_merge($this->group->getOption(), $option); - $group = new RuleGroup($this, $name, $option, $pattern); + $group = new RuleGroup($this, $this->group, $name, $option, $pattern); // 注册子分组 $parentGroup->addRule($group); @@ -580,7 +579,7 @@ class Route */ public function resource($rule, $route = '', $option = [], $pattern = []) { - $resource = new Resource($this, $rule, $route, $option, $pattern, $this->rest); + $resource = new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest); // 添加到当前分组 $this->group->addRule($resource); diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 46137940..b7bb0d83 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -13,12 +13,29 @@ namespace think\route; use think\Facade; use think\Loader; +use think\Route; use think\route\dispatch\Callback as CallbackDispatch; use think\route\dispatch\Controller as ControllerDispatch; use think\route\dispatch\Module as ModuleDispatch; class Domain extends RuleGroup { + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 分组名称 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, $name = '', $option = [], $pattern = []) + { + $this->router = $router; + $this->name = trim($name, '/'); + $this->option = $option; + $this->pattern = $pattern; + } + /** * 检测域名路由 * @access public diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 036da138..ade9500d 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -26,15 +26,17 @@ class Resource extends RuleGroup * 架构函数 * @access public * @param Route $router 路由对象 + * @param RuleGroup $group 路由所属分组对象 * @param string $rule 资源名称 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @param array $rest 资源定义 */ - public function __construct(Route $router, $rule, $route, $option = [], $pattern = [], $rest = []) + public function __construct(Route $router, RuleGroup $group = null, $rule = '', $route = '', $option = [], $pattern = [], $rest = []) { $this->router = $router; + $this->parent = $group; $this->rule = $rule; $this->route = $route; $this->name = strpos($rule, '.') ? strstr($rule, '.', true) : $rule; diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index d342cb8c..1e606349 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -27,6 +27,8 @@ abstract class Rule protected $name; // 路由对象实例 protected $router; + // 路由父对象 + protected $parent; // 路由参数 protected $option = []; // 路由变量规则 @@ -473,7 +475,7 @@ abstract class Rule } // 开启请求缓存 - if ($request->isGet() && isset($option['cache'])) { + if (isset($option['cache']) && $request->isGet()) { $this->parseRequestCache($request, $option['cache']); } diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index c3676d25..d556f019 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -39,13 +39,15 @@ class RuleGroup extends Rule * 架构函数 * @access public * @param Route $router 路由对象 + * @param RuleGroup $group 路由所属分组对象 * @param string $name 分组名称 * @param array $option 路由参数 * @param array $pattern 变量规则 */ - public function __construct(Route $router, $name = '', $option = [], $pattern = []) + public function __construct(Route $router, RuleGroup $group = null, $name = '', $option = [], $pattern = []) { $this->router = $router; + $this->parent = $group; $this->name = trim($name, '/'); $this->option = $option; $this->pattern = $pattern; @@ -62,11 +64,38 @@ class RuleGroup extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + if ($this->parent) { + $option = array_merge($this->parent->getOption(), $this->option); + } else { + $option = $this->option; + } + // 检查参数有效性 - if (!$this->checkOption($this->option, $request)) { + if (!$this->checkOption($option, $request)) { return false; } + // 分组匹配后执行的行为 + + // 指定Response响应数据 + if (!empty($this->option['response'])) { + Facade::make('hook')->add('response_send', $this->option['response']); + } + + // 开启请求缓存 + if (isset($this->option['cache']) && $request->isGet()) { + $this->parseRequestCache($request, $this->option['cache']); + } + + // 检测路由after行为 + if (!empty($this->option['after'])) { + $dispatch = $this->checkAfter($this->option['after']); + + if (false !== $dispatch) { + return $dispatch; + } + } + // 获取当前路由规则 $method = strtolower($request->method()); $rules = array_merge($this->rules['*'], $this->rules[$method]); @@ -75,6 +104,8 @@ class RuleGroup extends Rule $completeMatch = $this->option['complete_match']; } + $this->option = $option; + if (isset($rules[$url])) { // 快速定位 $item = $rules[$url]; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index a0b14480..affbea38 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -21,8 +21,6 @@ class RuleItem extends Rule protected $route; // 请求类型 protected $method; - // 所属分组 - protected $group; /** * 架构函数 @@ -38,10 +36,10 @@ class RuleItem extends Rule public function __construct(Route $router, RuleGroup $group, $name, $route, $method = '*', $option = [], $pattern = []) { $this->router = $router; - $this->group = $group; + $this->parent = $group; $this->route = $route; $this->method = $method; - $this->option = array_merge($group->getOption(), $this->option); + $this->option = $option; $this->pattern = $pattern; $this->setRule($name); @@ -106,17 +104,19 @@ class RuleItem extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + $option = array_merge($this->parent->getOption(), $this->option); + // 检查参数有效性 - if (!$this->checkOption($this->option, $request)) { + if (!$this->checkOption($option, $request)) { return false; } - if (isset($this->option['ext'])) { + if (isset($option['ext'])) { // 路由ext参数 优先于系统配置的URL伪静态后缀参数 $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); } - return $this->checkRule($request, $url, $depr, $completeMatch); + return $this->checkRule($request, $url, $depr, $completeMatch, $option); } /** @@ -126,9 +126,10 @@ class RuleItem extends Rule * @param string $url URL地址 * @param string $depr URL分隔符(全局) * @param bool $completeMatch 路由是否完全匹配 + * @param array $option 路由参数 * @return array|false */ - private function checkRule($request, $url, $depr, $completeMatch = false) + private function checkRule($request, $url, $depr, $completeMatch = false, $option = []) { // 检查完整规则定义 if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { @@ -136,23 +137,23 @@ class RuleItem extends Rule } // 检查路由的参数分隔符 - if (isset($this->option['param_depr'])) { - $url = str_replace(['|', $this->option['param_depr']], [$depr, '|'], $url); + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); } $len1 = substr_count($url, '|'); $len2 = substr_count($this->name, '/'); // 多余参数是否合并 - $merge = !empty($this->option['merge_extra_vars']) ? true : false; + $merge = !empty($option['merge_extra_vars']) ? true : false; if ($merge && $len1 > $len2) { $url = str_replace('|', $depr, $url); $url = implode('|', explode($depr, $url, $len2 + 1)); } - if (isset($this->option['complete_match'])) { - $completeMatch = $this->option['complete_match']; + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; } if ($len1 >= $len2 || strpos($this->name, '[')) { @@ -161,11 +162,11 @@ class RuleItem extends Rule return false; } - $pattern = array_merge($this->group->getPattern(), $this->pattern); + $pattern = array_merge($this->parent->getPattern(), $this->pattern); if (false !== $match = $this->match($url, $pattern)) { // 匹配到路由规则 - return $this->parseRule($request, $this->name, $this->route, $url, $this->option, $match); + return $this->parseRule($request, $this->name, $this->route, $url, $option, $match); } } -- Gitee From 19aad7c905efb8941abcfa3ef170d015a03e31e7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 16:19:11 +0800 Subject: [PATCH 0405/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BXmlResponse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/Xml.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/think/response/Xml.php b/library/think/response/Xml.php index 6bc448fd..104f8dcf 100644 --- a/library/think/response/Xml.php +++ b/library/think/response/Xml.php @@ -11,6 +11,8 @@ namespace think\response; +use think\Collection; +use think\Model; use think\Response; class Xml extends Response @@ -84,6 +86,10 @@ class Xml extends Response { $xml = $attr = ''; + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + foreach ($data as $key => $val) { if (is_numeric($key)) { $id && $attr = " {$id}=\"{$key}\""; -- Gitee From ff8ef2d8ce3d624982282229eb3bf9f987f62c76 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 16:20:42 +0800 Subject: [PATCH 0406/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bsession=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Session.php b/library/think/Session.php index cba98251..b10c72ff 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -376,7 +376,7 @@ class Session * @param bool $delete 是否删除关联会话文件 * @return void */ - private function regenerate($delete = false) + public function regenerate($delete = false) { session_regenerate_id($delete); } -- Gitee From 4a5d49d6fb430a99f69474fca859b1a3a7741704 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 17:47:43 +0800 Subject: [PATCH 0407/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BUrl=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 46 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index f475bd95..8ab45a51 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -44,13 +44,16 @@ class Url 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); @@ -69,6 +72,7 @@ class Url if ($url) { $rule = $this->app['route']->getName(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { $rule = $this->app['route']->getName($url); // 解析地址里面参数 合并到vars @@ -83,9 +87,14 @@ class Url $url = $match[0]; // 替换可选分隔符 $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!empty($match[1])) { - $domain = $match[1]; + $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(); + if ($domain || $match[1] != $host) { + $domain = $match[1]; + } } + if (!is_null($match[2])) { $suffix = $match[2]; } @@ -95,12 +104,14 @@ class Url // 检查别名路由 $alias = $this->app['route']->getAlias(); $matchAlias = false; + if ($alias) { // 别名路由解析 foreach ($alias as $key => $val) { if (is_array($val)) { $val = $val[0]; } + if (0 === strpos($url, $val)) { $url = $key . substr($url, strlen($val)); $matchAlias = true; @@ -108,10 +119,12 @@ class Url } } } + if (!$matchAlias) { // 路由标识不存在 直接解析 $url = $this->parseUrl($url, $domain); } + if (isset($info['query'])) { // 解析地址里面参数 合并到vars parse_str($info['query'], $params); @@ -121,8 +134,8 @@ class Url // 检测URL绑定 if (!$this->bindCheck) { - $bind = $this->app['route']->getBind(); + if (0 === strpos($url, $bind)) { $url = substr($url, strlen($bind) + 1); } @@ -137,6 +150,7 @@ class Url // 锚点 $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 if (!empty($vars)) { // 添加参数 @@ -145,6 +159,7 @@ class Url $url .= $suffix . '?' . $vars . $anchor; } else { $paramType = $this->app['config']->get('url_param_type'); + foreach ($vars as $var => $val) { if ('' !== trim($val)) { if ($paramType) { @@ -154,6 +169,7 @@ class Url } } } + $url .= $suffix . $anchor; } } else { @@ -191,6 +207,7 @@ class Url $module = $module ? $module . '/' : ''; $controller = Loader::parseName($request->controller()); + if ('' == $url) { // 空字符串输出当前的 模块/控制器/操作 $url = $module . $controller . '/' . $request->action(); @@ -213,14 +230,14 @@ class Url return ''; } - $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(); - $rootDomain = $this->app['config']->get('url_domain_root'); - if (true === $domain) { + // 自动判断域名 - $domain = $host; + $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(); + $rootDomain = $this->app['config']->get('url_domain_root'); $domains = $this->app['route']->getDomains(); + if ($domains) { $route_domain = array_keys($domains); foreach ($route_domain as $domain_prefix) { @@ -230,6 +247,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; @@ -239,28 +257,16 @@ class Url if (!empty($rootDomain)) { $domain .= $rootDomain; } + break; } } } } } - - } else { - if (empty($rootDomain)) { - $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; - } - - if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { - $domain .= '.' . $rootDomain; - } } - if (false !== strpos($domain, '://')) { - $scheme = ''; - } else { - $scheme = $this->app['request']->isSsl() || $this->app['config']->get('is_https') ? 'https://' : 'http://'; - } + $scheme = $this->app['request']->isSsl() || $this->app['config']->get('is_https') ? 'https://' : 'http://'; return $scheme . $domain; } -- Gitee From e2d3fe7260425399016618726bbb463a4f4de115 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 17:59:13 +0800 Subject: [PATCH 0408/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDebug=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Debug.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/Debug.php b/library/think/Debug.php index 8006fffc..f8d774f8 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -12,7 +12,7 @@ namespace think; use think\exception\ClassNotFoundException; -use think\model\Collection; +use think\model\Collection as ModelCollection; use think\response\Redirect; class Debug @@ -182,7 +182,7 @@ class Debug public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) { $label = (null === $label) ? '' : rtrim($label) . ':'; - if ($var instanceof Model || $var instanceof Collection) { + if ($var instanceof Model || $var instanceof ModelCollection) { $var = $var->toArray(); } @@ -201,7 +201,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo($output); + echo ($output); return; } else { return $output; -- Gitee From ca9794d64797cf3d9a675933af76cb7674a42b0f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 18 Jul 2017 19:07:52 +0800 Subject: [PATCH 0409/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=9A=84=E6=9D=A1=E4=BB=B6=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index d556f019..13cedebd 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -64,14 +64,8 @@ class RuleGroup extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { - if ($this->parent) { - $option = array_merge($this->parent->getOption(), $this->option); - } else { - $option = $this->option; - } - // 检查参数有效性 - if (!$this->checkOption($option, $request)) { + if (!$this->checkOption($this->option, $request)) { return false; } @@ -100,12 +94,14 @@ class RuleGroup extends Rule $method = strtolower($request->method()); $rules = array_merge($this->rules['*'], $this->rules[$method]); + if ($this->parent) { + $this->option = array_merge($this->parent->getOption(), $this->option); + } + if (isset($this->option['complete_match'])) { $completeMatch = $this->option['complete_match']; } - $this->option = $option; - if (isset($rules[$url])) { // 快速定位 $item = $rules[$url]; -- Gitee From 2d61ec842147f5bc484682f5fde961063ac05006 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 19 Jul 2017 10:39:09 +0800 Subject: [PATCH 0410/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index affbea38..1e88bd8a 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -104,13 +104,13 @@ class RuleItem extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { - $option = array_merge($this->parent->getOption(), $this->option); - // 检查参数有效性 - if (!$this->checkOption($option, $request)) { + if (!$this->checkOption($this->option, $request)) { return false; } + $option = array_merge($this->parent->getOption(), $this->option); + if (isset($option['ext'])) { // 路由ext参数 优先于系统配置的URL伪静态后缀参数 $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); -- Gitee From 534d4e6cb7ff381bf5ae921687e54474db731f62 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 19 Jul 2017 11:30:20 +0800 Subject: [PATCH 0411/1384] =?UTF-8?q?=E8=BD=AF=E5=88=A0=E9=99=A4=E5=92=8C?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E6=9F=A5=E8=AF=A2=E8=8C=83=E5=9B=B4=E6=96=B9?= =?UTF-8?q?=E6=B3=95base=E5=88=86=E5=BC=80=20=E4=BA=92=E4=B8=8D=E5=BD=B1?= =?UTF-8?q?=E5=93=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 14 +++++++++++--- library/think/model/concern/SoftDelete.php | 13 ------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 6cf98598..ee279c5b 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -159,9 +159,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { $query = $this->buildQuery(); - // 全局作用域 - if ($useBaseQuery && method_exists($this, 'base')) { - call_user_func_array([$this, 'base'], [ & $query]); + if ($useBaseQuery) { + // 软删除 + if (method_exists($this, 'getDeleteTimeField')) { + $field = $this->getDeleteTimeField(true); + $query->useSoftDelete($field); + } + + // 全局作用域 + if (method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } } // 返回当前模型的数据库查询对象 diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index e63e1623..e41d4aed 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -137,19 +137,6 @@ trait SoftDelete ->update([$name => null]); } - /** - * 查询默认不包含软删除数据 - * @access protected - * @param Query $query 查询对象 - * @return void - */ - protected function base($query) - { - $field = $this->getDeleteTimeField(true); - - $query->useSoftDelete($field); - } - /** * 获取软删除字段 * @access public -- Gitee From 227e17543b08f2dbbee187dc9d0594698930a713 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 19 Jul 2017 11:32:09 +0800 Subject: [PATCH 0412/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1e?= =?UTF-8?q?xt=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 1e88bd8a..cd8b0ae2 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -113,7 +113,7 @@ class RuleItem extends Rule if (isset($option['ext'])) { // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); } return $this->checkRule($request, $url, $depr, $completeMatch, $option); -- Gitee From efba5e0b7c7e27bfab9be4c89ffe089c9186dadb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 19 Jul 2017 19:15:24 +0800 Subject: [PATCH 0413/1384] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=B1=BB=E6=94=B9?= =?UTF-8?q?=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 367 +++++++++++++++++++++++++++---------- 1 file changed, 270 insertions(+), 97 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 76b0018a..b6e0df7d 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -25,7 +25,8 @@ class Validate // 当前验证的规则 protected $rule = []; - + // 验证规则 + protected $item = []; // 验证提示信息 protected $message = []; // 验证字段描述 @@ -139,10 +140,15 @@ class Validate public function rule($name, $rule = '') { if (is_array($name)) { - $this->rule = array_merge($this->rule, $name); + foreach ($name as $key => $val) { + $this->rule($key, $val); + } + if (is_array($rule)) { $this->field = array_merge($this->field, $rule); } + } elseif ($rule instanceof Validate) { + $this->rule[$name] = $rule->getItem(); } else { $this->rule[$name] = $rule; } @@ -150,6 +156,16 @@ class Validate return $this; } + /** + * 获取验证因子 + * @access public + * @return array + */ + public function getItem() + { + return $this->item; + } + /** * 注册扩展验证(类型)规则 * @access public @@ -442,7 +458,7 @@ class Validate // 验证类型 $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; // 验证数据 - $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + $result = call_user_func_array($callback, [$rule, $value, $data, $field, $title]); } else { $result = true; } @@ -475,14 +491,18 @@ class Validate /** * 验证是否和某个字段的值一致 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @param string $field 字段名 * @return bool */ - public function confirm($value, $rule, $data = [], $field = '') + public function confirm($rule = '', $value = null, $data = [], $field = '') { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if ('' == $rule) { if (strpos($field, '_confirm')) { $rule = strstr($field, '_confirm', true); @@ -497,90 +517,120 @@ class Validate /** * 验证是否和某个字段的值是否不同 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function different($value, $rule, $data = []) + public function different($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return $this->getDataValue($data, $rule) != $value; } /** * 验证是否大于等于某个值 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function egt($value, $rule, $data = []) + public function egt($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return $value >= $this->getDataValue($data, $rule); } /** * 验证是否大于某个值 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function gt($value, $rule, $data) + public function gt($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return $value > $this->getDataValue($data, $rule); } /** * 验证是否小于等于某个值 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function elt($value, $rule, $data = []) + public function elt($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return $value <= $this->getDataValue($data, $rule); } /** * 验证是否小于某个值 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function lt($value, $rule, $data = []) + public function lt($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return $value < $this->getDataValue($data, $rule); } /** * 验证是否等于某个值 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function eq($value, $rule) + public function eq($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return $value == $rule; } /** * 验证字段值是否为有效格式 * @access public - * @param mixed $value 字段值 * @param string $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 验证数据 * @return bool */ - public function is($value, $rule, $data = []) + public function is($rule, $value = null, $data = []) { + if (is_null($value)) { + // 设置验证规则 + $this->item[] = $rule; + return $this; + } + switch ($rule) { case 'require': // 必须 @@ -596,35 +646,35 @@ class Validate break; case 'alpha': // 只允许字母 - $result = $this->regex($value, '/^[A-Za-z]+$/'); + $result = $this->regex('/^[A-Za-z]+$/', $value); break; case 'alphaNum': // 只允许字母和数字 - $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); + $result = $this->regex('/^[A-Za-z0-9]+$/', $value); break; case 'alphaDash': // 只允许字母、数字和下划线 破折号 - $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); + $result = $this->regex('/^[A-Za-z0-9\-\_]+$/', $value); break; case 'chs': // 只允许汉字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); + $result = $this->regex('/^[\x{4e00}-\x{9fa5}]+$/u', $value); break; case 'chsAlpha': // 只允许汉字、字母 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); + $result = $this->regex('/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', $value); break; case 'chsAlphaNum': // 只允许汉字、字母和数字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); + $result = $this->regex('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', $value); break; case 'chsDash': // 只允许汉字、字母、数字和下划线_及破折号- - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); + $result = $this->regex('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', $value); break; case 'mobile': // 国内手机号码 - $result = $this->regex($value, '/^1[3|4|5|7|8][0-9]\d{8}$/'); + $result = $this->regex('/^1[3|4|5|7|8][0-9]\d{8}$/', $value); break; case 'activeUrl': // 是否为有效的网址 @@ -632,26 +682,26 @@ class Validate break; case 'ip': // 是否为IP地址 - $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); + $result = $this->filter([FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], $value); break; case 'url': // 是否为一个URL地址 - $result = $this->filter($value, FILTER_VALIDATE_URL); + $result = $this->filter(FILTER_VALIDATE_URL, $value); break; case 'float': // 是否为float - $result = $this->filter($value, FILTER_VALIDATE_FLOAT); + $result = $this->filter(FILTER_VALIDATE_FLOAT, $value); break; case 'number': $result = is_numeric($value); break; case 'integer': // 是否为整型 - $result = $this->filter($value, FILTER_VALIDATE_INT); + $result = $this->filter(FILTER_VALIDATE_INT, $value); break; case 'email': // 是否为邮箱地址 - $result = $this->filter($value, FILTER_VALIDATE_EMAIL); + $result = $this->filter(FILTER_VALIDATE_EMAIL, $value); break; case 'boolean': // 是否为布尔值 @@ -668,7 +718,7 @@ class Validate $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); break; case 'token': - $result = $this->token($value, '__token__', $data); + $result = $this->token('__token__', $value, $data); break; default: if (isset(self::$type[$rule])) { @@ -676,7 +726,7 @@ class Validate $result = call_user_func_array(self::$type[$rule], [$value]); } else { // 正则验证 - $result = $this->regex($value, $rule); + $result = $this->regex($rule, $value); } } @@ -697,12 +747,16 @@ class Validate /** * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function activeUrl($value, $rule = 'MX') + public function activeUrl($rule = 'MX', $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { $rule = 'MX'; } @@ -713,28 +767,37 @@ class Validate /** * 验证是否有效IP * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 ipv4 ipv6 + * @param mixed $value 字段值 * @return bool */ - public function ip($value, $rule = 'ipv4') + public function ip($rule = 'ipv4', $value = null) { + if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } - return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + + return $this->filter([FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4], $value); } /** * 验证上传文件后缀 * @access public - * @param mixed $file 上传文件 * @param mixed $rule 验证规则 + * @param mixed $file 上传文件 * @return bool */ - public function fileExt($file, $rule) + public function fileExt($rule, $file = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (!($file instanceof File)) { return false; } @@ -758,12 +821,16 @@ class Validate /** * 验证上传文件类型 * @access public - * @param mixed $file 上传文件 * @param mixed $rule 验证规则 + * @param mixed $file 上传文件 * @return bool */ - public function fileMime($file, $rule) + public function fileMime($rule, $file = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (!($file instanceof File)) { return false; } @@ -787,12 +854,16 @@ class Validate /** * 验证上传文件大小 * @access public - * @param mixed $file 上传文件 * @param mixed $rule 验证规则 + * @param mixed $file 上传文件 * @return bool */ - public function fileSize($file, $rule) + public function fileSize($rule, $file = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (!($file instanceof File)) { return false; } @@ -812,12 +883,16 @@ class Validate /** * 验证图片的宽高及类型 * @access public - * @param mixed $file 上传文件 * @param mixed $rule 验证规则 + * @param mixed $file 上传文件 * @return bool */ - public function image($file, $rule) + public function image($rule, $file = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (!($file instanceof File)) { return false; } @@ -850,12 +925,16 @@ class Validate /** * 验证请求类型 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function method($value, $rule) + public function method($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + $method = Facade::make('request')->method(); return strtoupper($rule) == $method; } @@ -863,27 +942,35 @@ class Validate /** * 验证时间和日期是否符合指定格式 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function dateFormat($value, $rule) + public function dateFormat($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + $info = date_parse_from_format($rule, $value); return 0 == $info['warning_count'] && 0 == $info['error_count']; } /** * 验证是否唯一 - * @access protected - * @param mixed $value 字段值 + * @access public * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param mixed $value 字段值 * @param array $data 数据 * @param string $field 验证字段名 * @return bool */ - protected function unique($value, $rule, $data, $field) + public function unique($rule, $value = null, $data = [], $field = '') { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_string($rule)) { $rule = explode(',', $rule); } @@ -929,26 +1016,34 @@ class Validate /** * 使用行为类验证 - * @access protected - * @param mixed $value 字段值 + * @access public * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return mixed */ - protected function behavior($value, $rule, $data) + public function behavior($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return Facade::make('hook')->exec($rule, $data); } /** * 使用filter_var方式验证 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function filter($value, $rule) + public function filter($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_string($rule) && strpos($rule, ',')) { list($rule, $param) = explode(',', $rule); } elseif (is_array($rule)) { @@ -963,14 +1058,18 @@ class Validate /** * 验证某个字段等于某个值的时候必须 - * @access protected - * @param mixed $value 字段值 + * @access public * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - protected function requireIf($value, $rule, $data) + public function requireIf($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + list($field, $val) = explode(',', $rule); if ($this->getDataValue($data, $field) == $val) { @@ -982,14 +1081,18 @@ class Validate /** * 通过回调方法验证某个字段是否必须 - * @access protected - * @param mixed $value 字段值 + * @access public * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - protected function requireCallback($value, $rule, $data) + public function requireCallback($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + $result = call_user_func_array($rule, [$value, $data]); if ($result) { @@ -1001,14 +1104,18 @@ class Validate /** * 验证某个字段有值的情况下必须 - * @access protected - * @param mixed $value 字段值 + * @access public * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - protected function requireWith($value, $rule, $data) + public function requireWith($rule, $value = null, $data = []) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + $val = $this->getDataValue($data, $rule); if (!empty($val)) { @@ -1021,36 +1128,48 @@ class Validate /** * 验证是否在范围内 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function in($value, $rule) + public function in($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证是否不在某个范围 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function notIn($value, $rule) + public function notIn($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * between验证数据 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function between($value, $rule) + public function between($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1062,12 +1181,16 @@ class Validate /** * 使用notbetween验证数据 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function notBetween($value, $rule) + public function notBetween($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1079,12 +1202,16 @@ class Validate /** * 验证数据长度 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function length($value, $rule) + public function length($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { @@ -1106,12 +1233,16 @@ class Validate /** * 验证数据最大长度 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function max($value, $rule) + public function max($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { @@ -1126,12 +1257,16 @@ class Validate /** * 验证数据最小长度 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function min($value, $rule) + public function min($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { @@ -1146,36 +1281,48 @@ class Validate /** * 验证日期 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function after($value, $rule) + public function after($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return strtotime($value) >= strtotime($rule); } /** * 验证日期 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function before($value, $rule) + public function before($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return strtotime($value) <= strtotime($rule); } /** * 验证有效期 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @return bool */ - public function expire($value, $rule) + public function expire($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1196,36 +1343,48 @@ class Validate /** * 验证IP许可 * @access public - * @param string $value 字段值 * @param mixed $rule 验证规则 + * @param string $value 字段值 * @return mixed */ - public function allowIp($value, $rule) + public function allowIp($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证IP禁用 * @access public - * @param string $value 字段值 * @param mixed $rule 验证规则 + * @param string $value 字段值 * @return mixed */ - public function denyIp($value, $rule) + public function denyIp($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); } /** * 使用正则验证数据 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @param mixed $value 字段值 * @return mixed */ - public function regex($value, $rule) + public function regex($rule, $value = null) { + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + if (isset($this->regex[$rule])) { $rule = $this->regex[$rule]; } @@ -1241,14 +1400,17 @@ class Validate /** * 验证表单令牌 * @access public - * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function token($value, $rule, $data) + public function token($rule = '__token__', $value = null, $data = []) { - $rule = !empty($rule) ? $rule : '__token__'; + if (is_null($value)) { + return $this->addValidateItem(__FUNCTION__, $rule); + } + $session = Facade::make('session'); if (!isset($data[$rule]) || !$session->has($rule)) { @@ -1340,6 +1502,17 @@ class Validate return $msg; } + protected function addValidateItem($name, $rule) + { + if (is_array($rule)) { + $this->item[] = [$name => $rule]; + } else { + $this->item[] = $name . ':' . $rule; + } + + return $this; + } + /** * 获取数据验证的场景 * @access protected -- Gitee From 2e015b731ae114ae49951b13f924014ee646bf03 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 08:57:26 +0800 Subject: [PATCH 0414/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=20=E5=A2=9E=E5=8A=A0=E9=AA=8C=E8=AF=81=E5=9B=A0?= =?UTF-8?q?=E5=AD=90=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 375 +++++++----------------- library/think/validate/ValidateItem.php | 116 ++++++++ 2 files changed, 218 insertions(+), 273 deletions(-) create mode 100644 library/think/validate/ValidateItem.php diff --git a/library/think/Validate.php b/library/think/Validate.php index b6e0df7d..39426e14 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -12,6 +12,7 @@ namespace think; use think\exception\ClassNotFoundException; +use think\validate\ValidateItem; class Validate { @@ -25,8 +26,7 @@ class Validate // 当前验证的规则 protected $rule = []; - // 验证规则 - protected $item = []; + // 验证提示信息 protected $message = []; // 验证字段描述 @@ -140,15 +140,10 @@ class Validate public function rule($name, $rule = '') { if (is_array($name)) { - foreach ($name as $key => $val) { - $this->rule($key, $val); - } - + $this->rule = array_merge($this->rule, $name); if (is_array($rule)) { $this->field = array_merge($this->field, $rule); } - } elseif ($rule instanceof Validate) { - $this->rule[$name] = $rule->getItem(); } else { $this->rule[$name] = $rule; } @@ -156,16 +151,6 @@ class Validate return $this; } - /** - * 获取验证因子 - * @access public - * @return array - */ - public function getItem() - { - return $this->item; - } - /** * 注册扩展验证(类型)规则 * @access public @@ -347,8 +332,6 @@ class Validate foreach ($rules as $key => $rule) { // field => 'rule1|rule2...' field => ['rule1','rule2',...] - $msg = []; - if (strpos($key, '|')) { // 字段|描述 用于指定属性名称 list($key, $title) = explode('|', $key); @@ -368,8 +351,11 @@ class Validate if ($rule instanceof \Closure) { // 匿名函数验证 支持传入当前字段和所有字段两个数据 $result = call_user_func_array($rule, [$value, $data]); + } elseif ($rule instanceof ValidateItem) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); } else { - $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + $result = $this->checkItem($key, $value, $rule, $data, $title); } if (true !== $result) { @@ -458,7 +444,7 @@ class Validate // 验证类型 $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; // 验证数据 - $result = call_user_func_array($callback, [$rule, $value, $data, $field, $title]); + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); } else { $result = true; } @@ -491,18 +477,14 @@ class Validate /** * 验证是否和某个字段的值一致 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @param string $field 字段名 * @return bool */ - public function confirm($rule = '', $value = null, $data = [], $field = '') + public function confirm($value, $rule, $data = [], $field = '') { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if ('' == $rule) { if (strpos($field, '_confirm')) { $rule = strstr($field, '_confirm', true); @@ -517,120 +499,90 @@ class Validate /** * 验证是否和某个字段的值是否不同 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - public function different($rule, $value = null, $data = []) + public function different($value, $rule, $data = []) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return $this->getDataValue($data, $rule) != $value; } /** * 验证是否大于等于某个值 * @access public + * @param mixed $value 字段值 * @param mixed $rule 验证规则 - * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function egt($rule, $value = null, $data = []) + public function egt($value, $rule, $data = []) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return $value >= $this->getDataValue($data, $rule); } /** * 验证是否大于某个值 * @access public + * @param mixed $value 字段值 * @param mixed $rule 验证规则 - * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function gt($rule, $value = null, $data = []) + public function gt($value, $rule, $data) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return $value > $this->getDataValue($data, $rule); } /** * 验证是否小于等于某个值 * @access public + * @param mixed $value 字段值 * @param mixed $rule 验证规则 - * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function elt($rule, $value = null, $data = []) + public function elt($value, $rule, $data = []) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return $value <= $this->getDataValue($data, $rule); } /** * 验证是否小于某个值 * @access public + * @param mixed $value 字段值 * @param mixed $rule 验证规则 - * @param mixed $value 字段值 * @param array $data 数据 * @return bool */ - public function lt($rule, $value = null, $data = []) + public function lt($value, $rule, $data = []) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return $value < $this->getDataValue($data, $rule); } /** * 验证是否等于某个值 * @access public + * @param mixed $value 字段值 * @param mixed $rule 验证规则 - * @param mixed $value 字段值 * @return bool */ - public function eq($rule, $value = null) + public function eq($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return $value == $rule; } /** * 验证字段值是否为有效格式 * @access public + * @param mixed $value 字段值 * @param string $rule 验证规则 - * @param mixed $value 字段值 * @param array $data 验证数据 * @return bool */ - public function is($rule, $value = null, $data = []) + public function is($value, $rule, $data = []) { - if (is_null($value)) { - // 设置验证规则 - $this->item[] = $rule; - return $this; - } - switch ($rule) { case 'require': // 必须 @@ -646,35 +598,35 @@ class Validate break; case 'alpha': // 只允许字母 - $result = $this->regex('/^[A-Za-z]+$/', $value); + $result = $this->regex($value, '/^[A-Za-z]+$/'); break; case 'alphaNum': // 只允许字母和数字 - $result = $this->regex('/^[A-Za-z0-9]+$/', $value); + $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); break; case 'alphaDash': // 只允许字母、数字和下划线 破折号 - $result = $this->regex('/^[A-Za-z0-9\-\_]+$/', $value); + $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); break; case 'chs': // 只允许汉字 - $result = $this->regex('/^[\x{4e00}-\x{9fa5}]+$/u', $value); + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); break; case 'chsAlpha': // 只允许汉字、字母 - $result = $this->regex('/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', $value); + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); break; case 'chsAlphaNum': // 只允许汉字、字母和数字 - $result = $this->regex('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', $value); + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); break; case 'chsDash': // 只允许汉字、字母、数字和下划线_及破折号- - $result = $this->regex('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', $value); + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); break; case 'mobile': // 国内手机号码 - $result = $this->regex('/^1[3|4|5|7|8][0-9]\d{8}$/', $value); + $result = $this->regex($value, '/^1[3|4|5|7|8][0-9]\d{8}$/'); break; case 'activeUrl': // 是否为有效的网址 @@ -682,26 +634,26 @@ class Validate break; case 'ip': // 是否为IP地址 - $result = $this->filter([FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], $value); + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); break; case 'url': // 是否为一个URL地址 - $result = $this->filter(FILTER_VALIDATE_URL, $value); + $result = $this->filter($value, FILTER_VALIDATE_URL); break; case 'float': // 是否为float - $result = $this->filter(FILTER_VALIDATE_FLOAT, $value); + $result = $this->filter($value, FILTER_VALIDATE_FLOAT); break; case 'number': $result = is_numeric($value); break; case 'integer': // 是否为整型 - $result = $this->filter(FILTER_VALIDATE_INT, $value); + $result = $this->filter($value, FILTER_VALIDATE_INT); break; case 'email': // 是否为邮箱地址 - $result = $this->filter(FILTER_VALIDATE_EMAIL, $value); + $result = $this->filter($value, FILTER_VALIDATE_EMAIL); break; case 'boolean': // 是否为布尔值 @@ -718,7 +670,7 @@ class Validate $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); break; case 'token': - $result = $this->token('__token__', $value, $data); + $result = $this->token($value, '__token__', $data); break; default: if (isset(self::$type[$rule])) { @@ -726,7 +678,7 @@ class Validate $result = call_user_func_array(self::$type[$rule], [$value]); } else { // 正则验证 - $result = $this->regex($rule, $value); + $result = $this->regex($value, $rule); } } @@ -747,16 +699,12 @@ class Validate /** * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 * @access public + * @param mixed $value 字段值 * @param mixed $rule 验证规则 - * @param mixed $value 字段值 * @return bool */ - public function activeUrl($rule = 'MX', $value = null) + public function activeUrl($value, $rule = 'MX') { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { $rule = 'MX'; } @@ -767,37 +715,28 @@ class Validate /** * 验证是否有效IP * @access public - * @param mixed $rule 验证规则 ipv4 ipv6 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 * @return bool */ - public function ip($rule = 'ipv4', $value = null) + public function ip($value, $rule = 'ipv4') { - if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - - return $this->filter([FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4], $value); + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); } /** * 验证上传文件后缀 * @access public - * @param mixed $rule 验证规则 * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - public function fileExt($rule, $file = null) + public function fileExt($file, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (!($file instanceof File)) { return false; } @@ -821,16 +760,12 @@ class Validate /** * 验证上传文件类型 * @access public - * @param mixed $rule 验证规则 * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - public function fileMime($rule, $file = null) + public function fileMime($file, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (!($file instanceof File)) { return false; } @@ -854,16 +789,12 @@ class Validate /** * 验证上传文件大小 * @access public - * @param mixed $rule 验证规则 * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - public function fileSize($rule, $file = null) + public function fileSize($file, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (!($file instanceof File)) { return false; } @@ -883,16 +814,12 @@ class Validate /** * 验证图片的宽高及类型 * @access public - * @param mixed $rule 验证规则 * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 * @return bool */ - public function image($rule, $file = null) + public function image($file, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (!($file instanceof File)) { return false; } @@ -925,16 +852,12 @@ class Validate /** * 验证请求类型 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function method($rule, $value = null) + public function method($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - $method = Facade::make('request')->method(); return strtoupper($rule) == $method; } @@ -942,35 +865,27 @@ class Validate /** * 验证时间和日期是否符合指定格式 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function dateFormat($rule, $value = null) + public function dateFormat($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - $info = date_parse_from_format($rule, $value); return 0 == $info['warning_count'] && 0 == $info['error_count']; } /** * 验证是否唯一 - * @access public - * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @access protected * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 * @param array $data 数据 * @param string $field 验证字段名 * @return bool */ - public function unique($rule, $value = null, $data = [], $field = '') + protected function unique($value, $rule, $data, $field) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1016,34 +931,26 @@ class Validate /** * 使用行为类验证 - * @access public - * @param mixed $rule 验证规则 + * @access protected * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @return mixed */ - public function behavior($rule, $value = null, $data = []) + protected function behavior($value, $rule, $data) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return Facade::make('hook')->exec($rule, $data); } /** * 使用filter_var方式验证 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function filter($rule, $value = null) + public function filter($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_string($rule) && strpos($rule, ',')) { list($rule, $param) = explode(',', $rule); } elseif (is_array($rule)) { @@ -1058,18 +965,14 @@ class Validate /** * 验证某个字段等于某个值的时候必须 - * @access public - * @param mixed $rule 验证规则 + * @access protected * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - public function requireIf($rule, $value = null, $data = []) + protected function requireIf($value, $rule, $data) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - list($field, $val) = explode(',', $rule); if ($this->getDataValue($data, $field) == $val) { @@ -1081,18 +984,14 @@ class Validate /** * 通过回调方法验证某个字段是否必须 - * @access public - * @param mixed $rule 验证规则 + * @access protected * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - public function requireCallback($rule, $value = null, $data = []) + protected function requireCallback($value, $rule, $data) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - $result = call_user_func_array($rule, [$value, $data]); if ($result) { @@ -1104,18 +1003,14 @@ class Validate /** * 验证某个字段有值的情况下必须 - * @access public - * @param mixed $rule 验证规则 + * @access protected * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - public function requireWith($rule, $value = null, $data = []) + protected function requireWith($value, $rule, $data) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - $val = $this->getDataValue($data, $rule); if (!empty($val)) { @@ -1128,48 +1023,36 @@ class Validate /** * 验证是否在范围内 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function in($rule, $value = null) + public function in($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证是否不在某个范围 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function notIn($rule, $value = null) + public function notIn($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** * between验证数据 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function between($rule, $value = null) + public function between($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1181,16 +1064,12 @@ class Validate /** * 使用notbetween验证数据 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function notBetween($rule, $value = null) + public function notBetween($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1202,16 +1081,12 @@ class Validate /** * 验证数据长度 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function length($rule, $value = null) + public function length($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { @@ -1233,16 +1108,12 @@ class Validate /** * 验证数据最大长度 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function max($rule, $value = null) + public function max($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { @@ -1257,16 +1128,12 @@ class Validate /** * 验证数据最小长度 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function min($rule, $value = null) + public function min($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { @@ -1281,48 +1148,36 @@ class Validate /** * 验证日期 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function after($rule, $value = null) + public function after($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return strtotime($value) >= strtotime($rule); } /** * 验证日期 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function before($rule, $value = null) + public function before($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return strtotime($value) <= strtotime($rule); } /** * 验证有效期 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @return bool */ - public function expire($rule, $value = null) + public function expire($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (is_string($rule)) { $rule = explode(',', $rule); } @@ -1343,48 +1198,36 @@ class Validate /** * 验证IP许可 * @access public - * @param mixed $rule 验证规则 * @param string $value 字段值 + * @param mixed $rule 验证规则 * @return mixed */ - public function allowIp($rule, $value = null) + public function allowIp($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); } /** * 验证IP禁用 * @access public - * @param mixed $rule 验证规则 * @param string $value 字段值 + * @param mixed $rule 验证规则 * @return mixed */ - public function denyIp($rule, $value = null) + public function denyIp($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); } /** * 使用正则验证数据 * @access public - * @param mixed $rule 验证规则 正则规则或者预定义正则名 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 * @return mixed */ - public function regex($rule, $value = null) + public function regex($value, $rule) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - if (isset($this->regex[$rule])) { $rule = $this->regex[$rule]; } @@ -1400,17 +1243,14 @@ class Validate /** * 验证表单令牌 * @access public - * @param mixed $rule 验证规则 * @param mixed $value 字段值 + * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - public function token($rule = '__token__', $value = null, $data = []) + public function token($value, $rule, $data) { - if (is_null($value)) { - return $this->addValidateItem(__FUNCTION__, $rule); - } - + $rule = !empty($rule) ? $rule : '__token__'; $session = Facade::make('session'); if (!isset($data[$rule]) || !$session->has($rule)) { @@ -1502,17 +1342,6 @@ class Validate return $msg; } - protected function addValidateItem($name, $rule) - { - if (is_array($rule)) { - $this->item[] = [$name => $rule]; - } else { - $this->item[] = $name . ':' . $rule; - } - - return $this; - } - /** * 获取数据验证的场景 * @access protected diff --git a/library/think/validate/ValidateItem.php b/library/think/validate/ValidateItem.php new file mode 100644 index 00000000..5e803109 --- /dev/null +++ b/library/think/validate/ValidateItem.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\validate; + +use think\Validate; + +/** + * Class ValidateItem + * @package think\validate + * @method ValidateItem confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateItem different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + */ +class ValidateItem +{ + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access public + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + public function addItem($name, $rule = '', $msg = '') + { + if (is_array($rule)) { + $this->rule[] = [$name => $rule]; + } else { + $this->rule[] = $name . ':' . $rule; + } + + $this->messge[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg() + { + return $this->message; + } + + /** + * 设置验证名称 + * @access public + * @return $this + */ + public function title($title) + { + $this->title = $title; + return $this; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param string $rule 验证规则 + * @param string $msg 提示信息 + * @return bool + */ + public static function is($rule, $msg = '') + { + $validateItem = new static; + + return $validateItem->addItem($rule, '', $msg); + } + + public static function __callStatic($method, $args) + { + $validateItem = new static(); + + array_unshift($args, $method); + + return call_user_func_array([$validateItem, 'addItem'], $args); + } +} -- Gitee From d08d43c7c3ecafe309d6de592d57b92fa82cefee Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 11:15:02 +0800 Subject: [PATCH 0415/1384] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=B1=BB=E6=94=B9?= =?UTF-8?q?=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Facade.php | 5 +++ library/think/validate/ValidateItem.php | 49 +++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/library/think/Facade.php b/library/think/Facade.php index 6a15643b..73e893b1 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -15,6 +15,7 @@ class Facade { protected static $bind = []; + protected static $alwaysNewInstance; /** * 绑定类的静态代理 @@ -57,6 +58,10 @@ class Facade $class = self::$bind[$class]; } + if (static::$alwaysNewInstance) { + $newInstance = true; + } + return Container::getInstance()->make($class, $args, $newInstance); } diff --git a/library/think/validate/ValidateItem.php b/library/think/validate/ValidateItem.php index 5e803109..d3350135 100644 --- a/library/think/validate/ValidateItem.php +++ b/library/think/validate/ValidateItem.php @@ -18,9 +18,44 @@ use think\Validate; * @package think\validate * @method ValidateItem confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 * @method ValidateItem different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateItem egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateItem gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateItem elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateItem lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateItem eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateItem in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateItem notin(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateItem between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateItem notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateItem length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateItem max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateItem min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateItem after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateItem before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateItem expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateItem allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateItem denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateItem regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateItem token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateItem is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateItem activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateItem ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateItem fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateItem fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateItem fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateItem image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateItem method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateItem dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateItem unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateItem behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateItem filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateItem requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateItem requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateItem requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 */ class ValidateItem { + // 验证字段的名称 protected $title; // 当前验证规则 @@ -45,7 +80,7 @@ class ValidateItem $this->rule[] = $name . ':' . $rule; } - $this->messge[] = $msg; + $this->message[] = $msg; return $this; } @@ -61,7 +96,7 @@ class ValidateItem } /** - * 获取名称 + * 获取验证字段名称 * @access public * @return string */ @@ -81,13 +116,14 @@ class ValidateItem } /** - * 设置验证名称 + * 设置验证字段名称 * @access public * @return $this */ public function title($title) { $this->title = $title; + return $this; } @@ -105,6 +141,13 @@ class ValidateItem return $validateItem->addItem($rule, '', $msg); } + public function __call($method, $args) + { + array_unshift($args, $method); + + return call_user_func_array([$this, 'addItem'], $args); + } + public static function __callStatic($method, $args) { $validateItem = new static(); -- Gitee From b52f0b73e1d11b2d6f078f8969bd6851573241a9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 12:24:46 +0800 Subject: [PATCH 0416/1384] =?UTF-8?q?ValidateItem=E7=B1=BB=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E4=B8=BAValidateRule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 4 +- .../{ValidateItem.php => ValidateRule.php} | 84 +++++++++---------- 2 files changed, 44 insertions(+), 44 deletions(-) rename library/think/validate/{ValidateItem.php => ValidateRule.php} (59%) diff --git a/library/think/Validate.php b/library/think/Validate.php index 39426e14..6b6f339e 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -12,7 +12,7 @@ namespace think; use think\exception\ClassNotFoundException; -use think\validate\ValidateItem; +use think\validate\ValidateRule; class Validate { @@ -351,7 +351,7 @@ class Validate if ($rule instanceof \Closure) { // 匿名函数验证 支持传入当前字段和所有字段两个数据 $result = call_user_func_array($rule, [$value, $data]); - } elseif ($rule instanceof ValidateItem) { + } elseif ($rule instanceof ValidateRule) { // 验证因子 $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); } else { diff --git a/library/think/validate/ValidateItem.php b/library/think/validate/ValidateRule.php similarity index 59% rename from library/think/validate/ValidateItem.php rename to library/think/validate/ValidateRule.php index d3350135..73578141 100644 --- a/library/think/validate/ValidateItem.php +++ b/library/think/validate/ValidateRule.php @@ -14,46 +14,46 @@ namespace think\validate; use think\Validate; /** - * Class ValidateItem + * Class ValidateRule * @package think\validate - * @method ValidateItem confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 - * @method ValidateItem different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 - * @method ValidateItem egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 - * @method ValidateItem gt(mixed $rule, string $msg = '') static 验证是否大于某个值 - * @method ValidateItem elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 - * @method ValidateItem lt(mixed $rule, string $msg = '') static 验证是否小于某个值 - * @method ValidateItem eg(mixed $rule, string $msg = '') static 验证是否等于某个值 - * @method ValidateItem in(mixed $rule, string $msg = '') static 验证是否在范围内 - * @method ValidateItem notin(mixed $rule, string $msg = '') static 验证是否不在某个范围 - * @method ValidateItem between(mixed $rule, string $msg = '') static 验证是否在某个区间 - * @method ValidateItem notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 - * @method ValidateItem length(mixed $rule, string $msg = '') static 验证数据长度 - * @method ValidateItem max(mixed $rule, string $msg = '') static 验证数据最大长度 - * @method ValidateItem min(mixed $rule, string $msg = '') static 验证数据最小长度 - * @method ValidateItem after(mixed $rule, string $msg = '') static 验证日期 - * @method ValidateItem before(mixed $rule, string $msg = '') static 验证日期 - * @method ValidateItem expire(mixed $rule, string $msg = '') static 验证有效期 - * @method ValidateItem allowIp(mixed $rule, string $msg = '') static 验证IP许可 - * @method ValidateItem denyIp(mixed $rule, string $msg = '') static 验证IP禁用 - * @method ValidateItem regex(mixed $rule, string $msg = '') static 使用正则验证数据 - * @method ValidateItem token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 - * @method ValidateItem is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 - * @method ValidateItem activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP - * @method ValidateItem ip(mixed $rule, string $msg = '') static 验证是否有效IP - * @method ValidateItem fileExt(mixed $rule, string $msg = '') static 验证文件后缀 - * @method ValidateItem fileMime(mixed $rule, string $msg = '') static 验证文件类型 - * @method ValidateItem fileSize(mixed $rule, string $msg = '') static 验证文件大小 - * @method ValidateItem image(mixed $rule, string $msg = '') static 验证图像文件 - * @method ValidateItem method(mixed $rule, string $msg = '') static 验证请求类型 - * @method ValidateItem dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 - * @method ValidateItem unique(mixed $rule, string $msg = '') static 验证是否唯一 - * @method ValidateItem behavior(mixed $rule, string $msg = '') static 使用行为类验证 - * @method ValidateItem filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 - * @method ValidateItem requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 - * @method ValidateItem requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 - * @method ValidateItem requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notin(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 */ -class ValidateItem +class ValidateRule { // 验证字段的名称 protected $title; @@ -136,9 +136,9 @@ class ValidateItem */ public static function is($rule, $msg = '') { - $validateItem = new static; + $rule = new static; - return $validateItem->addItem($rule, '', $msg); + return $rule->addItem($rule, '', $msg); } public function __call($method, $args) @@ -150,10 +150,10 @@ class ValidateItem public static function __callStatic($method, $args) { - $validateItem = new static(); + $rule = new static(); array_unshift($args, $method); - return call_user_func_array([$validateItem, 'addItem'], $args); + return call_user_func_array([$rule, 'addItem'], $args); } } -- Gitee From 715cbac6af4f34f0dc0bc6a2791b85312afd85ce Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 15:54:26 +0800 Subject: [PATCH 0417/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=20=E6=B7=BB=E5=8A=A0must=E9=AA=8C=E8=AF=81=E8=A7=84?= =?UTF-8?q?=E5=88=99=20=E4=BE=BF=E4=BA=8EValidateRule=E7=B1=BB=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 31 +++++++++++++++++++++++-- library/think/route/Rule.php | 2 +- library/think/validate/ValidateRule.php | 23 ++++-------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 6b6f339e..6661b307 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -35,6 +35,7 @@ class Validate // 验证规则默认提示信息 protected static $typeMsg = [ 'require' => ':attribute不能为空', + 'must' => ':attribute必须', 'number' => ':attribute必须是数字', 'integer' => ':attribute必须是整数', 'float' => ':attribute必须是浮点数', @@ -440,7 +441,8 @@ class Validate } // 如果不是require 有数据才会行验证 - if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + + if ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { // 验证类型 $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; // 验证数据 @@ -452,7 +454,7 @@ class Validate if (false === $result) { // 验证失败 返回错误信息 - if (isset($msg[$i])) { + if (!empty($msg[$i])) { $message = $msg[$i]; if (is_string($message) && strpos($message, '{%') === 0) { $message = Lang::get(substr($message, 2, -1)); @@ -573,6 +575,18 @@ class Validate return $value == $rule; } + /** + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function must($value, $rule = null) + { + return !empty($value) || '0' == $value; + } + /** * 验证字段值是否为有效格式 * @access public @@ -1375,4 +1389,17 @@ class Validate } } + /** + * 动态方法 直接调用is方法进行验证 + * @access protected + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) + { + array_push($args, $method); + + return call_user_func_array([$this, 'is'], $args); + } } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 1e606349..562fe4a2 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -215,7 +215,7 @@ abstract class Rule /** * 绑定验证 * @access public - * @param string $validate 验证器类 + * @param mixed $validate 验证器类 * @param string $scene 验证场景 * @param array $message 验证提示 * @param bool $batch 批量验证 diff --git a/library/think/validate/ValidateRule.php b/library/think/validate/ValidateRule.php index 73578141..c3d229b2 100644 --- a/library/think/validate/ValidateRule.php +++ b/library/think/validate/ValidateRule.php @@ -52,6 +52,7 @@ use think\Validate; * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule=null, string $msg = '') static 必须验证 */ class ValidateRule { @@ -66,18 +67,18 @@ class ValidateRule /** * 添加验证因子 - * @access public + * @access protected * @param string $name 验证名称 * @param mixed $rule 验证规则 * @param string $msg 提示信息 * @return $this */ - public function addItem($name, $rule = '', $msg = '') + protected function addItem($name, $rule = null, $msg = '') { if (is_array($rule)) { - $this->rule[] = [$name => $rule]; + $this->rule[] = $name . ':' . implode(',', $rule); } else { - $this->rule[] = $name . ':' . $rule; + $this->rule[] = $name . (is_null($rule) ? '' : ':' . $rule); } $this->message[] = $msg; @@ -127,20 +128,6 @@ class ValidateRule return $this; } - /** - * 验证字段值是否为有效格式 - * @access public - * @param string $rule 验证规则 - * @param string $msg 提示信息 - * @return bool - */ - public static function is($rule, $msg = '') - { - $rule = new static; - - return $rule->addItem($rule, '', $msg); - } - public function __call($method, $args) { array_unshift($args, $method); -- Gitee From 578cd366c6fc2cb55747be9a3998478bc560f82e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 16:32:12 +0800 Subject: [PATCH 0418/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 5 +++++ library/think/validate/ValidateRule.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 6661b307..45bb673e 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -41,6 +41,7 @@ class Validate 'float' => ':attribute必须是浮点数', 'boolean' => ':attribute必须是布尔值', 'email' => ':attribute格式不符', + 'mobile' => ':attribute格式不符', 'array' => ':attribute必须是数组', 'accepted' => ':attribute必须是yes、on或者1', 'date' => ':attribute不是一个有效的日期或时间格式', @@ -1398,6 +1399,10 @@ class Validate */ public function __call($method, $args) { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + array_push($args, $method); return call_user_func_array([$this, 'is'], $args); diff --git a/library/think/validate/ValidateRule.php b/library/think/validate/ValidateRule.php index c3d229b2..ae793b11 100644 --- a/library/think/validate/ValidateRule.php +++ b/library/think/validate/ValidateRule.php @@ -78,7 +78,7 @@ class ValidateRule if (is_array($rule)) { $this->rule[] = $name . ':' . implode(',', $rule); } else { - $this->rule[] = $name . (is_null($rule) ? '' : ':' . $rule); + $this->rule[] = $name . ($rule ? ':' . $rule : ''); } $this->message[] = $msg; -- Gitee From 4c2f77a6d886686c750111f0b134b83d012f9ae8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 16:43:19 +0800 Subject: [PATCH 0419/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/Validate.php b/library/think/Validate.php index 45bb673e..b9668edd 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -643,6 +643,9 @@ class Validate // 国内手机号码 $result = $this->regex($value, '/^1[3|4|5|7|8][0-9]\d{8}$/'); break; + case 'idCard': + $result = $this->regex($value, '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/'); + break; case 'activeUrl': // 是否为有效的网址 $result = checkdnsrr($value); @@ -671,6 +674,7 @@ class Validate $result = $this->filter($value, FILTER_VALIDATE_EMAIL); break; case 'boolean': + case 'bool': // 是否为布尔值 $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; -- Gitee From 068d20911091a4a3daea9d63f98b372ce1c0a2a2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 17:01:29 +0800 Subject: [PATCH 0420/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/validate/ValidateRule.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/think/validate/ValidateRule.php b/library/think/validate/ValidateRule.php index ae793b11..a3371cf8 100644 --- a/library/think/validate/ValidateRule.php +++ b/library/think/validate/ValidateRule.php @@ -38,6 +38,18 @@ use think\Validate; * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isNumber(mixed $rule, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isChs(mixed $rule, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule, string $msg = '') static 验证字段值是否为有效邮箱格式 * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 -- Gitee From 056a6b302f07033e8a7423e3e19263f846085759 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 17:53:07 +0800 Subject: [PATCH 0421/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=9A=84=E5=B1=9E=E6=80=A7=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsTo.php | 16 ++++++++-------- library/think/model/relation/HasOne.php | 16 ++++++++-------- library/think/model/relation/OneToOne.php | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index df482ab5..6272b668 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -144,11 +144,11 @@ class BelongsTo extends OneToOne $relationModel = $data[$result->$foreignKey]; $relationModel->setParent(clone $result); $relationModel->isUpdate(true); + } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 @@ -179,11 +179,11 @@ class BelongsTo extends OneToOne $relationModel = $data[$result->$foreignKey]; $relationModel->setParent(clone $result); $relationModel->isUpdate(true); + } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 57410bc1..402e0be3 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -150,11 +150,11 @@ class HasOne extends OneToOne $relationModel = $data[$result->$localKey]; $relationModel->setParent(clone $result); $relationModel->isUpdate(true); + } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 @@ -185,11 +185,11 @@ class HasOne extends OneToOne $relationModel = $data[$result->$localKey]; $relationModel->setParent(clone $result); $relationModel->isUpdate(true); + } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); } $result->setRelation(Loader::parseName($relation), $relationModel); diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 34cbc69f..7c840e59 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -287,7 +287,7 @@ abstract class OneToOne extends Relation if (isset($result->$key)) { throw new Exception('bind attr has exists:' . $key); } else { - $result->setAttr($key, $model->$attr); + $result->setAttr($key, $model ? $model->$attr : null); } } } -- Gitee From ecc7d531fa2ba22dceb619edfc1c22ff44174ff8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 20 Jul 2017 18:50:48 +0800 Subject: [PATCH 0422/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E8=A7=84=E5=88=99=E7=9A=84=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 2 +- library/think/validate/ValidateRule.php | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index b9668edd..b8e4f48e 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -1407,7 +1407,7 @@ class Validate $method = substr($method, 2); } - array_push($args, $method); + array_push($args, lcfirst($method)); return call_user_func_array([$this, 'is'], $args); } diff --git a/library/think/validate/ValidateRule.php b/library/think/validate/ValidateRule.php index a3371cf8..c22c8090 100644 --- a/library/think/validate/ValidateRule.php +++ b/library/think/validate/ValidateRule.php @@ -87,10 +87,10 @@ class ValidateRule */ protected function addItem($name, $rule = null, $msg = '') { - if (is_array($rule)) { - $this->rule[] = $name . ':' . implode(',', $rule); + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; } else { - $this->rule[] = $name . ($rule ? ':' . $rule : ''); + $this->rule[] = $name; } $this->message[] = $msg; @@ -142,7 +142,11 @@ class ValidateRule public function __call($method, $args) { - array_unshift($args, $method); + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); return call_user_func_array([$this, 'addItem'], $args); } @@ -151,7 +155,11 @@ class ValidateRule { $rule = new static(); - array_unshift($args, $method); + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); return call_user_func_array([$rule, 'addItem'], $args); } -- Gitee From 74c422aac95e75925767f0b449adcc5456267d94 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 21 Jul 2017 15:32:21 +0800 Subject: [PATCH 0423/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 29 +++++++++++++++---------- library/think/validate/ValidateRule.php | 9 +++++++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index b8e4f48e..c7ea4443 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -438,6 +438,7 @@ class Validate } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) { // 规则已经移除 + $i++; continue; } @@ -467,8 +468,12 @@ class Validate } elseif (true !== $result) { // 返回自定义错误信息 if (is_string($result) && false !== strpos($result, ':')) { - $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); + $result = str_replace( + [':attribute', ':rule'], + [$title, (string) $rule], + $result); } + return $result; } $i++; @@ -598,7 +603,7 @@ class Validate */ public function is($value, $rule, $data = []) { - switch ($rule) { + switch (Loader::parseName($rule, 1, false)) { case 'require': // 必须 $result = !empty($value) || '0' == $value; @@ -896,14 +901,14 @@ class Validate /** * 验证是否唯一 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 * @param array $data 数据 * @param string $field 验证字段名 * @return bool */ - protected function unique($value, $rule, $data, $field) + public function unique($value, $rule, $data, $field) { if (is_string($rule)) { $rule = explode(',', $rule); @@ -950,13 +955,13 @@ class Validate /** * 使用行为类验证 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rule 验证规则 * @param array $data 数据 * @return mixed */ - protected function behavior($value, $rule, $data) + public function behavior($value, $rule, $data) { return Facade::make('hook')->exec($rule, $data); } @@ -984,13 +989,13 @@ class Validate /** * 验证某个字段等于某个值的时候必须 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - protected function requireIf($value, $rule, $data) + public function requireIf($value, $rule, $data) { list($field, $val) = explode(',', $rule); @@ -1003,13 +1008,13 @@ class Validate /** * 通过回调方法验证某个字段是否必须 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - protected function requireCallback($value, $rule, $data) + public function requireCallback($value, $rule, $data) { $result = call_user_func_array($rule, [$value, $data]); @@ -1022,13 +1027,13 @@ class Validate /** * 验证某个字段有值的情况下必须 - * @access protected + * @access public * @param mixed $value 字段值 * @param mixed $rule 验证规则 * @param array $data 数据 * @return bool */ - protected function requireWith($value, $rule, $data) + public function requireWith($value, $rule, $data) { $val = $this->getDataValue($data, $rule); diff --git a/library/think/validate/ValidateRule.php b/library/think/validate/ValidateRule.php index c22c8090..39d4a34a 100644 --- a/library/think/validate/ValidateRule.php +++ b/library/think/validate/ValidateRule.php @@ -24,7 +24,7 @@ use think\Validate; * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 - * @method ValidateRule notin(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 @@ -38,7 +38,13 @@ use think\Validate; * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule, string $msg = '') static 验证字段必须 * @method ValidateRule isNumber(mixed $rule, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule, string $msg = '') static 验证字段值是否为身份证号码 * @method ValidateRule isChs(mixed $rule, string $msg = '') static 验证字段值是否为中文 * @method ValidateRule isChsDash(mixed $rule, string $msg = '') static 验证字段值是否为中文字母及下划线 * @method ValidateRule isChsAlpha(mixed $rule, string $msg = '') static 验证字段值是否为中文和字母 @@ -50,6 +56,7 @@ use think\Validate; * @method ValidateRule isAlphaNum(mixed $rule, string $msg = '') static 验证字段值是否为字母和数字 * @method ValidateRule isAccepted(mixed $rule, string $msg = '') static 验证字段值是否为yes, on, 或是 1 * @method ValidateRule isEmail(mixed $rule, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule, string $msg = '') static 验证字段值是否为有效URL地址 * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 -- Gitee From 423601715210ddd15cf2440a5a3788b979ba5921 Mon Sep 17 00:00:00 2001 From: ThinkPHP Date: Fri, 21 Jul 2017 07:33:16 +0000 Subject: [PATCH 0424/1384] Apply fixes from StyleCI --- library/think/Debug.php | 2 +- library/think/validate/ValidateRule.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/Debug.php b/library/think/Debug.php index f8d774f8..d03b3453 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -201,7 +201,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); + echo($output); return; } else { return $output; diff --git a/library/think/validate/ValidateRule.php b/library/think/validate/ValidateRule.php index 39d4a34a..beb68ca1 100644 --- a/library/think/validate/ValidateRule.php +++ b/library/think/validate/ValidateRule.php @@ -11,8 +11,6 @@ namespace think\validate; -use think\Validate; - /** * Class ValidateRule * @package think\validate -- Gitee From 3e8ca808cb295b947320e3c6c063c857b0013842 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 21 Jul 2017 15:44:11 +0800 Subject: [PATCH 0425/1384] =?UTF-8?q?readme=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 115 +++++------------------------------------------------- 1 file changed, 9 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index b76f9aed..c72896fe 100644 --- a/README.md +++ b/README.md @@ -6,131 +6,34 @@ ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特 + 采用容器统一管理对象 + 支持Facade + 更易用的路由 + + 验证类增强 + 配置和路由目录独立 + 取消系统常量 - + 助手函数增强 + 类库别名机制 + 模型和数据库增强 + 依赖注入完善 - + 支持配置多级获取 + + 支持PSR-3日志规范 -废除的功能: +### 废除的功能: + 聚合模型 + 内置控制器扩展类 + + 模型自动验证 > ThinkPHP5的运行环境要求PHP5.6以上。 -## 目录结构 - -初始的目录结构如下: - -~~~ -www WEB部署目录(或者子目录) -├─application 应用目录 -│ ├─common 公共模块目录(可以更改) -│ ├─module_name 模块目录 -│ │ ├─common.php 模块函数文件 -│ │ ├─controller 控制器目录 -│ │ ├─model 模型目录 -│ │ ├─view 视图目录 -│ │ └─ ... 更多类库目录 -│ │ -│ ├─command.php 命令行定义文件 -│ ├─common.php 公共函数文件 -│ └─tags.php 应用行为扩展定义文件 -│ -├─config 应用配置目录 -│ ├─module_name 模块配置目录 -│ │ ├─database.php 数据库配置 -│ │ ├─cache 缓存配置 -│ │ └─ ... -│ │ -│ ├─app.php 应用配置 -│ ├─cache.php 缓存配置 -│ ├─cookie.php Cookie配置 -│ ├─database.php 数据库配置 -│ ├─log.php 日志配置 -│ ├─session.php Session配置 -│ ├─template.php 模板引擎配置 -│ └─trace.php Trace配置 -│ -├─route 路由定义目录 -│ ├─route.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 命令行入口文件 -~~~ - -> router.php用于php自带webserver支持,可用于快速测试 -> 切换到public目录后,启动命令:php -S localhost:8888 router.php -> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 - -## 升级指导 - -应用类库的命名空间app如果需要更改,设置app_namespace环境变量 - -取消命名空间的别名功能,原有下面系统类库的命名空间需要调整: - -* think\App => think\facade\App (或者 App ) -* think\Cache => think\facade\Cache (或者 Cache ) -* think\Config => think\facade\Config (或者 Config ) -* think\Cookie => think\facade\Cookie (或者 Cookie ) -* think\Debug => think\facade\Debug (或者 Debug ) -* think\Env => think\facade\Env (或者 Env ) -* think\Hook => think\facade\Hook (或者 Hook ) -* think\Lang => think\facade\Lang (或者 Lang ) -* think\Log => think\facade\Log (或者 Log ) -* think\Request => think\facade\Request (或者 Request ) -* think\Response => think\facade\Reponse (或者 Reponse ) -* think\Route => think\facade\Route (或者 Route ) -* think\Session => think\facade\Session (或者 Session ) -* think\Url => think\facade\Url (或者 Url ) -* think\View => think\facade\View (或者 View ) - -原有的配置文件config.php 拆分为app.php cache.php 等独立配置文件 放入config目录(原来模块的配置目录直接移动到config目录下面)。 -原有的路由定义文件route.php 移动到route目录(支持放置任意文件名的路由定义文件) - -取消Loader::import方法以及import和vendor助手函数 -原来Loader类的controller、model、action和validate方法改为App类的同名方法 -模型的数据集查询始终返回数据集对象而不是数组 -模型的数据表主键如果不是id 需要设置模型的pk属性 -路由的before_behavior和after_behavior参数更改为before和after -路由缓存功能暂时取消 -软删除trait引入更改为 think\model\concern\SoftDelete +## 在线手册 + ++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1) ++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) ## 命名规范 `ThinkPHP5`遵循PSR-2命名规范和PSR-4自动加载规范。 ## 参与开发 + 请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。 ## 版权信息 -- Gitee From a91435a86388a689c15bb012ac5a0a92ceed782c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 21 Jul 2017 18:01:33 +0800 Subject: [PATCH 0426/1384] =?UTF-8?q?Container=E7=B1=BB=E5=A2=9E=E5=8A=A0g?= =?UTF-8?q?et=E5=92=8Cset=E9=9D=99=E6=80=81=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=BF=AB=E9=80=9F=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/library/think/Container.php b/library/think/Container.php index f4c6bb90..3bb48ec7 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -34,6 +34,31 @@ class Container return static::$instance; } + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function get($abstract, $vars = [], $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + public static function set($abstract, $concrete = null) + { + return static::getInstance()->bind($abstract, $concrete); + } + /** * 绑定一个类、闭包、实例、接口实现到容器 * @access public @@ -84,7 +109,7 @@ class Container /** * 创建类的实例 * @access public - * @param string $class 类名或者标识 + * @param string $abstract 类名或者标识 * @param array|true $args 变量 * @param bool $newInstance 是否每次创建新的实例 * @return object -- Gitee From aa10f73ab737b02390faa8f4c50b0dfe9348e163 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 21 Jul 2017 18:31:15 +0800 Subject: [PATCH 0427/1384] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console.php | 2 +- library/think/Console.php | 4 +-- library/think/Controller.php | 2 +- library/think/Cookie.php | 2 +- library/think/Db.php | 2 +- library/think/Error.php | 4 +-- library/think/Hook.php | 4 +-- library/think/Lang.php | 4 +-- library/think/Model.php | 2 +- library/think/Paginator.php | 4 +-- library/think/Request.php | 10 +++---- library/think/Response.php | 14 +++++----- library/think/Session.php | 4 +-- library/think/Template.php | 10 +++---- library/think/Validate.php | 10 +++---- library/think/View.php | 4 +-- library/think/cache/driver/File.php | 4 +-- library/think/console/command/Make.php | 8 +++--- .../console/command/optimize/Autoload.php | 20 ++++++------- .../think/console/command/optimize/Config.php | 4 +-- .../think/console/command/optimize/Schema.php | 18 ++++++------ library/think/db/Connection.php | 28 +++++++++---------- library/think/db/Query.php | 5 ++-- library/think/debug/Console.php | 12 ++++---- library/think/debug/Html.php | 12 ++++---- library/think/exception/Handle.php | 24 ++++++++-------- library/think/log/driver/File.php | 10 +++---- library/think/log/driver/Socket.php | 8 +++--- library/think/response/Jsonp.php | 4 +-- library/think/response/Redirect.php | 10 +++---- library/think/response/View.php | 6 ++-- library/think/route/Dispatch.php | 4 +-- library/think/route/Domain.php | 14 +++++----- library/think/route/Rule.php | 23 ++++++++------- library/think/route/RuleGroup.php | 3 +- library/think/view/driver/Php.php | 11 ++++---- library/think/view/driver/Think.php | 10 +++---- library/traits/controller/Jump.php | 20 ++++++------- start.php | 2 +- tpl/page_trace.tpl | 2 +- 40 files changed, 171 insertions(+), 173 deletions(-) diff --git a/console.php b/console.php index dc615d1e..29e3fbf0 100644 --- a/console.php +++ b/console.php @@ -16,5 +16,5 @@ namespace think; require __DIR__ . '/base.php'; // 执行应用 -Facade::make('app', [defined('APP_PATH') ? APP_PATH : ''])->initialize(); +Container::get('app', [defined('APP_PATH') ? APP_PATH : ''])->initialize(); Console::init(); diff --git a/library/think/Console.php b/library/think/Console.php index 135596a2..13dac946 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -69,8 +69,8 @@ class Console $console = new self('Think Console', '0.1'); // 读取指令集 - if (is_file(Facade::make('env')->get('config_path') . 'command.php')) { - $commands = include Facade::make('env')->get('config_path') . 'command.php'; + if (is_file(Container::get('env')->get('config_path') . 'command.php')) { + $commands = include Container::get('env')->get('config_path') . 'command.php'; if (is_array($commands)) { foreach ($commands as $command) { diff --git a/library/think/Controller.php b/library/think/Controller.php index 7b1a9dba..0355b150 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -52,7 +52,7 @@ class Controller */ public function __construct(Request $request, App $app) { - $this->view = Facade::make('view')->init( + $this->view = Container::get('view')->init( $app['config']->pull('template'), $app['config']->get('view_replace_str') ); diff --git a/library/think/Cookie.php b/library/think/Cookie.php index f70fbd36..16dba573 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -40,7 +40,7 @@ class Cookie public function init(array $config = []) { if (empty($config)) { - $config = Facade::make('config')->pull('cookie'); + $config = Container::get('config')->pull('cookie'); } $this->config = array_merge($this->config, array_change_key_case($config)); diff --git a/library/think/Db.php b/library/think/Db.php index 99703329..6cc1ba61 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -20,7 +20,7 @@ class Db public static function __callStatic($method, $args) { - $class = Facade::make('config')->get('database.query') ?: '\\think\\db\\Query'; + $class = Container::get('config')->get('database.query') ?: '\\think\\db\\Query'; $query = new $class(); diff --git a/library/think/Error.php b/library/think/Error.php index 06c0f99c..1ad0c2bf 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -82,7 +82,7 @@ class Error } // 写入日志 - Facade::make('log')->save(); + Container::get('log')->save(); } /** @@ -107,7 +107,7 @@ class Error if (!$handle) { // 异常处理handle - $class = Facade::make('app')->config('exception_handle'); + $class = Container::get('app')->config('exception_handle'); if ($class && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { $handle = new $class; } else { diff --git a/library/think/Hook.php b/library/think/Hook.php index b8070685..2c6f343d 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -154,7 +154,7 @@ class Hook */ protected function execTag($class, $tag = '', $params = null, $extra = null) { - $app = Facade::make('app'); + $app = Container::get('app'); $app->isDebug() && $app['debug']->remark('behavior_start', 'time'); @@ -166,7 +166,7 @@ class Hook } elseif (strpos($class, '::')) { $call = $class; } else { - $obj = Facade::make($class); + $obj = Container::get($class); if (!is_callable([$obj, $method])) { $method = 'run'; diff --git a/library/think/Lang.php b/library/think/Lang.php index 75155e66..8fb601ee 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -80,7 +80,7 @@ class Lang foreach ($file as $_file) { if (is_file($_file)) { // 记录加载信息 - Facade::make('app')->log('[ LANG ] ' . $_file); + Container::get('app')->log('[ LANG ] ' . $_file); $_lang = include $_file; if (is_array($_lang)) { $lang = array_change_key_case($_lang) + $lang; @@ -168,7 +168,7 @@ class Lang // 自动侦测浏览器语言 preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); $langSet = strtolower($matches[1]); - $acceptLangs = Facade::make('config')->get('header_accept_lang'); + $acceptLangs = Container::get('config')->get('header_accept_lang'); if (isset($acceptLangs[$langSet])) { $langSet = $acceptLangs[$langSet]; } elseif (isset($this->acceptLanguage[$langSet])) { diff --git a/library/think/Model.php b/library/think/Model.php index ee279c5b..f78207d3 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -70,7 +70,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录原始数据 $this->origin = $this->data; - $config = Facade::make('config'); + $config = Container::get('config'); if (empty($this->name)) { // 当前模型名 diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 015aeab7..f8ae5ea5 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -138,7 +138,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J */ public static function getCurrentPage($varPage = 'page', $default = 1) { - $page = Facade::make('request')->request($varPage); + $page = Container::get('request')->request($varPage); if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { return $page; @@ -153,7 +153,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J */ public static function getCurrentPath() { - return Facade::make('request')->baseUrl(); + return Container::get('request')->baseUrl(); } public function total() diff --git a/library/think/Request.php b/library/think/Request.php index f50bad05..91b0b8e1 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -818,7 +818,7 @@ class Request public function session($name = '', $default = null, $filter = '') { if (empty($this->session)) { - $this->session = Facade::make('session')->get(); + $this->session = Container::get('session')->get(); } if (is_array($name)) { @@ -838,7 +838,7 @@ class Request */ public function cookie($name = '', $default = null, $filter = '') { - $cookie = Facade::make('cookie'); + $cookie = Container::get('cookie'); if (empty($this->cookie)) { $this->cookie = $cookie->get(); @@ -959,7 +959,7 @@ class Request public function env($name = '', $default = null, $filter = '') { if (empty($this->env)) { - $this->env = Facade::make('env')->get(); + $this->env = Container::get('env')->get(); } if (is_array($name)) { @@ -1597,7 +1597,7 @@ class Request header($name . ': ' . $token); } - Facade::make('session')->set($name, $token); + Container::get('session')->set($name, $token); return $token; } @@ -1658,7 +1658,7 @@ class Request if (isset($fun)) { $key = $fun($key); } - $cache = Facade::make('cache'); + $cache = Container::get('cache'); if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { // 读取缓存 $response = Response::create()->code(304); diff --git a/library/think/Response.php b/library/think/Response.php index b30fe093..eae3bec5 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -89,23 +89,23 @@ class Response public function send() { // 监听response_send - Facade::make('hook')->listen('response_send', $this); + Container::get('hook')->listen('response_send', $this); // 处理输出数据 $data = $this->getContent(); // Trace调试注入 - if (Facade::make('env')->get('app_trace', Facade::make('app')->config('app.app_trace'))) { - Facade::make('debug')->inject($this, $data); + if (Container::get('env')->get('app_trace', Container::get('app')->config('app.app_trace'))) { + Container::get('debug')->inject($this, $data); } if (200 == $this->code) { - $cache = Facade::make('request')->getCache(); + $cache = Container::get('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'; - Facade::make('cache')->set($cache[0], [$data, $this->header], $cache[1]); + Container::get('cache')->set($cache[0], [$data, $this->header], $cache[1]); } } @@ -126,11 +126,11 @@ class Response } // 监听response_end - Facade::make('hook')->listen('response_end', $this); + Container::get('hook')->listen('response_end', $this); // 清空当次请求有效的数据 if (!($this instanceof RedirectResponse)) { - Facade::make('session')->flush(); + Container::get('session')->flush(); } } diff --git a/library/think/Session.php b/library/think/Session.php index b10c72ff..a52f2c57 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -41,11 +41,11 @@ class Session public function init(array $config = []) { if (empty($config)) { - $config = Facade::make('config')->pull('session'); + $config = Container::get('config')->pull('session'); } // 记录初始化信息 - Facade::make('app')->log('[ SESSION ] INIT ' . var_export($config, true)); + Container::get('app')->log('[ SESSION ] INIT ' . var_export($config, true)); $isDoStart = false; if (isset($config['use_trans_sid'])) { ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); diff --git a/library/think/Template.php b/library/think/Template.php index da9689d5..88310e23 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -63,7 +63,7 @@ class Template */ public function __construct(array $config = []) { - $this->config['cache_path'] = Facade::make('app')->getRuntimePath() . 'temp/'; + $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; $this->config = array_merge($this->config, $config); $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); @@ -178,7 +178,7 @@ class Template $this->config($config); } - $cache = Facade::make('cache'); + $cache = Container::get('cache'); if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 读取渲染缓存 @@ -337,7 +337,7 @@ class Template { if ($cacheId && $this->config['display_cache']) { // 缓存页面输出 - return Facade::make('cache')->has($cacheId); + return Container::get('cache')->has($cacheId); } return false; @@ -1232,10 +1232,10 @@ class Template } if ($this->config['view_base']) { - $module = isset($module) ? $module : Facade::make('request')->module(); + $module = isset($module) ? $module : Container::get('request')->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? Facade::make('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; + $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; } $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); diff --git a/library/think/Validate.php b/library/think/Validate.php index c7ea4443..9f251968 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -882,7 +882,7 @@ class Validate */ public function method($value, $rule) { - $method = Facade::make('request')->method(); + $method = Container::get('request')->method(); return strtoupper($rule) == $method; } @@ -919,7 +919,7 @@ class Validate $db = new $rule[0]; } else { try { - $db = Facade::make('app')->model($rule[0]); + $db = Container::get('app')->model($rule[0]); } catch (ClassNotFoundException $e) { $db = Db::name($rule[0]); } @@ -963,7 +963,7 @@ class Validate */ public function behavior($value, $rule, $data) { - return Facade::make('hook')->exec($rule, $data); + return Container::get('hook')->exec($rule, $data); } /** @@ -1275,7 +1275,7 @@ class Validate public function token($value, $rule, $data) { $rule = !empty($rule) ? $rule : '__token__'; - $session = Facade::make('session'); + $session = Container::get('session'); if (!isset($data[$rule]) || !$session->has($rule)) { // 令牌数据无效 @@ -1347,7 +1347,7 @@ class Validate } if (is_string($msg) && 0 === strpos($msg, '{%')) { - $msg = Facade::make('lang')->get(substr($msg, 2, -1)); + $msg = Container::get('lang')->get(substr($msg, 2, -1)); } if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { diff --git a/library/think/View.php b/library/think/View.php index 354b302f..a3cc25ba 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -35,7 +35,7 @@ class View $this->engine($engine); // 基础替换字符串 - $request = Facade::make('request'); + $request = Container::get('request'); $root = $request->rootUrl(); $baseReplace = [ @@ -155,7 +155,7 @@ class View $content = ob_get_clean(); // 内容过滤标签 - Facade::make('hook')->listen('view_filter', $content); + Container::get('hook')->listen('view_filter', $content); // 允许用户自定义模板的字符串替换 $replace = array_merge($this->replace, $replace); diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index a1449559..d56ca8f9 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -12,7 +12,7 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Facade; +use think\Container; /** * 文件类型缓存类 @@ -39,7 +39,7 @@ class File extends Driver } if (empty($this->options['path'])) { - $this->options['path'] = Facade::make('app')->getRuntimePath() . 'cache/'; + $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache/'; } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { $this->options['path'] .= DIRECTORY_SEPARATOR; } diff --git a/library/think/console/command/Make.php b/library/think/console/command/Make.php index e1b3c466..8d1a71af 100644 --- a/library/think/console/command/Make.php +++ b/library/think/console/command/Make.php @@ -15,7 +15,7 @@ use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; -use think\Facade; +use think\Container; use think\facade\Config; use think\facade\Env; @@ -66,21 +66,21 @@ abstract class Make extends Command return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ $class, $namespace, - Facade::make('app')->getNamespace(), + Container::get('app')->getNamespace(), ], $stub); } protected function getPathName($name) { - $name = str_replace(Facade::make('app')->getNamespace() . '\\', '', $name); + $name = str_replace(Container::get('app')->getNamespace() . '\\', '', $name); return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php'; } protected function getClassName($name) { - $appNamespace = Facade::make('app')->getNamespace(); + $appNamespace = Container::get('app')->getNamespace(); if (strpos($name, $appNamespace . '\\') !== false) { return $name; diff --git a/library/think/console/command/optimize/Autoload.php b/library/think/console/command/optimize/Autoload.php index 5723a5a4..802ac444 100644 --- a/library/think/console/command/optimize/Autoload.php +++ b/library/think/console/command/optimize/Autoload.php @@ -13,7 +13,7 @@ namespace think\console\command\optimize; use think\console\Command; use think\console\Input; use think\console\Output; -use think\Facade; +use think\Container; class Autoload extends Command { @@ -38,10 +38,10 @@ return [ EOF; $namespacesToScan = [ - Facade::make('app')->getNamespace() . '\\' => realpath(rtrim(Facade::make('app')->getAppPath())), - 'think\\' => Facade::make('app')->getAppPath() . 'library/think', - 'traits\\' => Facade::make('app')->getAppPath() . 'library/traits', - '' => realpath(rtrim(Facade::make('app')->getRootPath() . 'extend')), + Container::get('app')->getNamespace() . '\\' => realpath(rtrim(Container::get('app')->getAppPath())), + 'think\\' => Container::get('app')->getAppPath() . 'library/think', + 'traits\\' => Container::get('app')->getAppPath() . 'library/traits', + '' => realpath(rtrim(Container::get('app')->getRootPath() . 'extend')), ]; krsort($namespacesToScan); @@ -61,7 +61,7 @@ EOF; $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; } $classmapFile .= "];\n"; - $runtimePath = Facade::make('app')->getRuntimePath(); + $runtimePath = Container::get('app')->getRuntimePath(); if (!is_dir($runtimePath)) { @mkdir($runtimePath, 0755, true); } @@ -95,13 +95,13 @@ EOF; { $baseDir = ''; - $appPath = $this->normalizePath(realpath(Facade::make('app')->getAppPath())); - $libPath = $this->normalizePath(realpath(Facade::make('app')->getThinkPath() . 'library')); - $extendPath = $this->normalizePath(realpath(Facade::make('app')->getRootPath() . 'extend')); + $appPath = $this->normalizePath(realpath(Container::get('app')->getAppPath())); + $libPath = $this->normalizePath(realpath(Container::get('app')->getThinkPath() . 'library')); + $extendPath = $this->normalizePath(realpath(Container::get('app')->getRootPath() . 'extend')); $path = $this->normalizePath($path); if (strpos($path, $libPath . '/') === 0) { - $path = substr($path, strlen(Facade::make('app')->getThinkPath() . 'library')); + $path = substr($path, strlen(Container::get('app')->getThinkPath() . 'library')); $baseDir = 'LIB_PATH'; } elseif (strpos($path, $appPath . '/') === 0) { $path = substr($path, strlen($appPath) + 1); diff --git a/library/think/console/command/optimize/Config.php b/library/think/console/command/optimize/Config.php index 9b226625..71dd5f80 100644 --- a/library/think/console/command/optimize/Config.php +++ b/library/think/console/command/optimize/Config.php @@ -14,7 +14,7 @@ use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; -use think\Facade; +use think\Container; use think\facade\App; class Config extends Command @@ -54,7 +54,7 @@ class Config extends Command $path = realpath(App::getAppPath() . $module) . DIRECTORY_SEPARATOR; $configPath = App::getConfigPath(); $ext = App::getConfigExt(); - $con = Facade::make('config'); + $con = Container::get('config'); if ($module) { // 加载模块配置 diff --git a/library/think/console/command/optimize/Schema.php b/library/think/console/command/optimize/Schema.php index a2fa3939..be79bfda 100644 --- a/library/think/console/command/optimize/Schema.php +++ b/library/think/console/command/optimize/Schema.php @@ -14,8 +14,8 @@ use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; +use think\Container; use think\Db; -use think\Facade; class Schema extends Command { @@ -33,14 +33,14 @@ class Schema extends Command protected function execute(Input $input, Output $output) { - if (!is_dir(Facade::make('app')->getRuntimePath() . 'schema')) { - @mkdir(Facade::make('app')->getRuntimePath() . 'schema', 0755, true); + if (!is_dir(Container::get('app')->getRuntimePath() . 'schema')) { + @mkdir(Container::get('app')->getRuntimePath() . 'schema', 0755, true); } if ($input->hasOption('module')) { $module = $input->getOption('module'); // 读取模型 - $list = scandir(Facade::make('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'); - $app = Facade::make('app')->getNamespace(); + $list = scandir(Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'); + $app = Container::get('app')->getNamespace(); foreach ($list as $file) { if (0 === strpos($file, '.')) { @@ -63,8 +63,8 @@ class Schema extends Command $dbName = $input->getOption('db'); $tables = Db::getConnection()->getTables($dbName); } elseif (!\think\facade\Config::get('app_multi_module')) { - $app = Facade::make('app')->getNamespace(); - $list = scandir(Facade::make('app')->getAppPath() . 'model'); + $app = Container::get('app')->getNamespace(); + $list = scandir(Container::get('app')->getAppPath() . 'model'); foreach ($list as $file) { if (0 === strpos($file, '.')) { continue; @@ -94,7 +94,7 @@ class Schema extends Command $info = $class::getConnection()->getFields($table); $content .= var_export($info, true) . ';'; - file_put_contents(Facade::make('app')->getRuntimePath() . 'schema/' . $dbName . '.' . $table . '.php', $content); + file_put_contents(Container::get('app')->getRuntimePath() . 'schema/' . $dbName . '.' . $table . '.php', $content); } } @@ -110,7 +110,7 @@ class Schema extends Command $content = 'getFields($db . $table); $content .= var_export($info, true) . ';'; - file_put_contents(Facade::make('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . $table . '.php', $content); + file_put_contents(Container::get('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . $table . '.php', $content); } } } diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 38fb383e..6781ae66 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -13,12 +13,12 @@ namespace think\db; use PDO; use PDOStatement; +use think\Container; use think\Db; use think\db\exception\BindParamException; use think\Debug; use think\Exception; use think\exception\PDOException; -use think\Facade; abstract class Connection { @@ -171,7 +171,7 @@ abstract class Connection $class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']); // 记录初始化信息 - Facade::make('app')->log('[ DB ] INIT ' . $options['type']); + Container::get('app')->log('[ DB ] INIT ' . $options['type']); if (true === $name) { $name = md5(serialize($config)); @@ -348,7 +348,7 @@ abstract class Connection if (!isset(self::$info[$schema])) { // 读取缓存 - $cacheFile = Facade::make('app')->getRuntimePath() . 'schema/' . $schema . '.php'; + $cacheFile = Container::get('app')->getRuntimePath() . 'schema/' . $schema . '.php'; if (is_file($cacheFile)) { $info = include $cacheFile; } else { @@ -693,7 +693,7 @@ abstract class Connection $key = md5(serialize($options) . serialize($query->getBind(false))); } - $result = Facade::make('cache')->get($key); + $result = Container::get('cache')->get($key); } if (false === $result) { @@ -763,7 +763,7 @@ abstract class Connection // 判断查询缓存 $cache = $options['cache']; $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options) . serialize($query->getBind(false))); - $resultSet = Facade::make('cache')->get($key); + $resultSet = Container::get('cache')->get($key); } if (false === $resultSet) { @@ -970,7 +970,7 @@ abstract class Connection return $this->getRealSql($sql, $bind); } else { // 检测缓存 - $cache = Facade::make('cache'); + $cache = Container::get('cache'); if (isset($key) && $cache->get($key)) { // 删除缓存 @@ -1045,7 +1045,7 @@ abstract class Connection } // 检测缓存 - $cache = Facade::make('cache'); + $cache = Container::get('cache'); if (isset($key) && $cache->get($key)) { // 删除缓存 @@ -1090,7 +1090,7 @@ abstract class Connection $cache = $options['cache']; $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false))); - $result = Facade::make('cache')->get($key); + $result = Container::get('cache')->get($key); } if (false === $result) { @@ -1152,7 +1152,7 @@ abstract class Connection $cache = $options['cache']; $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false))); - $result = Facade::make('cache')->get($guid); + $result = Container::get('cache')->get($guid); } if (false === $result) { @@ -1705,7 +1705,7 @@ abstract class Connection { if (!empty($this->config['debug'])) { // 开启数据库调试模式 - $debug = Facade::make('debug'); + $debug = Container::get('debug'); if ($start) { $debug->remark('queryStartTime', 'time'); @@ -1766,7 +1766,7 @@ abstract class Connection public function log($log, $type = 'sql') { - $this->config['debug'] && Facade::make('log')->record($log, $type); + $this->config['debug'] && Container::get('log')->record($log, $type); } /** @@ -1874,7 +1874,7 @@ abstract class Connection */ protected function cacheData($key, $data, $config = []) { - $cache = Facade::make('cache'); + $cache = Container::get('cache'); if (isset($config['tag'])) { $cache->tag($config['tag'])->set($key, $data, $config['expire']); @@ -1914,10 +1914,10 @@ abstract class Connection private static function parseConfig($config) { if (empty($config)) { - $config = Facade::make('config')->pull('database'); + $config = Container::get('config')->pull('database'); } elseif (is_string($config) && false === strpos($config, '/')) { // 支持读取配置参数 - $config = Facade::make('config')->get('database.' . $config); + $config = Container::get('config')->get('database.' . $config); } if (is_string($config)) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 68f5505c..0e29bafe 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -20,7 +20,6 @@ use think\db\exception\ModelNotFoundException; use think\Exception; use think\exception\DbException; use think\exception\PDOException; -use think\Facade; use think\Loader; use think\Model; use think\model\Relation; @@ -603,7 +602,7 @@ class Query */ protected function lazyWrite($type, $guid, $step, $lazyTime) { - $cache = Facade::make('cache'); + $cache = Container::get('cache'); if (!$cache->has($guid . '_time')) { // 计时开始 @@ -1371,7 +1370,7 @@ class Query $simple = false; } - $paginate = Facade::make('config')->pull('paginate'); + $paginate = Container::get('config')->pull('paginate'); if (is_array($listRows)) { $config = array_merge($paginate, $listRows); diff --git a/library/think/debug/Console.php b/library/think/debug/Console.php index 1a563661..7d55c0a0 100644 --- a/library/think/debug/Console.php +++ b/library/think/debug/Console.php @@ -11,8 +11,8 @@ namespace think\debug; +use think\Container; use think\Db; -use think\Facade; use think\Response; /** @@ -41,7 +41,7 @@ class Console */ public function output(Response $response, array $log = []) { - $request = Facade::make('request'); + $request = Container::get('request'); $contentType = $response->getHeader('Content-Type'); $accept = $request->header('accept'); if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { @@ -50,9 +50,9 @@ class Console return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - Facade::make('app')->getBeginTime(), 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() - Facade::make('app')->getBeginMem()) / 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']; @@ -65,14 +65,14 @@ 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 ', - '缓存信息' => Facade::make('cache')->getReadTimes() . ' reads,' . Facade::make('cache')->getWriteTimes() . ' writes', + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', ]; if (session_id()) { $base['会话信息'] = 'SESSION_ID=' . session_id(); } - $info = Facade::make('debug')->getFile(true); + $info = Container::get('debug')->getFile(true); // 页面Trace信息 $trace = []; diff --git a/library/think/debug/Html.php b/library/think/debug/Html.php index 31420e5d..d5ca50f9 100644 --- a/library/think/debug/Html.php +++ b/library/think/debug/Html.php @@ -11,8 +11,8 @@ namespace think\debug; +use think\Container; use think\Db; -use think\Facade; use think\Response; /** @@ -40,7 +40,7 @@ class Html */ public function output(Response $response, array $log = []) { - $request = Facade::make('request'); + $request = Container::get('request'); $contentType = $response->getHeader('Content-Type'); $accept = $request->header('accept'); if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { @@ -49,9 +49,9 @@ class Html return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - Facade::make('app')->getBeginTime(), 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() - Facade::make('app')->getBeginMem()) / 1024, 2); + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); // 页面Trace信息 if (isset($_SERVER['HTTP_HOST'])) { @@ -63,14 +63,14 @@ 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 ', - '缓存信息' => Facade::make('cache')->getReadTimes() . ' reads,' . Facade::make('cache')->getWriteTimes() . ' writes', + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', ]; if (session_id()) { $base['会话信息'] = 'SESSION_ID=' . session_id(); } - $info = Facade::make('debug')->getFile(true); + $info = Container::get('debug')->getFile(true); // 页面Trace信息 $trace = []; diff --git a/library/think/exception/Handle.php b/library/think/exception/Handle.php index 3485c12c..366074bc 100644 --- a/library/think/exception/Handle.php +++ b/library/think/exception/Handle.php @@ -13,7 +13,7 @@ namespace think\exception; use Exception; use think\console\Output; -use think\Facade; +use think\Container; use think\Response; class Handle @@ -33,7 +33,7 @@ class Handle { if (!$this->isIgnoreReport($exception)) { // 收集异常数据 - if (Facade::make('app')->isDebug()) { + if (Container::get('app')->isDebug()) { $data = [ 'file' => $exception->getFile(), 'line' => $exception->getLine(), @@ -49,11 +49,11 @@ class Handle $log = "[{$data['code']}]{$data['message']}"; } - if (Facade::make('app')->config('log.record_trace')) { + if (Container::get('app')->config('log.record_trace')) { $log .= "\r\n" . $exception->getTraceAsString(); } - Facade::make('log')->record($log, 'error'); + Container::get('log')->record($log, 'error'); } } @@ -88,7 +88,7 @@ class Handle */ public function renderForConsole(Output $output, Exception $e) { - if (Facade::make('app')->isDebug()) { + if (Container::get('app')->isDebug()) { $output->setVerbosity(Output::VERBOSITY_DEBUG); } $output->renderException($e); @@ -101,8 +101,8 @@ class Handle protected function renderHttpException(HttpException $e) { $status = $e->getStatusCode(); - $template = Facade::make('app')->config('http_exception_template'); - if (!Facade::make('app')->isDebug() && !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); @@ -116,7 +116,7 @@ class Handle protected function convertExceptionToResponse(Exception $exception) { // 收集异常数据 - if (Facade::make('app')->isDebug()) { + if (Container::get('app')->isDebug()) { // 调试模式,获取详细的错误信息 $data = [ 'name' => get_class($exception), @@ -145,9 +145,9 @@ class Handle 'message' => $this->getMessage($exception), ]; - if (!Facade::make('app')->config('show_error_msg')) { + if (!Container::get('app')->config('show_error_msg')) { // 不显示详细错误信息 - $data['message'] = Facade::make('app')->config('error_message'); + $data['message'] = Container::get('app')->config('error_message'); } } @@ -160,7 +160,7 @@ class Handle ob_start(); extract($data); - include Facade::make('app')->config('exception_tmpl'); + include Container::get('app')->config('exception_tmpl'); // 获取并清空缓存 $content = ob_get_clean(); $response = Response::create($content, 'html'); @@ -204,7 +204,7 @@ class Handle if (PHP_SAPI == 'cli') { return $message; } - $lang = Facade::make('lang'); + $lang = Container::get('lang'); if (strpos($message, ':')) { $name = strstr($message, ':', true); $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 1eb26046..86a894c1 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -11,7 +11,7 @@ namespace think\log\driver; -use think\Facade; +use think\Container; /** * 本地化调试输出到文件 @@ -35,7 +35,7 @@ class File } if (empty($this->config['path'])) { - $this->config['path'] = Facade::make('app')->getRuntimePath() . 'log/'; + $this->config['path'] = Container::get('app')->getRuntimePath() . 'log/'; } } @@ -97,13 +97,13 @@ class File } if (empty($this->writed[$destination]) && PHP_SAPI != 'cli') { - if (Facade::make('app')->isDebug() && !$apart) { + if (Container::get('app')->isDebug() && !$apart) { // 获取基本信息 $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - $runtime = round(microtime(true) - Facade::make('app')->getBeginTime(), 10); + $runtime = round(microtime(true) - Container::get('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() - Facade::make('app')->getBeginMem()) / 1024, 2); + $memory_use = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; $file_load = ' [文件加载:' . count(get_included_files()) . ']'; $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index 923ebcab..18daa60f 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -11,7 +11,7 @@ namespace think\log\driver; -use think\Facade; +use think\Container; /** * github: https://github.com/luofei614/SocketLog @@ -68,11 +68,11 @@ class Socket $trace = []; - if (Facade::make('app')->isDebug()) { - $runtime = round(microtime(true) - Facade::make('app')->getBeginTime(), 10); + if (Container::get('app')->isDebug()) { + $runtime = round(microtime(true) - Container::get('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() - Facade::make('app')->getBeginMem()) / 1024, 2); + $memory_use = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; $file_load = ' [文件加载:' . count(get_included_files()) . ']'; diff --git a/library/think/response/Jsonp.php b/library/think/response/Jsonp.php index a0fd419e..99bfdbc2 100644 --- a/library/think/response/Jsonp.php +++ b/library/think/response/Jsonp.php @@ -11,7 +11,7 @@ namespace think\response; -use think\Facade; +use think\Container; use think\Response; class Jsonp extends Response @@ -36,7 +36,7 @@ class Jsonp extends Response { try { // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Facade::make('request')->param($this->options['var_jsonp_handler'], ""); + $var_jsonp_handler = Container::get('request')->param($this->options['var_jsonp_handler'], ""); $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; $data = json_encode($data, $this->options['json_encode_param']); diff --git a/library/think/response/Redirect.php b/library/think/response/Redirect.php index 7b069ac5..0e1e20a6 100644 --- a/library/think/response/Redirect.php +++ b/library/think/response/Redirect.php @@ -11,7 +11,7 @@ namespace think\response; -use think\Facade; +use think\Container; use think\Response; class Redirect extends Response @@ -51,7 +51,7 @@ class Redirect extends Response */ public function with($name, $value = null) { - $session = Facade::make('Session'); + $session = Container::get('session'); if (is_array($name)) { foreach ($name as $key => $val) { @@ -73,7 +73,7 @@ class Redirect extends Response if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { return $this->data; } else { - return Facade::make('url')->build($this->data, $this->params); + return Container::get('url')->build($this->data, $this->params); } } @@ -90,7 +90,7 @@ class Redirect extends Response */ public function remember() { - Facade::make('Session')->set('redirect_url', Facade::make('request')->url()); + Container::get('session')->set('redirect_url', Container::get('request')->url()); return $this; } @@ -101,7 +101,7 @@ class Redirect extends Response */ public function restore() { - $session = Facade::make('Session'); + $session = Container::get('session'); if ($session->has('redirect_url')) { $this->data = $session->get('redirect_url'); diff --git a/library/think/response/View.php b/library/think/response/View.php index 6f72a9e2..6369fc45 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -11,7 +11,7 @@ namespace think\response; -use think\Facade; +use think\Container; use think\Response; class View extends Response @@ -31,8 +31,8 @@ class View extends Response protected function output($data) { // 渲染模板输出 - return Facade::make('view') - ->init(Facade::make('app')->config('template'), Facade::make('app')->config('view_replace_str')) + return Container::get('view') + ->init(Container::get('app')->config('template'), Container::get('app')->config('view_replace_str')) ->fetch($data, $this->vars, $this->replace); } diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 364b4c3e..b1abc1ad 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -11,7 +11,7 @@ namespace think\route; -use think\Facade; +use think\Container; abstract class Dispatch { @@ -28,7 +28,7 @@ abstract class Dispatch public function __construct($dispatch, $param = [], $code = null) { - $this->app = Facade::make('app'); + $this->app = Container::get('app'); $this->dispatch = $dispatch; $this->param = $param; $this->code = $code; diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index b7bb0d83..c7abfae4 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -11,7 +11,7 @@ namespace think\route; -use think\Facade; +use think\Container; use think\Loader; use think\Route; use think\route\dispatch\Callback as CallbackDispatch; @@ -130,7 +130,7 @@ class Domain extends RuleGroup if (!empty($bind)) { // 记录绑定信息 - Facade::make('app')->log('[ BIND ] ' . var_export($bind, true)); + Container::get('app')->log('[ BIND ] ' . var_export($bind, true)); // 如果有URL绑定 则进行绑定检测 if (0 === strpos($bind, '\\')) { @@ -160,7 +160,7 @@ class Domain extends RuleGroup { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Facade::make('config')->get('default_action'); + $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); if (!empty($array[1])) { $this->parseUrlParams($array[1]); @@ -181,8 +181,8 @@ class Domain extends RuleGroup { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 3); - $class = !empty($array[0]) ? $array[0] : Facade::make('config')->get('default_controller'); - $method = !empty($array[1]) ? $array[1] : Facade::make('config')->get('default_action'); + $class = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Container::get('config')->get('default_action'); if (!empty($array[2])) { $this->parseUrlParams($array[2]); @@ -203,7 +203,7 @@ class Domain extends RuleGroup { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Facade::make('config')->get('default_action'); + $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); if (!empty($array[1])) { $this->parseUrlParams($array[1]); @@ -224,7 +224,7 @@ class Domain extends RuleGroup { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Facade::make('config')->get('default_action'); + $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); if (!empty($array[1])) { $this->parseUrlParams($array[1]); diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 562fe4a2..b15db2d6 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -13,7 +13,6 @@ namespace think\route; use think\Container; use think\exception\ValidateException; -use think\Facade; use think\Request; use think\Response; use think\route\dispatch\Callback as CallbackDispatch; @@ -395,7 +394,7 @@ abstract class Rule } if ($match) { - $query = strpos($model, '\\') ? $model::where($where) : Facade::make('app')->model($model)->where($where); + $query = strpos($model, '\\') ? $model::where($where) : Container::get('app')->model($model)->where($where); $result = $query->failException($exception)->find(); } } @@ -471,7 +470,7 @@ abstract class Rule // 指定Response响应数据 if (!empty($option['response'])) { - Facade::make('hook')->add('response_send', $option['response']); + Container::get('hook')->add('response_send', $option['response']); } // 开启请求缓存 @@ -517,11 +516,11 @@ abstract class Rule if (is_array($validate)) { // 指定验证规则 - $v = Facade::make('app')->validate(); + $v = Container::get('app')->validate(); $v->rule($validate); } else { // 调用验证器 - $v = Facade::make('app')->validate($validate); + $v = Container::get('app')->validate($validate); if (!empty($scene)) { $v->scene($scene); } @@ -549,7 +548,7 @@ abstract class Rule */ protected function checkAfter($after) { - $hook = Facade::make('hook'); + $hook = Container::get('hook'); foreach ((array) $after as $behavior) { $result = $hook->exec($behavior); @@ -598,7 +597,7 @@ abstract class Rule $result = new ControllerDispatch(implode('/', $route), $var); $request->action(array_pop($route)); - $app = Facade::make('app'); + $app = Container::get('app'); $request->controller($route ? array_pop($route) : $app->config('default_controller')); $request->module($route ? array_pop($route) : $app->config('default_module')); $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : '')); @@ -619,8 +618,8 @@ abstract class Rule protected function parseModule($url) { list($path, $var) = $this->parseUrlPath($url); - $config = Facade::make('config'); - $request = Facade::make('request'); + $config = Container::get('config'); + $request = Container::get('request'); $action = array_pop($path); $controller = !empty($path) ? array_pop($path) : null; $module = $config->get('app_multi_module') && !empty($path) ? array_pop($path) : null; @@ -651,7 +650,7 @@ abstract class Rule if (!empty($option['before'])) { // 路由前置检查 $before = $option['before']; - $hook = Facade::make('hook'); + $hook = Container::get('hook'); foreach ((array) $before as $behavior) { $result = $hook->exec($behavior); @@ -709,7 +708,7 @@ abstract class Rule protected function parseUrlParams($url, &$var = []) { if ($url) { - if (Facade::make('config')->get('url_param_type')) { + if (Container::get('config')->get('url_param_type')) { $var += explode('|', $url); } else { preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { @@ -719,7 +718,7 @@ abstract class Rule } // 设置当前请求的参数 - Facade::make('request')->route($var); + Container::get('request')->route($var); } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 13cedebd..51593ea0 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -11,6 +11,7 @@ namespace think\route; +use think\Container; use think\Request; use think\Route; use think\route\dispatch\Url as UrlDispatch; @@ -73,7 +74,7 @@ class RuleGroup extends Rule // 指定Response响应数据 if (!empty($this->option['response'])) { - Facade::make('hook')->add('response_send', $this->option['response']); + Container::get('hook')->add('response_send', $this->option['response']); } // 开启请求缓存 diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 25e1aa14..260a5784 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -11,9 +11,8 @@ namespace think\view\driver; -use think\App; +use think\Container; use think\exception\TemplateNotFoundException; -use think\Facade; use think\Loader; class Php @@ -71,7 +70,7 @@ class Php } // 记录视图信息 - Facade::make('app') + Container::get('app') ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); if (isset($data['template'])) { @@ -112,10 +111,10 @@ class Php private function parseTemplate($template) { if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::getModulePath() . 'view' . DIRECTORY_SEPARATOR; + $this->config['view_path'] = Container::get('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } - $request = Facade::make('request'); + $request = Container::get('request'); // 获取视图根目录 if (strpos($template, '@')) { @@ -128,7 +127,7 @@ class Php $module = isset($module) ? $module : $request->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? Facade::make('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 5883bac7..66552270 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -11,8 +11,8 @@ namespace think\view\driver; +use think\Container; use think\exception\TemplateNotFoundException; -use think\Facade; use think\Loader; use think\Template; @@ -38,7 +38,7 @@ class Think { $this->config = array_merge($this->config, (array) $config); if (empty($this->config['view_path'])) { - $this->config['view_path'] = Facade::make('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + $this->config['view_path'] = Container::get('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } $this->template = new Template($this->config); @@ -81,7 +81,7 @@ class Think } // 记录视图信息 - Facade::make('app') + Container::get('app') ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); $this->template->fetch($template, $data, $config); @@ -109,7 +109,7 @@ class Think private function parseTemplate($template) { // 分析模板文件规则 - $request = Facade::make('request'); + $request = Container::get('request'); // 获取视图根目录 if (strpos($template, '@')) { @@ -122,7 +122,7 @@ class Think $module = isset($module) ? $module : $request->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? Facade::make('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; diff --git a/library/traits/controller/Jump.php b/library/traits/controller/Jump.php index bfdf526d..25461519 100644 --- a/library/traits/controller/Jump.php +++ b/library/traits/controller/Jump.php @@ -13,8 +13,8 @@ */ namespace traits\controller; +use think\Container; use think\exception\HttpResponseException; -use think\Facade; use think\Response; use think\response\Redirect; @@ -35,7 +35,7 @@ trait Jump if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { $url = $_SERVER["HTTP_REFERER"]; } elseif ('' !== $url) { - $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Facade::make('url')->build($url); + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); } $result = [ @@ -49,8 +49,8 @@ trait Jump $type = $this->getResponseType(); if ('html' == strtolower($type)) { - $config = Facade::make('config'); - $result = Facade::make('view') + $config = Container::get('config'); + $result = Container::get('view') ->init($config->get('template'), $config->get('view_replace_str')) ->fetch($config->get('dispatch_success_tmpl'), $result); } @@ -73,9 +73,9 @@ trait Jump protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) { if (is_null($url)) { - $url = Facade::make('request')->isAjax() ? '' : 'javascript:history.back(-1);'; + $url = Container::get('request')->isAjax() ? '' : 'javascript:history.back(-1);'; } elseif ('' !== $url) { - $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Facade::make('url')->build($url); + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); } $result = [ @@ -89,8 +89,8 @@ trait Jump $type = $this->getResponseType(); if ('html' == strtolower($type)) { - $config = Facade::make('config'); - $result = Facade::make('view') + $config = Container::get('config'); + $result = Container::get('view') ->init($config->get('template'), $config->get('view_replace_str')) ->fetch($config->get('dispatch_error_tmpl'), $result); } @@ -155,8 +155,8 @@ trait Jump */ protected function getResponseType() { - $isAjax = Facade::make('request')->isAjax(); - $config = Facade::make('config'); + $isAjax = Container::get('request')->isAjax(); + $config = Container::get('config'); return $isAjax ? $config->get('default_ajax_return') diff --git a/start.php b/start.php index 8dca43ea..75370b09 100644 --- a/start.php +++ b/start.php @@ -17,6 +17,6 @@ require __DIR__ . '/base.php'; // 支持事先使用静态方法设置Request对象和Config对象 // 执行应用并响应 -Facade::make('app', [defined('APP_PATH') ? APP_PATH : '']) +Container::get('app', [defined('APP_PATH') ? APP_PATH : '']) ->run() ->send(); diff --git a/tpl/page_trace.tpl b/tpl/page_trace.tpl index 983661f4..2e5afbab 100644 --- a/tpl/page_trace.tpl +++ b/tpl/page_trace.tpl @@ -24,7 +24,7 @@
-
getUseTime().'s ';?>
+
getUseTime().'s ';?>
-- Gitee From 2d87111b9b082b731b5285128bc340b7be67c9bc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 22 Jul 2017 18:06:34 +0800 Subject: [PATCH 0428/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=E7=9A=84=E6=AD=A3=E5=88=99=E5=92=8CFilter=5Fvar?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=20=E4=BE=BF=E4=BA=8E=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 87 ++++++++++++-------------------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 9f251968..40f997a5 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -90,7 +90,28 @@ class Validate protected $currentScene = null; // 正则表达式 regex = ['zip'=>'\d{6}',...] - protected $regex = []; + protected $regex = [ + 'alpha' => '/^[A-Za-z]+$/', + 'alphaNum' => '/^[A-Za-z0-9]+$/', + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3|4|5|7|8][0-9]\d{8}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', + 'zip' => '/\d{6}/', + ]; + + // Filter_var 验证规则 + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; // 验证场景 scene = ['edit'=>'name1,name2,...'] protected $scene = []; @@ -616,73 +637,18 @@ class Validate // 是否是一个有效日期 $result = false !== strtotime($value); break; - case 'alpha': - // 只允许字母 - $result = $this->regex($value, '/^[A-Za-z]+$/'); - break; - case 'alphaNum': - // 只允许字母和数字 - $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); - break; - case 'alphaDash': - // 只允许字母、数字和下划线 破折号 - $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); - break; - case 'chs': - // 只允许汉字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); - break; - case 'chsAlpha': - // 只允许汉字、字母 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); - break; - case 'chsAlphaNum': - // 只允许汉字、字母和数字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); - break; - case 'chsDash': - // 只允许汉字、字母、数字和下划线_及破折号- - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); - break; - case 'mobile': - // 国内手机号码 - $result = $this->regex($value, '/^1[3|4|5|7|8][0-9]\d{8}$/'); - break; - case 'idCard': - $result = $this->regex($value, '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/'); - break; case 'activeUrl': // 是否为有效的网址 $result = checkdnsrr($value); break; - case 'ip': - // 是否为IP地址 - $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); - break; - case 'url': - // 是否为一个URL地址 - $result = $this->filter($value, FILTER_VALIDATE_URL); - break; - case 'float': - // 是否为float - $result = $this->filter($value, FILTER_VALIDATE_FLOAT); - break; - case 'number': - $result = is_numeric($value); - break; - case 'integer': - // 是否为整型 - $result = $this->filter($value, FILTER_VALIDATE_INT); - break; - case 'email': - // 是否为邮箱地址 - $result = $this->filter($value, FILTER_VALIDATE_EMAIL); - break; case 'boolean': case 'bool': // 是否为布尔值 $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; + case 'number': + $result = is_numeric($value); + break; case 'array': // 是否为数组 $result = is_array($value); @@ -700,6 +666,9 @@ class Validate if (isset(self::$type[$rule])) { // 注册的验证规则 $result = call_user_func_array(self::$type[$rule], [$value]); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); } else { // 正则验证 $result = $this->regex($value, $rule); -- Gitee From c371a6e377971c98eba03e762a040228a053c812 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 24 Jul 2017 08:53:52 +0800 Subject: [PATCH 0429/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=9A=84=E8=A7=A3=E6=9E=90=E4=B8=BA=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E6=97=B6=E5=80=99=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 34 ++++++++----------------------- library/think/route/Resource.php | 18 ++++++++-------- library/think/route/RuleGroup.php | 22 +++++++++++++++++++- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 1037b8d1..46c4459f 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -40,8 +40,8 @@ class Route 'patch' => 'patch', ]; - // 当前应用实例 - protected $app; + // 当前配置实例 + protected $config; // 当前请求对象 protected $request; // 当前域名 @@ -61,11 +61,11 @@ class Route // 别名路由 protected $alias = []; - public function __construct(App $app, Request $request) + public function __construct(Request $request, Config $config) { - $this->app = $app; + $this->config = $config; $this->request = $request; - $this->host = $this->app['request']->host(); + $this->host = $this->request->host(); $this->setDefaultDomain(); } @@ -455,27 +455,11 @@ class Route $name = isset($option['name']) ? $option['name'] : ''; } - // 上级分组 - $parentGroup = $this->group; - // 创建分组实例 - $group = new RuleGroup($this, $this->group, $name, $option, $pattern); + $group = new RuleGroup($this, $this->group, $name, $route, $option, $pattern); // 注册子分组 - $parentGroup->addRule($group); - - // 设置当前分组 - $this->group = $group; - - // 注册分组路由 - if ($route instanceof \Closure) { - Container::getInstance()->invokeFunction($route); - } else { - $this->rules($route); - } - - // 还原当前分组 - $this->group = $parentGroup; + $this->group->addRule($group); if (!empty($option['cross_domain'])) { $this->setCrossDomainRule($group); @@ -760,7 +744,7 @@ class Route throw new RouteNotFoundException(); } else { // 默认路由解析 - return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->app->config('app.controller_auto_search')]); + return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]); } } @@ -772,7 +756,7 @@ class Route */ protected function checkDomain($host) { - $rootDomain = $this->app['config']->get('url_domain_root'); + $rootDomain = $this->config->get('app.url_domain_root'); $domains = $this->domains; if ($rootDomain) { diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index ade9500d..dd74a171 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -16,7 +16,7 @@ use think\Route; class Resource extends RuleGroup { // 资源路由名称 - protected $rule; + protected $resource; // 资源路由地址 protected $route; // REST路由方法定义 @@ -27,19 +27,19 @@ class Resource extends RuleGroup * @access public * @param Route $router 路由对象 * @param RuleGroup $group 路由所属分组对象 - * @param string $rule 资源名称 + * @param string $name 资源名称 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @param array $rest 资源定义 */ - public function __construct(Route $router, RuleGroup $group = null, $rule = '', $route = '', $option = [], $pattern = [], $rest = []) + public function __construct(Route $router, RuleGroup $group = null, $name = '', $route = '', $option = [], $pattern = [], $rest = []) { - $this->router = $router; - $this->parent = $group; - $this->rule = $rule; - $this->route = $route; - $this->name = strpos($rule, '.') ? strstr($rule, '.', true) : $rule; + $this->router = $router; + $this->parent = $group; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($rule, '.') ? strstr($rule, '.', true) : $rule; // 资源路由默认为完整匹配 $option['complete_match'] = true; @@ -61,7 +61,7 @@ class Resource extends RuleGroup public function check($request, $url, $depr = '/', $completeMatch = false) { // 生成资源路由的路由规则 - $this->buildResourceRule($this->rule, $this->option); + $this->buildResourceRule($this->resource, $this->option); return parent::check($request, $url, $depr, $completeMatch); } diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 51593ea0..96031346 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -30,6 +30,8 @@ class RuleGroup extends Rule 'options' => [], ]; + protected $rule; + // MISS路由 protected $miss; @@ -42,13 +44,15 @@ class RuleGroup extends Rule * @param Route $router 路由对象 * @param RuleGroup $group 路由所属分组对象 * @param string $name 分组名称 + * @param mixed $rule 分组路由 * @param array $option 路由参数 * @param array $pattern 变量规则 */ - public function __construct(Route $router, RuleGroup $group = null, $name = '', $option = [], $pattern = []) + public function __construct(Route $router, RuleGroup $group = null, $name = '', $rule = [], $option = [], $pattern = []) { $this->router = $router; $this->parent = $group; + $this->rule = $rule; $this->name = trim($name, '/'); $this->option = $option; $this->pattern = $pattern; @@ -65,6 +69,22 @@ class RuleGroup extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + if ($this->rule) { + // 解析分组路由 + $group = $this->router->getGroup(); + + $this->router->setGroup($this); + + if ($this->rule instanceof \Closure) { + Container::getInstance()->invokeFunction($this->rule); + } else { + $this->router->rules($this->rule); + } + + $this->router->setGroup($group); + $this->rule = null; + } + // 检查参数有效性 if (!$this->checkOption($this->option, $request)) { return false; -- Gitee From 10a022f0067fbc99c56b065fa7f37a6f0f7d86f9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 24 Jul 2017 16:03:58 +0800 Subject: [PATCH 0430/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0url=5Froute=5Fparse?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0=20=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E8=B7=AF=E7=94=B1=E8=A7=A3=E6=9E=90=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=200=20=E5=AE=9E=E6=97=B6=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=201=20=E5=BB=B6=E8=BF=9F=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/Route.php | 30 +++++++++++++++++++++++++++++- library/think/route/RuleGroup.php | 10 +++++----- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/convention.php b/convention.php index 8718afcf..c8d8e161 100644 --- a/convention.php +++ b/convention.php @@ -83,6 +83,8 @@ return [ 'url_common_param' => false, // URL参数方式 0 按名称成对解析 1 按顺序解析 'url_param_type' => 0, + // 路由解析方式 0 正常解析 1 延迟解析 + 'url_route_parse' => 0, // 是否强制使用路由 'url_route_must' => false, // 域名根,如thinkphp.cn diff --git a/library/think/Route.php b/library/think/Route.php index 46c4459f..9557a76c 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -270,6 +270,17 @@ class Route return isset($this->name[$name]) ? $this->name[$name] : null; } + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return void + */ + public function setName($name) + { + $this->name = $name; + } + /** * 导入配置文件的路由规则 * @access public @@ -456,7 +467,23 @@ class Route } // 创建分组实例 - $group = new RuleGroup($this, $this->group, $name, $route, $option, $pattern); + $rule = 1 == $this->config->get('url_route_parse') ? $route : null; + $group = new RuleGroup($this, $this->group, $name, $rule, $option, $pattern); + + if (is_null($rule)) { + // 解析分组路由 + $parent = $this->getGroup(); + + $this->group = $group; + + if ($route instanceof \Closure) { + Container::getInstance()->invokeFunction($route); + } else { + $this->rules($route); + } + + $this->group = $parent; + } // 注册子分组 $this->group->addRule($group); @@ -849,4 +876,5 @@ class Route return $var; } + } diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 96031346..80a2dd60 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -69,6 +69,11 @@ class RuleGroup extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + if ($this->rule) { // 解析分组路由 $group = $this->router->getGroup(); @@ -85,11 +90,6 @@ class RuleGroup extends Rule $this->rule = null; } - // 检查参数有效性 - if (!$this->checkOption($this->option, $request)) { - return false; - } - // 分组匹配后执行的行为 // 指定Response响应数据 -- Gitee From eb108693ba9b34038dcce4b12da0ae0060dd561b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 25 Jul 2017 17:50:08 +0800 Subject: [PATCH 0431/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=89=E5=85=83?= =?UTF-8?q?=E8=BF=90=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 88310e23..112eb9cd 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -859,10 +859,11 @@ class Template } } else { if (isset($array[1])) { + $express = true; $this->parseVar($array[2]); - $_name = ' && ' . $name . $array[1] . $array[2]; + $express = $name . $array[1] . $array[2]; } else { - $_name = ''; + $express = false; } if (in_array($first, ['?', '=', ':'])) { @@ -876,15 +877,15 @@ class Template switch ($first) { case '?': // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; case '=': // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; + $str = ''; break; case ':': // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; default: if (strpos($str, ':')) { @@ -895,11 +896,8 @@ class Template $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; $str = implode(' : ', $array); - $str = ''; - - } else { - $str = ''; } + $str = ''; } } } else { -- Gitee From 14e5837134f488d5ada8f5d822f8f0afe2668962 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 25 Jul 2017 18:56:52 +0800 Subject: [PATCH 0432/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 112eb9cd..db6b8f23 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -877,15 +877,15 @@ class Template switch ($first) { case '?': // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; case '=': // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; + $str = ''; break; case ':': // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; break; default: if (strpos($str, ':')) { @@ -897,7 +897,7 @@ class Template $str = implode(' : ', $array); } - $str = ''; + $str = ''; } } } else { -- Gitee From 81bf96537fbd01f20dedaa5d960044c165ad6752 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 26 Jul 2017 08:19:40 +0800 Subject: [PATCH 0433/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=92=8C=E8=B7=AF=E7=94=B1=E5=88=86=E7=BB=84=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=88=B0=E4=B8=80=E4=B8=AAResponse=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 2 ++ library/think/route/RuleGroup.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index b15db2d6..36746f8c 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -581,6 +581,8 @@ abstract class Rule if ($route instanceof \Closure) { // 执行闭包 $result = new CallbackDispatch($route); + } elseif ($route instanceof Response) { + $result = new ResponseDispatch($route); } elseif (0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 80a2dd60..64d62949 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -13,7 +13,9 @@ namespace think\route; use think\Container; use think\Request; +use think\Response; use think\Route; +use think\route\dispatch\Response as ResponseDispatch; use think\route\dispatch\Url as UrlDispatch; class RuleGroup extends Rule @@ -75,6 +77,10 @@ class RuleGroup extends Rule } if ($this->rule) { + if ($this->rule instanceof Response) { + return new ResponseDispatch($this->rule); + } + // 解析分组路由 $group = $this->router->getGroup(); -- Gitee From 9e924edcd704638ab95a50e73ab3cf09c45cc616 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 26 Jul 2017 11:02:44 +0800 Subject: [PATCH 0434/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 91b0b8e1..01505feb 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -687,7 +687,7 @@ class Request /** * 设置获取GET参数 * @access public - * @param string|array $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -709,7 +709,7 @@ class Request /** * 设置获取POST参数 * @access public - * @param string $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -736,7 +736,7 @@ class Request /** * 设置获取PUT参数 * @access public - * @param string|array $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -763,7 +763,7 @@ class Request /** * 设置获取DELETE参数 * @access public - * @param string|array $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -776,7 +776,7 @@ class Request /** * 设置获取PATCH参数 * @access public - * @param string|array $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -788,7 +788,7 @@ class Request /** * 获取request变量 - * @param string $name 数据名称 + * @param mixed $name 数据名称 * @param string $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -810,7 +810,7 @@ class Request /** * 获取session数据 * @access public - * @param string|array $name 数据名称 + * @param mixed $name 数据名称 * @param string $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -831,7 +831,7 @@ class Request /** * 获取cookie参数 * @access public - * @param string|array $name 数据名称 + * @param mixed $name 数据名称 * @param string $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -868,7 +868,7 @@ class Request /** * 获取server参数 * @access public - * @param string|array $name 数据名称 + * @param mixed $name 数据名称 * @param string $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -951,7 +951,7 @@ class Request /** * 获取环境变量 - * @param string|array $name 数据名称 + * @param mixed $name 数据名称 * @param string $default 默认值 * @param string|array $filter 过滤方法 * @return mixed -- Gitee From 43c46b7b6f6c29abc18f709e0a396a7b0f28cae6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 26 Jul 2017 17:57:57 +0800 Subject: [PATCH 0435/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E5=8F=82=E6=95=B0=E8=AE=BE=E7=BD=AE=20?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=20=E5=8A=A8=E6=80=81=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 4 ++-- library/think/Route.php | 11 +++++++++++ library/think/route/Rule.php | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 01505feb..1b57581b 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -628,7 +628,7 @@ class Request /** * 获取当前请求的参数 * @access public - * @param string|array $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -669,7 +669,7 @@ class Request /** * 设置获取路由参数 * @access public - * @param string|array $name 变量名 + * @param mixed $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed diff --git a/library/think/Route.php b/library/think/Route.php index 9557a76c..92599916 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -877,4 +877,15 @@ class Route return $var; } + /** + * 设置全局的路由分组参数 + * @access protected + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 36746f8c..a8189022 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -754,4 +754,20 @@ abstract class Rule return [$path, $var]; } + /** + * 设置路由参数 + * @access protected + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'option'], $args); + } } -- Gitee From ca93a985f36afc73d4792da73c81a964aecd8a3c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 07:59:07 +0800 Subject: [PATCH 0436/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 6 +++--- library/think/model/relation/HasMany.php | 2 +- library/think/model/relation/HasManyThrough.php | 2 +- library/think/model/relation/HasOne.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index 484e7f1d..5c172bfe 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -292,7 +292,7 @@ trait RelationShip * @access public * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 + * @param string $localKey 当前主键 * @return HasOne */ public function hasOne($model, $foreignKey = '', $localKey = '') @@ -330,7 +330,7 @@ trait RelationShip * @access public * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 + * @param string $localKey 当前主键 * @return HasMany */ public function hasMany($model, $foreignKey = '', $localKey = '') @@ -350,7 +350,7 @@ trait RelationShip * @param string $through 中间模型名 * @param string $foreignKey 关联外键 * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 + * @param string $localKey 当前主键 * @return HasManyThrough */ public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 1b242879..27d1be2a 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -24,7 +24,7 @@ class HasMany extends Relation * @param Model $parent 上级模型对象 * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 + * @param string $localKey 当前模型主键 */ public function __construct(Model $parent, $model, $foreignKey, $localKey) { diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php index e4706dc2..c71d20c9 100644 --- a/library/think/model/relation/HasManyThrough.php +++ b/library/think/model/relation/HasManyThrough.php @@ -32,7 +32,7 @@ class HasManyThrough extends Relation * @param string $through 中间模型名 * @param string $foreignKey 关联外键 * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 + * @param string $localKey 当前主键 */ public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) { diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 402e0be3..794b85ea 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -23,7 +23,7 @@ class HasOne extends OneToOne * @param Model $parent 上级模型对象 * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 + * @param string $localKey 当前模型主键 */ public function __construct(Model $parent, $model, $foreignKey, $localKey) { -- Gitee From 9457a1a70da9a929da3615e92e244d74479b5278 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 08:59:30 +0800 Subject: [PATCH 0437/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 92599916..fbd706b4 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -168,6 +168,10 @@ class Route // 支持多个域名使用相同路由规则 $domain = is_array($name) ? array_shift($name) : $name; + if (!strpos($domain, '.')) { + $domain .= '.' . $this->config->get('app.url_domain_root'); + } + // 获取原始分组 $originGroup = $this->group; @@ -194,6 +198,10 @@ class Route if (is_array($name) && !empty($name)) { foreach ($name as $item) { + if (!strpos($item, '.')) { + $item .= '.' . implode('.', $this->getMasterDomain()); + } + $this->domains[$item] = $this->domains[$domain]; } } @@ -752,8 +760,7 @@ class Route public function check($url, $depr = '/', $must = false, $completeMatch = false) { // 自动检测域名路由 - $host = $this->request->host(); - $domain = $this->checkDomain($host); + $domain = $this->checkDomain(); $url = str_replace($depr, '|', $url); $result = $domain->check($this->request, $url, $depr, $completeMatch); @@ -781,20 +788,19 @@ class Route * @param string $host 当前主机地址 * @return Domain */ - protected function checkDomain($host) + protected function checkDomain() { + // 获取当前主域名 $rootDomain = $this->config->get('app.url_domain_root'); - $domains = $this->domains; if ($rootDomain) { // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); + $domain = explode('.', rtrim(stristr($this->host, $rootDomain, true), '.')); } else { - $domain = explode('.', $host, -2); + $domain = explode('.', $this->host, -2); } - - // 子域名配置 - $item = false; + $domains = $this->domains; + $item = false; if (!empty($domain) && count($domains) > 1) { // 当前子域名 @@ -829,7 +835,7 @@ class Route if (false === $item) { // 检测当前完整域名 - $item = $domains[$host]; + $item = $domains[$this->host]; } return $item; -- Gitee From dd40f928133f8b3e9da04164783f2d183b6252e3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 12:13:57 +0800 Subject: [PATCH 0438/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E8=AF=86=E5=88=AB=20=E5=B9=B6?= =?UTF-8?q?=E4=B8=94=E6=94=AF=E6=8C=81=E5=9F=9F=E5=90=8D=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=B0Response=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 12 ++++++++++-- library/think/route/RuleGroup.php | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index fbd706b4..184248de 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -169,7 +169,13 @@ class Route $domain = is_array($name) ? array_shift($name) : $name; if (!strpos($domain, '.')) { - $domain .= '.' . $this->config->get('app.url_domain_root'); + $root = $this->config->get('app.url_domain_root'); + if (!$root) { + $item = explode('.', $this->host); + $count = count($item); + $root = $item[$count - 2] . '.' . $item[$count - 1]; + } + $domain .= '.' . $root; } // 获取原始分组 @@ -199,7 +205,7 @@ class Route if (is_array($name) && !empty($name)) { foreach ($name as $item) { if (!strpos($item, '.')) { - $item .= '.' . implode('.', $this->getMasterDomain()); + $item .= '.' . $root; } $this->domains[$item] = $this->domains[$domain]; @@ -486,6 +492,8 @@ class Route if ($route instanceof \Closure) { Container::getInstance()->invokeFunction($route); + } elseif ($route instanceof Response) { + $group->setRule($route); } else { $this->rules($route); } diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 64d62949..a723541a 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -60,6 +60,18 @@ class RuleGroup extends Rule $this->pattern = $pattern; } + /** + * 设置分组的路由规则 + * @access public + * @param mixed $rule 路由规则 + * @return $this + */ + public function setRule($rule) + { + $this->rule = $rule; + return $this; + } + /** * 检测分组路由 * @access public -- Gitee From b393f58dd1da8bd37d8e18b2f2066469ad73487d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 12:21:57 +0800 Subject: [PATCH 0439/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=A4=9A=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E4=B8=80=E4=B8=AA=E8=B7=AF=E7=94=B1=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 184248de..cfc3f660 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -208,7 +208,7 @@ class Route $item .= '.' . $root; } - $this->domains[$item] = $this->domains[$domain]; + $this->domains[$item] = $domain; } } @@ -807,10 +807,10 @@ class Route } else { $domain = explode('.', $this->host, -2); } - $domains = $this->domains; - $item = false; - if (!empty($domain) && count($domains) > 1) { + $item = false; + + if (!empty($domain) && count($this->domains) > 1) { // 当前子域名 $subDomain = implode('.', $domain); $domain2 = array_pop($domain); @@ -820,17 +820,17 @@ class Route $domain3 = array_pop($domain); } - if ($subDomain && isset($domains[$subDomain])) { + if ($subDomain && isset($this->domains[$subDomain])) { // 子域名配置 - $item = $domains[$subDomain]; - } elseif (isset($domains['*.' . $domain2]) && !empty($domain3)) { + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { // 泛三级域名 - $item = $domains['*.' . $domain2]; + $item = $this->domains['*.' . $domain2]; $panDomain = $domain3; - } elseif (isset($domains['*']) && !empty($domain2)) { + } elseif (isset($this->domains['*']) && !empty($domain2)) { // 泛二级域名 if ('www' != $domain2) { - $item = $domains['*']; + $item = $this->domains['*']; $panDomain = $domain2; } } @@ -843,7 +843,9 @@ class Route if (false === $item) { // 检测当前完整域名 - $item = $domains[$this->host]; + $item = $this->domains[$this->host]; + } elseif (is_string($item)) { + $item = $this->domains[$item]; } return $item; -- Gitee From ba65954b14ece0162cbfe824b6a9c59ec9b50bf6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 14:51:38 +0800 Subject: [PATCH 0440/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9A=84=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 5 ++--- library/think/route/dispatch/Module.php | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 95bfcdf7..a54272da 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -459,11 +459,10 @@ class App implements \ArrayAccess * @param string $layer 控制层名称 * @param bool $appendSuffix 是否添加类名后缀 * @param string $empty 空控制器名称 - * @param bool $throwException 是否抛异常 * @return Object|null * @throws ClassNotFoundException */ - public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '', $throwException = true) + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') { if (false !== strpos($name, '\\')) { $class = $name; @@ -481,7 +480,7 @@ class App implements \ArrayAccess return $this->__get($class); } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { return $this->__get($emptyClass); - } elseif ($throwException) { + } else { throw new ClassNotFoundException('class not exists:' . $class, $class); } } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index a195132c..db8e1ec7 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -12,6 +12,7 @@ namespace think\route\dispatch; use think\Container; +use think\exception\ClassNotFoundException; use think\exception\HttpException; use think\Loader; use think\route\Dispatch; @@ -90,14 +91,13 @@ class Module extends Dispatch $this->app['hook']->listen('module_init', $this->app['request']); // 实例化控制器 - $instance = $this->app->controller($controller, - $this->app->config('app.url_controller_layer'), - $this->app->config('app.controller_suffix'), - $this->app->config('app.empty_controller'), - false); - - if (is_null($instance)) { - throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1)); + try { + $instance = $this->app->controller($controller, + $this->app->config('app.url_controller_layer'), + $this->app->config('app.controller_suffix'), + $this->app->config('app.empty_controller')); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); } // 获取当前操作名 -- Gitee From 4d7e9172dd7b66b9814a74f8d3851a72ffd0f730 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 15:52:32 +0800 Subject: [PATCH 0441/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index a54272da..e1d859db 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -318,8 +318,8 @@ class App implements \ArrayAccess } elseif (!is_null($data)) { // 默认自动识别响应输出类型 $isAjax = $this->request->isAjax(); + $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); - $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); $response = Response::create($data, $type); } else { $response = Response::create(); @@ -368,7 +368,7 @@ class App implements \ArrayAccess /** * URL路由检测(根据PATH_INFO) * @access public - * @return array + * @return Dispatch * @throws \think\Exception */ public function routeCheck() @@ -389,9 +389,10 @@ class App implements \ArrayAccess } } + // 是否强制路由模式 $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); - // 路由检测(根据路由定义返回不同的URL调度) + // 路由检测 返回一个Dispatch对象 return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match')); } -- Gitee From 2b11749adc5873f36485b25991f5999b1b9cf8a4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 27 Jul 2017 18:06:50 +0800 Subject: [PATCH 0442/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E7=9A=84=E6=96=AD=E7=BA=BF=E9=87=8D=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 6781ae66..3c510ada 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1452,7 +1452,7 @@ abstract class Connection return $this->close()->startTrans(); } throw $e; - } catch (\ErrorException $e) { + } catch (\Exception $e) { if ($this->isBreak($e)) { return $this->close()->startTrans(); } -- Gitee From 2a12ccd8639454083e84e0853accb9afce87e018 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 28 Jul 2017 18:39:04 +0800 Subject: [PATCH 0443/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Hook=E7=B1=BB?= =?UTF-8?q?=E7=9A=84exec=E6=96=B9=E6=B3=95=E5=8F=82=E6=95=B0=E8=B0=83?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Hook.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/Hook.php b/library/think/Hook.php index 2c6f343d..bf62484d 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -126,10 +126,10 @@ class Hook * 执行行为 * @access public * @param mixed $class 行为 - * @param array $params 参数 + * @param mixed $params 参数 * @return mixed */ - public function exec($class, $params = []) + public function exec($class, $params = null) { if ($class instanceof \Closure || is_array($class)) { $method = $class; @@ -140,7 +140,7 @@ class Hook $method = [$class, 'run']; } - return Container::getInstance()->invoke($method, $params); + return Container::getInstance()->invoke($method, [$params]); } /** -- Gitee From 9b0418c54f1c156e128d7c4206e6f5abb616a8ee Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 28 Jul 2017 18:48:50 +0800 Subject: [PATCH 0444/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0behavior=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=94=A8=E4=BA=8E=E6=89=A7=E8=A1=8C=E6=9F=90=E4=B8=AA?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/helper.php b/helper.php index b7be052b..d8fdcfb9 100644 --- a/helper.php +++ b/helper.php @@ -21,6 +21,7 @@ use think\facade\Cache; use think\facade\Config; use think\facade\Cookie; use think\facade\Debug; +use think\facade\Hook; use think\facade\Lang; use think\facade\Log; use think\facade\Request; @@ -79,6 +80,19 @@ if (!function_exists('call')) { } } +if (!function_exists('behavior')) { + /** + * 执行某个行为(run方法) 支持依赖注入 + * @param mixed $behavior 行为类名或者别名 + * @param mixed $args 参数 + * @return mixed + */ + function behavior($behavior, $args = null) + { + return Hook::exec($behavior, $args); + } +} + if (!function_exists('exception')) { /** * 抛出异常处理 -- Gitee From 865d6f71ff14c39d9d836e9a32e0bf2bf86ea672 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 28 Jul 2017 20:51:16 +0800 Subject: [PATCH 0445/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E7=B1=BB=20=E5=8F=96=E6=B6=88=E9=A2=9D=E5=A4=96=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Hook.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/library/think/Hook.php b/library/think/Hook.php index bf62484d..e04e77c1 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -99,17 +99,16 @@ class Hook * @access public * @param string $tag 标签名称 * @param mixed $params 传入参数 - * @param mixed $extra 额外参数 * @param bool $once 只获取一个有效返回值 * @return mixed */ - public function listen($tag, $params = null, $extra = null, $once = false) + public function listen($tag, $params = null, $once = false) { $results = []; $tags = $this->get($tag); foreach ($tags as $key => $name) { - $results[$key] = $this->execTag($name, $tag, $params, $extra); + $results[$key] = $this->execTag($name, $tag, $params); if (false === $results[$key]) { // 如果返回false 则中断行为执行 @@ -149,10 +148,9 @@ class Hook * @param mixed $class 要执行的行为 * @param string $tag 方法名(标签名) * @param mixed $params 参数 - * @param mixed $extra 额外参数 * @return mixed */ - protected function execTag($class, $tag = '', $params = null, $extra = null) + protected function execTag($class, $tag = '', $params = null) { $app = Container::get('app'); @@ -176,7 +174,7 @@ class Hook $class = $class . '->' . $method; } - $result = Container::getInstance()->invoke($call, [$params, $extra]); + $result = Container::getInstance()->invoke($call, [$params]); if ($app->isDebug()) { $debug = $app['debug']; -- Gitee From d759182c4c89d176c80ebcebc1c9cb0468f34f40 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 29 Jul 2017 07:55:08 +0800 Subject: [PATCH 0446/1384] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Console.php | 1 - 1 file changed, 1 deletion(-) diff --git a/library/think/Console.php b/library/think/Console.php index 13dac946..cda0eed8 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -43,7 +43,6 @@ class Console "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", ]; -- Gitee From 03e54d8a50e9410a0c9b0113fba9ebad8588f613 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 29 Jul 2017 10:08:40 +0800 Subject: [PATCH 0447/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E9=85=8D=E7=BD=AE=E7=BC=93=E5=AD=98=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/console/command/Build.php | 2 + .../think/console/command/optimize/Config.php | 59 ++++++++----------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/library/think/console/command/Build.php b/library/think/console/command/Build.php index ad391604..2b83199b 100644 --- a/library/think/console/command/Build.php +++ b/library/think/console/command/Build.php @@ -47,10 +47,12 @@ class Build extends Command } else { $build = include App::getAppPath() . 'build.php'; } + if (empty($build)) { $output->writeln("Build Config Is Empty"); return; } + AppBuild::run($build); $output->writeln("Successed"); diff --git a/library/think/console/command/optimize/Config.php b/library/think/console/command/optimize/Config.php index 71dd5f80..71ef9dd4 100644 --- a/library/think/console/command/optimize/Config.php +++ b/library/think/console/command/optimize/Config.php @@ -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 ) // +---------------------------------------------------------------------- @@ -50,49 +50,42 @@ class Config extends Command protected function buildCacheContent($module) { - $content = ''; - $path = realpath(App::getAppPath() . $module) . DIRECTORY_SEPARATOR; - $configPath = App::getConfigPath(); - $ext = App::getConfigExt(); - $con = Container::get('config'); - + $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 = $con->load($configPath . $module . 'config' . $ext); - - // 读取数据库配置文件 - $filename = $configPath . $module . 'database' . $ext; - $con->load($filename, 'database'); - - // 加载应用状态配置 - if (!empty($config['app_status'])) { - $config = $con->load($configPath . $module . $config['app_status'] . $ext); - } - - // 读取扩展配置文件 - if (is_dir($configPath . $module . 'extra')) { - $dir = $configPath . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if (strpos($file, $ext)) { - $filename = $dir . DIRECTORY_SEPARATOR . $file; - $con->load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } + $configPath = is_dir($path . 'config') ? $path . 'config' : App::getConfigPath() . $module; + } else { + $configPath = App::getConfigPath(); + } + $ext = App::getConfigExt(); + $config = Container::get('config'); + + $files = scandir($configPath); + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $ext) { + $filename = $configPath . DIRECTORY_SEPARATOR . $file; + $config->load($filename, pathinfo($file, PATHINFO_FILENAME)); } } // 加载行为扩展文件 - if (is_file($configPath . $module . 'tags.php')) { - $content .= '\think\Hook::import(' . (var_export(include $configPath . $module . 'tags.php', true)) . ');' . PHP_EOL; + if (is_file($path . 'tags.php')) { + $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export(include $path . 'tags.php' ?: [], true)) . ');' . PHP_EOL; } // 加载公共文件 if (is_file($path . 'common.php')) { - $content .= substr(php_strip_whitespace($path . 'common.php'), 5) . PHP_EOL; + $common = substr(php_strip_whitespace($path . 'common.php'), 6); + if ($common) { + $content .= PHP_EOL . $common . PHP_EOL; + } + } + + if (is_file($path . 'provider.php')) { + $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export(include $path . 'provider.php' ?: [], true) . ');' . PHP_EOL; } - $content .= '\think\facade\Config::set(' . var_export($con->get(), true) . ');'; + $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL; return $content; } -- Gitee From 60d9f16d5bf3b0b00ff8621b1abe9f8c77e72679 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 29 Jul 2017 11:11:11 +0800 Subject: [PATCH 0448/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=B1=BB=E5=BA=93?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E6=96=87=E4=BB=B6=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../console/command/optimize/Autoload.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/library/think/console/command/optimize/Autoload.php b/library/think/console/command/optimize/Autoload.php index 802ac444..c5eab03c 100644 --- a/library/think/console/command/optimize/Autoload.php +++ b/library/think/console/command/optimize/Autoload.php @@ -36,12 +36,12 @@ class Autoload extends Command return [ EOF; - + $app = Container::get('app'); $namespacesToScan = [ - Container::get('app')->getNamespace() . '\\' => realpath(rtrim(Container::get('app')->getAppPath())), - 'think\\' => Container::get('app')->getAppPath() . 'library/think', - 'traits\\' => Container::get('app')->getAppPath() . 'library/traits', - '' => realpath(rtrim(Container::get('app')->getRootPath() . 'extend')), + $app->getNamespace() . '\\' => realpath(rtrim($app->getAppPath())), + 'think\\' => $app->getAppPath() . 'library/think', + 'traits\\' => $app->getAppPath() . 'library/traits', + '' => realpath(rtrim($app->getRootPath() . 'extend')), ]; krsort($namespacesToScan); @@ -61,7 +61,7 @@ EOF; $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; } $classmapFile .= "];\n"; - $runtimePath = Container::get('app')->getRuntimePath(); + $runtimePath = $app->getRuntimePath(); if (!is_dir($runtimePath)) { @mkdir($runtimePath, 0755, true); } @@ -93,22 +93,22 @@ EOF; protected function getPathCode($path) { - $baseDir = ''; - $appPath = $this->normalizePath(realpath(Container::get('app')->getAppPath())); - $libPath = $this->normalizePath(realpath(Container::get('app')->getThinkPath() . 'library')); - $extendPath = $this->normalizePath(realpath(Container::get('app')->getRootPath() . 'extend')); + $app = Container::get('app'); + $appPath = $this->normalizePath(realpath($app->getAppPath())); + $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library')); + $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend')); $path = $this->normalizePath($path); if (strpos($path, $libPath . '/') === 0) { - $path = substr($path, strlen(Container::get('app')->getThinkPath() . 'library')); - $baseDir = 'LIB_PATH'; + $path = substr($path, strlen($app->getThinkPath() . 'library')); + $baseDir = "'" . $libPath . "/'"; } elseif (strpos($path, $appPath . '/') === 0) { $path = substr($path, strlen($appPath) + 1); - $baseDir = 'APP_PATH'; + $baseDir = "'" . $appPath . "/'"; } elseif (strpos($path, $extendPath . '/') === 0) { $path = substr($path, strlen($extendPath) + 1); - $baseDir = 'EXTEND_PATH'; + $baseDir = "'" . $extendPath . "/'"; } if (false !== $path) { -- Gitee From 2c7f98bd4a99749ac27a8ec41c16aa2c9baad355 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 29 Jul 2017 13:12:11 +0800 Subject: [PATCH 0449/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=9A=84=E7=89=B9=E6=AE=8A=E8=AF=B7=E6=B1=82OPTIONS=E5=A4=84?= =?UTF-8?q?=E7=90=86=20=E5=A2=9E=E5=8A=A0allowOptions=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=97=85=E6=8E=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 25 +++++++++++++++++++++++++ library/think/route/RuleGroup.php | 5 +++++ library/think/route/RuleItem.php | 5 +++++ 3 files changed, 35 insertions(+) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index a8189022..1c5a8d8c 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -315,6 +315,31 @@ abstract class Rule return $this->option('complete_match', $match); } + /** + * 设置是否允许OPTIONS嗅探 + * @access public + * @param bool $allow + * @return $this + */ + public function allowOptions($allow = true) + { + return $this->option('allow_options', $allow); + } + + /** + * 检查OPTIONS请求 + * @access public + * @param Request $request + * @return Dispatch|void + */ + protected function checkAllowOptions($request) + { + if (!empty($this->option['allow_options']) && $request->method(true) == 'OPTIONS') { + // 允许OPTIONS嗅探 + return new ResponseDispatch(Response::create()->code(200)); + } + } + /** * 设置路由规则全局有效 * @access public diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index a723541a..69d7c511 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -83,6 +83,11 @@ class RuleGroup extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + if ($dispatch = $this->checkAllowOptions($request)) { + // 允许OPTIONS嗅探 + return $dispatch; + } + // 检查参数有效性 if (!$this->checkOption($this->option, $request)) { return false; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index cd8b0ae2..761f4554 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -104,6 +104,11 @@ class RuleItem extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + if ($dispatch = $this->checkAllowOptions($request)) { + // 允许OPTIONS嗅探 + return $dispatch; + } + // 检查参数有效性 if (!$this->checkOption($this->option, $request)) { return false; -- Gitee From e357d16e2ededeb73dd185e8ad9361d4d2c3bd00 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 31 Jul 2017 10:53:25 +0800 Subject: [PATCH 0450/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BCollection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/Collection.php b/library/think/model/Collection.php index ac838650..8c561374 100644 --- a/library/think/model/Collection.php +++ b/library/think/model/Collection.php @@ -85,7 +85,7 @@ class Collection extends BaseCollection { $this->each(function ($model) use ($append, $override) { /** @var Model $model */ - $model->append($append, $override); + $model && $model->append($append, $override); }); return $this; -- Gitee From b672ecba10cad4b3b930ebc558547a1c84e8a802 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 31 Jul 2017 11:18:50 +0800 Subject: [PATCH 0451/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BView=E7=B1=BB?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0exists=E6=96=B9=E6=B3=95=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=96=87=E4=BB=B6=E6=98=AF=E5=90=A6=E5=AD=98?= =?UTF-8?q?=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/View.php | 11 +++++++++++ library/think/response/View.php | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/library/think/View.php b/library/think/View.php index a3cc25ba..4957bc34 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -127,6 +127,17 @@ class View return $this; } + /** + * 检查模板是否存在 + * @access private + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->engine->exists($name); + } + /** * 解析和获取模板内容 用于输出 * @param string $template 模板文件名或者内容 diff --git a/library/think/response/View.php b/library/think/response/View.php index 6369fc45..c289b7a4 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -70,6 +70,19 @@ class View extends Response return $this; } + /** + * 检查模板是否存在 + * @access private + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return Container::get('view') + ->init(Container::get('app')->config('template')) + ->exists($name); + } + /** * 视图内容替换 * @access public -- Gitee From 7e553c5ec7222a566b5d58d613e0ba3f00b03d52 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 31 Jul 2017 12:13:29 +0800 Subject: [PATCH 0452/1384] =?UTF-8?q?input=E5=8A=A9=E6=89=8B=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=94=AF=E6=8C=81route=E5=8F=98=E9=87=8F=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper.php b/helper.php index d8fdcfb9..a78e03a6 100644 --- a/helper.php +++ b/helper.php @@ -181,7 +181,7 @@ if (!function_exists('input')) { if ($pos = strpos($key, '.')) { // 指定参数来源 $method = substr($key, 0, $pos); - if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { $key = substr($key, $pos + 1); } else { $method = 'param'; -- Gitee From b95f4fad3541220f1d14604edbaeac08e13c94b9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 31 Jul 2017 14:56:32 +0800 Subject: [PATCH 0453/1384] =?UTF-8?q?=E5=8A=A9=E6=89=8B=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 700 ++++++++++++++++++++++++++--------------------------- 1 file changed, 350 insertions(+), 350 deletions(-) diff --git a/helper.php b/helper.php index a78e03a6..13f74d5a 100644 --- a/helper.php +++ b/helper.php @@ -29,14 +29,35 @@ use think\facade\Session; use think\facade\Url; use think\Response; -if (!function_exists('container')) { +if (!function_exists('abort')) { /** - * 获取容器对象实例 - * @return Container + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 */ - function container() + function abort($code, $message = null, $header = []) { - return Container::getInstance(); + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return app()->action($url, $vars, $layer, $appendSuffix); } } @@ -53,6 +74,19 @@ if (!function_exists('app')) { } } +if (!function_exists('behavior')) { + /** + * 执行某个行为(run方法) 支持依赖注入 + * @param mixed $behavior 行为类名或者别名 + * @param mixed $args 参数 + * @return mixed + */ + function behavior($behavior, $args = null) + { + return Hook::exec($behavior, $args); + } +} + if (!function_exists('bind')) { /** * 绑定一个类到容器 @@ -67,78 +101,95 @@ if (!function_exists('bind')) { } } -if (!function_exists('call')) { +if (!function_exists('cache')) { /** - * 调用反射执行callable 支持依赖注入 - * @param mixed $callable 支持闭包等callable写法 - * @param array $args 参数 + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 * @return mixed */ - function call($callable, $args = []) + function cache($name, $value = '', $options = null, $tag = null) { - return Container::getInstance()->invoke($callable, $args); + if (is_array($options)) { + // 缓存操作的同时初始化 + Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::rm($name); + } else { + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } } } -if (!function_exists('behavior')) { +if (!function_exists('call')) { /** - * 执行某个行为(run方法) 支持依赖注入 - * @param mixed $behavior 行为类名或者别名 - * @param mixed $args 参数 + * 调用反射执行callable 支持依赖注入 + * @param mixed $callable 支持闭包等callable写法 + * @param array $args 参数 * @return mixed */ - function behavior($behavior, $args = null) + function call($callable, $args = []) { - return Hook::exec($behavior, $args); + return Container::getInstance()->invoke($callable, $args); } } -if (!function_exists('exception')) { +if (!function_exists('class_basename')) { /** - * 抛出异常处理 - * - * @param string $msg 异常消息 - * @param integer $code 异常代码 默认为0 - * @param string $exception 异常类 + * 获取类名(不包含命名空间) * - * @throws Exception + * @param string|object $class + * @return string */ - function exception($msg, $code = 0, $exception = '') + function class_basename($class) { - $e = $exception ?: '\think\Exception'; - throw new $e($msg, $code); + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); } } -if (!function_exists('debug')) { +if (!function_exists('class_uses_recursive')) { /** - * 记录时间(微秒)和内存使用情况 - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer|string $dec 小数位 如果是m 表示统计内存占用 - * @return mixed + *获取一个类里所有用到的trait,包括父类的 + * + * @param $class + * @return array */ - function debug($start, $end = '', $dec = 6) + function class_uses_recursive($class) { - if ('' == $end) { - Debug::remark($start); - } else { - return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + if (is_object($class)) { + $class = get_class($class); } - } -} -if (!function_exists('lang')) { - /** - * 获取语言变量值 - * @param string $name 语言变量名 - * @param array $vars 动态变量值 - * @param string $lang 语言 - * @return mixed - */ - function lang($name, $vars = [], $lang = '') - { - return Lang::get($name, $vars, $lang); + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); } } @@ -163,80 +214,57 @@ if (!function_exists('config')) { } } -if (!function_exists('input')) { - /** - * 获取输入数据 支持默认值和过滤 - * @param string $key 获取的变量名 - * @param mixed $default 默认值 - * @param string $filter 过滤方法 - * @return mixed - */ - function input($key = '', $default = null, $filter = null) - { - if (0 === strpos($key, '?')) { - $key = substr($key, 1); - $has = true; - } - - if ($pos = strpos($key, '.')) { - // 指定参数来源 - $method = substr($key, 0, $pos); - if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { - $key = substr($key, $pos + 1); - } else { - $method = 'param'; - } - } else { - // 默认为自动判断 - $method = 'param'; - } - - if (isset($has)) { - return request()->has($key, $method, $default); - } else { - return request()->$method($key, $default, $filter); - } - } -} - -if (!function_exists('widget')) { +if (!function_exists('container')) { /** - * 渲染输出Widget - * @param string $name Widget名称 - * @param array $data 传入的参数 - * @return mixed + * 获取容器对象实例 + * @return Container */ - function widget($name, $data = []) + function container() { - return app()->action($name, $data, 'widget'); + return Container::getInstance(); } } -if (!function_exists('model')) { +if (!function_exists('controller')) { /** - * 实例化Model - * @param string $name Model名称 - * @param string $layer 业务层名称 + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Model + * @return \think\Controller */ - function model($name = '', $layer = 'model', $appendSuffix = false) + function controller($name, $layer = 'controller', $appendSuffix = false) { - return app()->model($name, $layer, $appendSuffix); + return app()->controller($name, $layer, $appendSuffix); } } -if (!function_exists('validate')) { +if (!function_exists('cookie')) { /** - * 实例化验证器 - * @param string $name 验证器名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Validate + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed */ - function validate($name = '', $layer = 'validate', $appendSuffix = false) + function cookie($name, $value = '', $option = null) { - return app()->validate($name, $layer, $appendSuffix); + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } } } @@ -254,32 +282,21 @@ if (!function_exists('db')) { } } -if (!function_exists('controller')) { +if (!function_exists('debug')) { /** - * 实例化控制器 格式:[模块/]控制器 - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Controller - */ - function controller($name, $layer = 'controller', $appendSuffix = false) - { - return app()->controller($name, $layer, $appendSuffix); - } -} - -if (!function_exists('action')) { - /** - * 调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 * @return mixed */ - function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + function debug($start, $end = '', $dec = 6) { - return app()->action($url, $vars, $layer, $appendSuffix); + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } } } @@ -297,179 +314,72 @@ if (!function_exists('dump')) { } } -if (!function_exists('url')) { +if (!function_exists('exception')) { /** - * Url生成 - * @param string $url 路由地址 - * @param string|array $vars 变量 - * @param bool|string $suffix 生成的URL后缀 - * @param bool|string $domain 域名 - * @return string + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception */ - function url($url = '', $vars = '', $suffix = true, $domain = false) + function exception($msg, $code = 0, $exception = '') { - return Url::build($url, $vars, $suffix, $domain); + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); } } -if (!function_exists('session')) { +if (!function_exists('halt')) { /** - * Session管理 - * @param string|array $name session名称,如果为数组表示进行session设置 - * @param mixed $value session值 - * @param string $prefix 前缀 - * @return mixed + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 */ - function session($name, $value = '', $prefix = null) + function halt($var) { - if (is_array($name)) { - // 初始化 - Session::init($name); - } elseif (is_null($name)) { - // 清除 - Session::clear($value); - } elseif ('' === $value) { - // 判断或获取 - return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); - } elseif (is_null($value)) { - // 删除 - return Session::delete($name, $prefix); - } else { - // 设置 - return Session::set($name, $value, $prefix); - } - } -} + dump($var); -if (!function_exists('cookie')) { - /** - * Cookie管理 - * @param string|array $name cookie名称,如果为数组表示进行cookie设置 - * @param mixed $value cookie值 - * @param mixed $option 参数 - * @return mixed - */ - function cookie($name, $value = '', $option = null) - { - if (is_array($name)) { - // 初始化 - Cookie::init($name); - } elseif (is_null($name)) { - // 清除 - Cookie::clear($value); - } elseif ('' === $value) { - // 获取 - return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); - } elseif (is_null($value)) { - // 删除 - return Cookie::delete($name); - } else { - // 设置 - return Cookie::set($name, $value, $option); - } + throw new HttpResponseException(new Response); } } -if (!function_exists('cache')) { +if (!function_exists('input')) { /** - * 缓存管理 - * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 - * @param mixed $value 缓存值 - * @param mixed $options 缓存参数 - * @param string $tag 缓存标签 + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 * @return mixed */ - function cache($name, $value = '', $options = null, $tag = null) + function input($key = '', $default = null, $filter = null) { - if (is_array($options)) { - // 缓存操作的同时初始化 - Cache::connect($options); - } elseif (is_array($name)) { - // 缓存初始化 - return Cache::connect($name); + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; } - if ('' === $value) { - // 获取缓存 - return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); - } elseif (is_null($value)) { - // 删除缓存 - return Cache::rm($name); - } else { - // 缓存数据 - if (is_array($options)) { - $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 - } else { - $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 - } - - if (is_null($tag)) { - return Cache::set($name, $value, $expire); + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); } else { - return Cache::tag($tag)->set($name, $value, $expire); + $method = 'param'; } + } else { + // 默认为自动判断 + $method = 'param'; } - } -} -if (!function_exists('trace')) { - /** - * 记录日志信息 - * @param mixed $log log信息 支持字符串和数组 - * @param string $level 日志级别 - * @return void|array - */ - function trace($log = '[think]', $level = 'log') - { - if ('[think]' === $log) { - return Log::getLog(); + if (isset($has)) { + return request()->has($key, $method, $default); } else { - Log::record($log, $level); + return request()->$method($key, $default, $filter); } } } -if (!function_exists('request')) { - /** - * 获取当前Request对象实例 - * @return Request - */ - function request() - { - return app('request'); - } -} - -if (!function_exists('response')) { - /** - * 创建普通 Response 对象实例 - * @param mixed $data 输出数据 - * @param int|string $code 状态码 - * @param array $header 头信息 - * @param string $type - * @return Response - */ - function response($data = [], $code = 200, $header = [], $type = 'html') - { - return Response::create($data, $type, $code, $header); - } -} - -if (!function_exists('view')) { - /** - * 渲染模板输出 - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $replace 模板替换 - * @param integer $code 状态码 - * @return \think\response\View - */ - function view($template = '', $vars = [], $replace = [], $code = 200) - { - return Response::create($template, 'view', $code)->replace($replace)->assign($vars); - } -} - if (!function_exists('json')) { /** * 获取\think\response\Json对象实例 @@ -500,18 +410,54 @@ if (!function_exists('jsonp')) { } } -if (!function_exists('xml')) { +if (!function_exists('lang')) { /** - * 获取\think\response\Xml对象实例 - * @param mixed $data 返回的数据 - * @param integer $code 状态码 - * @param array $header 头部 - * @param array $options 参数 - * @return \think\response\Xml + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed */ - function xml($data = [], $code = 200, $header = [], $options = []) + function lang($name, $vars = [], $lang = '') { - return Response::create($data, 'xml', $code, $header, $options); + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return app()->model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } } } @@ -534,33 +480,58 @@ if (!function_exists('redirect')) { } } -if (!function_exists('abort')) { +if (!function_exists('request')) { /** - * 抛出HTTP异常 - * @param integer|Response $code 状态码 或者 Response对象实例 - * @param string $message 错误信息 - * @param array $header 参数 + * 获取当前Request对象实例 + * @return Request */ - function abort($code, $message = null, $header = []) + function request() { - if ($code instanceof Response) { - throw new HttpResponseException($code); - } else { - throw new HttpException($code, $message, null, $header); - } + return app('request'); } } -if (!function_exists('halt')) { +if (!function_exists('response')) { /** - * 调试变量并且中断输出 - * @param mixed $var 调试变量或者信息 + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response */ - function halt($var) + function response($data = [], $code = 200, $header = [], $type = 'html') { - dump($var); + return Response::create($data, $type, $code, $header); + } +} - throw new HttpResponseException(new Response); +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear($value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } } } @@ -579,80 +550,109 @@ if (!function_exists('token')) { } } -if (!function_exists('parse_name')) { +if (!function_exists('trace')) { /** - * 字符串命名风格转换 - * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 - * @param string $name 字符串 - * @param integer $type 转换类型 - * @param bool $ucfirst 首字母是否大写(驼峰规则) - * @return string + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return void|array */ - function parse_name($name, $type = 0, $ucfirst = true) + function trace($log = '[think]', $level = 'log') { - if ($type) { - $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { - return strtoupper($match[1]); - }, $name); - - return $ucfirst ? ucfirst($name) : lcfirst($name); + if ('[think]' === $log) { + return Log::getLog(); } else { - return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + Log::record($log, $level); } } } -if (!function_exists('class_basename')) { +if (!function_exists('trait_uses_recursive')) { /** - * 获取类名(不包含命名空间) + * 获取一个trait里所有引用到的trait * - * @param string|object $class - * @return string + * @param string $trait + * @return array */ - function class_basename($class) + function trait_uses_recursive($trait) { - $class = is_object($class) ? get_class($class) : $class; - return basename(str_replace('\\', '/', $class)); + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; } } -if (!function_exists('class_uses_recursive')) { +if (!function_exists('url')) { /** - *获取一个类里所有用到的trait,包括父类的 - * - * @param $class - * @return array + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string */ - function class_uses_recursive($class) + function url($url = '', $vars = '', $suffix = true, $domain = false) { - if (is_object($class)) { - $class = get_class($class); - } + return Url::build($url, $vars, $suffix, $domain); + } +} - $results = []; - $classes = array_merge([$class => $class], class_parents($class)); - foreach ($classes as $class) { - $results += trait_uses_recursive($class); - } +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return app()->validate($name, $layer, $appendSuffix); + } +} - return array_unique($results); +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $replace 模板替换 + * @param integer $code 状态码 + * @return \think\response\View + */ + function view($template = '', $vars = [], $replace = [], $code = 200) + { + return Response::create($template, 'view', $code)->replace($replace)->assign($vars); } } -if (!function_exists('trait_uses_recursive')) { +if (!function_exists('widget')) { /** - * 获取一个trait里所有引用到的trait - * - * @param string $trait - * @return array + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed */ - function trait_uses_recursive($trait) + function widget($name, $data = []) { - $traits = class_uses($trait); - foreach ($traits as $trait) { - $traits += trait_uses_recursive($trait); - } + return app()->action($name, $data, 'widget'); + } +} - return $traits; +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); } } -- Gitee From 9290f28b1163e38af7fdd29da784c34226dab059 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 Aug 2017 12:14:00 +0800 Subject: [PATCH 0454/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=AD=E8=8E=B7=E5=8F=96=E5=85=B6=E5=AE=83?= =?UTF-8?q?=E7=9A=84=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/library/think/Config.php b/library/think/Config.php index 526c0533..c98804e1 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -121,6 +121,22 @@ class Config $name = explode('.', strtolower($name)); $config = $this->config; + if (!isset($config[$name[0]])) { + // 如果尚未载入 则动态加载配置文件 + $module = Container::get('request')->module(); + $module = $module ? $module . '/' : ''; + $path = Container::get('app')->getAppPath() . $module; + if (is_dir($path . 'config')) { + $file = $path . 'config/' . $name[0] . Container::get('app')->getConfigExt(); + } elseif (is_dir(Container::get('app')->getConfigPath() . $module)) { + $file = Container::get('app')->getConfigPath() . $module . $name[0] . Container::get('app')->getConfigExt(); + } + + if (isset($file) && is_file($file)) { + $this->load($file); + } + } + // 按.拆分成多维数组进行判断 foreach ($name as $val) { if (isset($config[$val])) { -- Gitee From ae63424842078f701e0a62d78aad7437d74387e1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 Aug 2017 13:40:34 +0800 Subject: [PATCH 0455/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Config.php b/library/think/Config.php index c98804e1..0d9b0141 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -133,7 +133,7 @@ class Config } if (isset($file) && is_file($file)) { - $this->load($file); + $this->load($file, $name[0]); } } -- Gitee From 78339802f6d409e75542aeba63672001b23a693a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 Aug 2017 15:03:04 +0800 Subject: [PATCH 0456/1384] =?UTF-8?q?Request=E7=B1=BB=E7=9A=84ip=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E9=BB=98=E8=AE=A4=E4=BD=BF=E7=94=A8=E9=AB=98=E7=BA=A7?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 1b57581b..25fdeeb2 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1329,7 +1329,7 @@ class Request * @param boolean $adv 是否进行高级模式获取(有可能被伪装) * @return mixed */ - public function ip($type = 0, $adv = false) + public function ip($type = 0, $adv = true) { $type = $type ? 1 : 0; static $ip = null; -- Gitee From 23bfda27d2829e257354b45bfb129ffc70f4efbd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 Aug 2017 18:56:17 +0800 Subject: [PATCH 0457/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=9A=84prefix=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 69d7c511..51afd238 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -218,6 +218,10 @@ class RuleGroup extends Rule */ public function prefix($prefix) { + if ($this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + return $this->option('prefix', $prefix); } -- Gitee From 7579dbaad80344057d9586a2271bffa5f6096c09 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 Aug 2017 11:55:23 +0800 Subject: [PATCH 0458/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 6 +++--- library/think/File.php | 3 ++- library/think/Lang.php | 5 +++-- library/think/Request.php | 1 + library/think/Template.php | 1 + library/think/db/Builder.php | 10 ++++------ library/think/db/Connection.php | 11 ++++++++--- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index e1d859db..bc1bf184 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -415,7 +415,7 @@ class App implements \ArrayAccess * @param string $layer 业务层名称 * @param bool $appendSuffix 是否添加类名后缀 * @param string $common 公共模块名 - * @return Object + * @return Model * @throws ClassNotFoundException */ public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') @@ -460,7 +460,7 @@ class App implements \ArrayAccess * @param string $layer 控制层名称 * @param bool $appendSuffix 是否添加类名后缀 * @param string $empty 空控制器名称 - * @return Object|null + * @return object * @throws ClassNotFoundException */ public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') @@ -492,7 +492,7 @@ class App implements \ArrayAccess * @param string $layer 验证层名称 * @param bool $appendSuffix 是否添加类名后缀 * @param string $common 公共模块名 - * @return Object + * @return Validate * @throws ClassNotFoundException */ public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') diff --git a/library/think/File.php b/library/think/File.php index de529daf..c3248e71 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -99,7 +99,8 @@ class File extends SplFileObject /** * 获取文件的哈希散列值 - * @return $string + * @param string $type + * @return string */ public function hash($type = 'sha1') { diff --git a/library/think/Lang.php b/library/think/Lang.php index 8fb601ee..da7313a9 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -50,6 +50,7 @@ class Lang if (!isset($this->lang[$range])) { $this->lang[$range] = []; } + if (is_array($name)) { return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; } else { @@ -59,8 +60,8 @@ class Lang /** * 加载语言定义(不区分大小写) - * @param string $file 语言文件 - * @param string $range 语言作用域 + * @param string|array $file 语言文件 + * @param string $range 语言作用域 * @return mixed */ public function load($file, $range = '') diff --git a/library/think/Request.php b/library/think/Request.php index 25fdeeb2..4b04a5c3 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -85,6 +85,7 @@ class Request protected $file = []; protected $cookie = []; protected $server = []; + protected $env = []; protected $header = []; /** diff --git a/library/think/Template.php b/library/think/Template.php index db6b8f23..118935cb 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -60,6 +60,7 @@ class Template /** * 架构函数 * @access public + * @param array $config */ public function __construct(array $config = []) { diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index d0db239b..7d040353 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -37,7 +37,6 @@ abstract class Builder * 架构函数 * @access public * @param Connection $connection 数据库连接对象实例 - * @param Query $query 数据库查询对象实例 */ public function __construct(Connection $connection) { @@ -47,7 +46,7 @@ abstract class Builder /** * 获取当前的连接对象实例 * @access public - * @return void + * @return Connection */ public function getConnection() { @@ -547,7 +546,7 @@ abstract class Builder * limit分析 * @access protected * @param Query $query 查询对象 - * @param mixed $lmit + * @param mixed $limit * @return string */ protected function parseLimit(Query $query, $limit) @@ -722,7 +721,7 @@ abstract class Builder * 设置锁机制 * @access protected * @param Query $query 查询对象 - * @param bool $locl + * @param bool $lock * @return string */ protected function parseLock(Query $query, $lock = false) @@ -852,7 +851,7 @@ abstract class Builder } /** - * 生成slectinsert SQL + * 生成slect insert SQL * @access public * @param Query $query 查询对象 * @param array $fields 数据 @@ -878,7 +877,6 @@ abstract class Builder * 生成update SQL * @access public * @param Query $query 查询对象 - * @param array $fields 数据 * @return string */ public function update(Query $query) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 3c510ada..66d0a44e 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -524,7 +524,8 @@ abstract class Connection * @param bool $pdo 是否返回PDO对象 * @return array * @throws BindParamException - * @throws PDOException + * @throws \PDOException + * @throws \Exception */ public function query($sql, $bind = [], $master = false, $pdo = false) { @@ -595,7 +596,8 @@ abstract class Connection * @param array $bind 参数绑定 * @return int * @throws BindParamException - * @throws PDOException + * @throws \PDOException + * @throws \Exception */ public function execute($sql, $bind = []) { @@ -1429,6 +1431,8 @@ abstract class Connection * 启动事务 * @access public * @return void + * @throws \PDOException + * @throws \Exception */ public function startTrans() { @@ -1596,7 +1600,7 @@ abstract class Connection /** * 是否断线 * @access protected - * @param \PDOException $e 异常对象 + * @param \PDOException|\Exception $e 异常对象 * @return bool */ protected function isBreak($e) @@ -1889,6 +1893,7 @@ abstract class Connection * @param mixed $value 缓存数据 * @param array $options 缓存参数 * @param array $bind 绑定参数 + * @return string */ protected function getCacheKey($value, $options, $bind = []) { -- Gitee From d4da351edc9b4f6d348041de4387c78d6ab93ddf Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 Aug 2017 15:03:24 +0800 Subject: [PATCH 0459/1384] =?UTF-8?q?=E5=9F=9F=E5=90=8D=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BB=B6=E8=BF=9F=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 44 ++++++++++++++++--------------- library/think/route/Domain.php | 27 ++++++++++++++++++- library/think/route/RuleGroup.php | 2 +- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index cfc3f660..5bf1baa3 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -178,30 +178,32 @@ class Route $domain .= '.' . $root; } - // 获取原始分组 - $originGroup = $this->group; - - // 设置当前域名 - $this->domain = $domain; - - $this->domains[$domain] = new Domain($this, $domain, $option, $pattern); - - $this->group = $this->createTopGroup($this->domains[$domain]); + $route = 1 == $this->config->get('url_route_parse') ? $rule : null; + + $this->domains[$domain] = new Domain($this, $domain, $route, $option, $pattern); + + if (is_null($route)) { + // 获取原始分组 + $originGroup = $this->group; + // 设置当前域名 + $this->domain = $domain; + $this->group = $this->createTopGroup($this->domains[$domain]); + + // 执行域名路由 + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_array($rule)) { + $this->rules($rule); + } elseif ($rule) { + $this->bind($rule); + } - // 执行域名路由 - if ($rule instanceof \Closure) { - Container::getInstance()->invokeFunction($rule); - } elseif (is_array($rule)) { - $this->rules($rule); - } elseif ($rule) { - $this->bind($rule); + // 还原默认域名 + $this->domain = $this->host; + // 还原默认分组 + $this->group = $originGroup; } - // 还原默认域名 - $this->domain = $this->host; - // 还原默认分组 - $this->group = $originGroup; - if (is_array($name) && !empty($name)) { foreach ($name as $item) { if (!strpos($item, '.')) { diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index c7abfae4..70645b21 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -13,10 +13,12 @@ namespace think\route; use think\Container; use think\Loader; +use think\Response; use think\Route; use think\route\dispatch\Callback as CallbackDispatch; use think\route\dispatch\Controller as ControllerDispatch; use think\route\dispatch\Module as ModuleDispatch; +use think\route\dispatch\Response as ResponseDispatch; class Domain extends RuleGroup { @@ -25,14 +27,16 @@ class Domain extends RuleGroup * @access public * @param Route $router 路由对象 * @param string $name 分组名称 + * @param mixed $rule 域名路由 * @param array $option 路由参数 * @param array $pattern 变量规则 */ - public function __construct(Route $router, $name = '', $option = [], $pattern = []) + public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = []) { $this->router = $router; $this->name = trim($name, '/'); $this->option = $option; + $this->rule = $rule; $this->pattern = $pattern; } @@ -47,6 +51,27 @@ class Domain extends RuleGroup */ public function check($request, $url, $depr = '/', $completeMatch = false) { + if ($this->rule) { + // 延迟解析域名路由 + if ($this->rule instanceof Response) { + return new ResponseDispatch($this->rule); + } + + $group = new RuleGroup($this->router); + $this->addRule($group); + $this->router->setGroup($group); + + if ($this->rule instanceof \Closure) { + Container::getInstance()->invokeFunction($this->rule); + } elseif (is_array($this->rule)) { + $this->router->rules($this->rule); + } else { + $this->router->bind($this->rule); + } + + $this->rule = null; + } + // 检测别名路由 if ($this->router->getAlias($url) || $this->router->getAlias(strstr($url, '|', true))) { // 检测路由别名 diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 51afd238..92c1301c 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -94,11 +94,11 @@ class RuleGroup extends Rule } if ($this->rule) { + // 延迟解析分组路由 if ($this->rule instanceof Response) { return new ResponseDispatch($this->rule); } - // 解析分组路由 $group = $this->router->getGroup(); $this->router->setGroup($this); -- Gitee From f46e043c229abdcd779f04b2d8f949ea55742829 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 Aug 2017 15:46:11 +0800 Subject: [PATCH 0460/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=BB=B6=E8=BF=9F?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E7=9A=84=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=B8=BAurl=5Flazy=5Froute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 4 ++-- library/think/Route.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/convention.php b/convention.php index c8d8e161..066ff821 100644 --- a/convention.php +++ b/convention.php @@ -83,8 +83,8 @@ return [ 'url_common_param' => false, // URL参数方式 0 按名称成对解析 1 按顺序解析 'url_param_type' => 0, - // 路由解析方式 0 正常解析 1 延迟解析 - 'url_route_parse' => 0, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, // 是否强制使用路由 'url_route_must' => false, // 域名根,如thinkphp.cn diff --git a/library/think/Route.php b/library/think/Route.php index 5bf1baa3..a34b08ec 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -178,7 +178,7 @@ class Route $domain .= '.' . $root; } - $route = 1 == $this->config->get('url_route_parse') ? $rule : null; + $route = $this->config->get('url_lazy_route') ? $rule : null; $this->domains[$domain] = new Domain($this, $domain, $route, $option, $pattern); @@ -483,7 +483,7 @@ class Route } // 创建分组实例 - $rule = 1 == $this->config->get('url_route_parse') ? $route : null; + $rule = $this->config->get('url_lazy_route') ? $route : null; $group = new RuleGroup($this, $this->group, $name, $rule, $option, $pattern); if (is_null($rule)) { -- Gitee From 1b2bc279b81860d3b958b93c6c5d1ebeb02f43b4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 8 Aug 2017 11:27:28 +0800 Subject: [PATCH 0461/1384] =?UTF-8?q?=E5=BB=B6=E8=BF=9F=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=20=E6=94=AF=E6=8C=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=98=A0=E5=B0=84=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E4=BB=8E=E8=80=8C=E6=94=AF=E6=8C=81Url=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 9 +++ library/think/Console.php | 1 + library/think/Route.php | 15 +++-- library/think/Url.php | 9 ++- .../think/console/command/optimize/Route.php | 56 +++++++++++++++++++ 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 library/think/console/command/optimize/Route.php diff --git a/library/think/App.php b/library/think/App.php index bc1bf184..2d1211ab 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -666,6 +666,15 @@ class App implements \ArrayAccess return $this->thinkPath; } + /** + * 获取路由目录 + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + /** * 获取应用配置目录 * @return string diff --git a/library/think/Console.php b/library/think/Console.php index cda0eed8..65f52fb1 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -44,6 +44,7 @@ class Console "think\\console\\command\\optimize\\Autoload", "think\\console\\command\\optimize\\Config", "think\\console\\command\\optimize\\Schema", + "think\\console\\command\\optimize\\Route", ]; public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') diff --git a/library/think/Route.php b/library/think/Route.php index a34b08ec..76c23945 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -72,8 +72,7 @@ class Route /** * 初始化默认域名 - * @access public - * @param RuleGroup $group 域名 + * @access protected * @return void */ protected function setDefaultDomain() @@ -88,15 +87,15 @@ class Route // 默认分组 $this->group = $this->createTopGroup($domain); - } /** * 创建一个域名下的顶级路由分组 * @access protected + * @param Domain $domain 域名 * @return RuleGroup */ - protected function createTopGroup($domain) + protected function createTopGroup(Domain $domain) { $group = new RuleGroup($this); // 注册分组到当前域名 @@ -173,7 +172,7 @@ class Route if (!$root) { $item = explode('.', $this->host); $count = count($item); - $root = $item[$count - 2] . '.' . $item[$count - 1]; + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; } $domain .= '.' . $root; } @@ -887,7 +886,11 @@ class Route if (0 === strpos($val, ':')) { // URL变量 - $name = substr($val, 1); + $name = substr($val, 1); + if ('$' == substr($name, -1)) { + $name = substr($name, 0, -1); + } + $var[$name] = $optional ? 2 : 1; } } diff --git a/library/think/Url.php b/library/think/Url.php index 8ab45a51..f68a9e41 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -22,6 +22,11 @@ class Url public function __construct(App $app) { $this->app = $app; + + if (is_file($app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $app['route']->setName(include $app->getRuntimePath() . 'route.php'); + } } /** @@ -296,11 +301,11 @@ class Url foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key . '', ':' . $key . '$', '<' . $key . '>'], urlencode($vars[$key]), $url); unset($vars[$key]); $result = [$url, $domain, $suffix]; } elseif (2 == $val) { - $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>'], '', $url); $result = [$url, $domain, $suffix]; } else { break; diff --git a/library/think/console/command/optimize/Route.php b/library/think/console/command/optimize/Route.php new file mode 100644 index 00000000..0664d809 --- /dev/null +++ b/library/think/console/command/optimize/Route.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- +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') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + file_put_contents(Container::get('app')->getRuntimePath() . 'route.php', $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + Container::get('route')->setName([]); + Container::get('config')->set('url_lazy_route', false); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + $files = scandir($path); + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + Container::get('route')->import($rules); + } + } + } + $content = 'getName(), true) . ';'; + return $content; + } +} -- Gitee From 58d6ab66216d2a17794d741f7a21cc6696426e47 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 8 Aug 2017 12:09:54 +0800 Subject: [PATCH 0462/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A9=BA=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/route/dispatch/Module.php | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/convention.php b/convention.php index 066ff821..37f4191c 100644 --- a/convention.php +++ b/convention.php @@ -56,6 +56,8 @@ return [ 'default_action' => 'index', // 默认验证器 'default_validate' => '', + // 默认的空模块名 + 'empty_module' => '', // 默认的空控制器名 'empty_controller' => 'Error', // 操作方法前缀 diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index db8e1ec7..884df59f 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -44,6 +44,9 @@ class Module extends Dispatch } } elseif (!in_array($module, $this->app->config('app.deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { $available = true; + } elseif ($this->app->config('app.empty_module')) { + $module = $this->app->config('app.empty_module'); + $available = true; } // 模块初始化 @@ -61,7 +64,6 @@ class Module extends Dispatch $this->app->config('app.request_cache_expire'), $this->app->config('app.request_cache_except') ); - } else { throw new HttpException(404, 'module not exists:' . $module); } -- Gitee From deca9d5db8d24fb689a900d4169e9d4cfdb44737 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 8 Aug 2017 12:34:52 +0800 Subject: [PATCH 0463/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 2d1211ab..096d5894 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -282,7 +282,7 @@ class App implements \ArrayAccess $dispatch = $this->dispatch; if (empty($dispatch)) { // 进行URL路由检测 - $dispatch = $this->routeCheck($this->request); + $dispatch = $this->routeCheck(); } // 记录当前调度信息 @@ -369,7 +369,6 @@ class App implements \ArrayAccess * URL路由检测(根据PATH_INFO) * @access public * @return Dispatch - * @throws \think\Exception */ public function routeCheck() { -- Gitee From 8b45bf2b7ed668affe44243172ce250df255f8f7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 8 Aug 2017 14:48:27 +0800 Subject: [PATCH 0464/1384] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 40f997a5..f7d8ef4b 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -98,7 +98,7 @@ class Validate 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', - 'mobile' => '/^1[3|4|5|7|8][0-9]\d{8}$/', + 'mobile' => '/^1[3-9][0-9]\d{8}$/', 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', 'zip' => '/\d{6}/', ]; -- Gitee From 35d869dd7a1dffda9ccfe09cc0f8c2496026364e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 9 Aug 2017 11:21:39 +0800 Subject: [PATCH 0465/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/Driver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 09bcc14c..9b79416a 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -193,7 +193,7 @@ abstract class Driver $key = 'tag_' . md5($tag); $value = $this->get($key); if ($value) { - return explode(',', $value); + return array_filter(explode(',', $value)); } else { return []; } -- Gitee From a9e90e55c45f7491138b625784f435cc9e8214eb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 9 Aug 2017 11:40:21 +0800 Subject: [PATCH 0466/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BrequireIf=20require?= =?UTF-8?q?With=20requireCallback=E9=AA=8C=E8=AF=81=E8=A7=84=E5=88=99?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E5=86=85=E7=BD=AE=E9=94=99=E8=AF=AF=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index f7d8ef4b..d22b7878 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -969,7 +969,7 @@ class Validate list($field, $val) = explode(',', $rule); if ($this->getDataValue($data, $field) == $val) { - return !empty($value); + return !empty($value) || '0' == $value; } else { return true; } @@ -988,7 +988,7 @@ class Validate $result = call_user_func_array($rule, [$value, $data]); if ($result) { - return !empty($value); + return !empty($value) || '0' == $value; } else { return true; } @@ -1007,7 +1007,7 @@ class Validate $val = $this->getDataValue($data, $rule); if (!empty($val)) { - return !empty($value); + return !empty($value) || '0' == $value; } else { return true; } @@ -1311,6 +1311,8 @@ class Validate $msg = $this->message[$attribute]; } elseif (isset(self::$typeMsg[$type])) { $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; } else { $msg = $title . '规则错误'; } -- Gitee From 7b5083de4e5cc5f11e22dfe65dc99ee8428f77b9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 9 Aug 2017 15:19:47 +0800 Subject: [PATCH 0467/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BallowIp=E5=92=8Cden?= =?UTF-8?q?yIp=E9=AA=8C=E8=AF=81=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index d22b7878..7a38e21d 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -1197,7 +1197,7 @@ class Validate */ public function allowIp($value, $rule) { - return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** @@ -1209,7 +1209,7 @@ class Validate */ public function denyIp($value, $rule) { - return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } /** -- Gitee From b682b0968c32be599bd05fc03cb767e10eb35651 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 10 Aug 2017 10:34:40 +0800 Subject: [PATCH 0468/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=E4=B8=AD=E7=9A=84Null?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 7d040353..ab82ed5b 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -323,7 +323,7 @@ abstract class Builder // 查询规则和条件 if (!is_array($val)) { - $val = ['=', $val]; + $val = is_null($val) ? ['null', ''] : ['=', $val]; } list($exp, $value) = $val; -- Gitee From 8f67a281dfd29163b046b11a97c48f0b4d23dd46 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 10 Aug 2017 14:08:32 +0800 Subject: [PATCH 0469/1384] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 76c23945..fbd7ea63 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -244,7 +244,7 @@ class Route * 读取路由绑定 * @access public * @param string $domain 域名 - * @return string + * @return string|null */ public function getBind($domain = null) { @@ -403,9 +403,16 @@ class Route return $rule; } + /** + * 设置路由标识 用于URL反解生成 + * @access public + * @param string $rule 路由规则 + * @param string $name 路由标识 + * @param array $option 路由参数 + * @return void + */ protected function setRuleName($rule, $name, $option = []) { - // 设置路由标识 用于URL快速生成 $vars = $this->parseVar($rule); if (isset($option['ext'])) { @@ -424,7 +431,7 @@ class Route * @access public * @param Rule $rule 路由规则 * @param string $method 请求类型 - * @return void + * @return $this */ public function setCrossDomainRule($rule, $method = '*') { @@ -656,7 +663,7 @@ class Route * 获取别名路由定义 * @access public * @param string $name 路由别名 - * @return string|array + * @return string|array|null */ public function getAlias($name = null) { -- Gitee From e60cfb3475321de499e884b35b01421f66834ec8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 Aug 2017 15:09:05 +0800 Subject: [PATCH 0470/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E7=9A=84date=E5=87=BD=E6=95=B0=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Template.php b/library/think/Template.php index 118935cb..d724a187 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -1059,7 +1059,7 @@ class Template case 'raw': continue; case 'date': - $name = 'date_format(date_create(' . $name . '),' . $args[1] . ')'; + $name = 'date(' . $args[1] . ',strtotime(' . $name . ') ?: ' . $name . ')'; break; case 'first': $name = 'current(' . $name . ')'; -- Gitee From 26d7096648e88b915940dba487621b333ca08d34 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 Aug 2017 15:43:23 +0800 Subject: [PATCH 0471/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmodel=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=5F=5Fisset=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 1 + library/think/model/concern/Attribute.php | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index f78207d3..9697af9c 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -11,6 +11,7 @@ namespace think; +use InvalidArgumentException; use think\db\Query; /** diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index fe6bb6a9..3105ebe8 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -13,6 +13,7 @@ namespace think\model\concern; use InvalidArgumentException; use think\Loader; +use think\model\Relation; trait Attribute { @@ -404,13 +405,16 @@ trait Attribute if ($relation) { $modelRelation = $this->$relation(); - $value = $this->getRelationData($modelRelation); + if ($modelRelation instanceof Relation) { + $value = $this->getRelationData($modelRelation); - // 保存关联对象值 - $this->relation[$name] = $value; - } else { - throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + // 保存关联对象值 + $this->relation[$name] = $value; + return $value; + } } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); } return $value; -- Gitee From 9c5a5d707d86ec99e8f09346631b96df9de0a3c9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 Aug 2017 15:59:27 +0800 Subject: [PATCH 0472/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=B1=BBisset=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 9697af9c..6811e5c0 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -11,7 +11,6 @@ namespace think; -use InvalidArgumentException; use think\db\Query; /** @@ -811,17 +810,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function __isset($name) { - try { - if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { - return true; - } else { - $this->getAttr($name); - return true; - } - } catch (InvalidArgumentException $e) { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { return false; } - } /** -- Gitee From 5d5954f339302ad8b8a4802f87935d51ee2a6b49 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 Aug 2017 15:58:49 +0800 Subject: [PATCH 0473/1384] =?UTF-8?q?exception=5Fhandle=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=94=AF=E6=8C=81=E4=BD=BF=E7=94=A8=E9=97=AD?= =?UTF-8?q?=E5=8C=85=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Error.php | 3 +++ library/think/exception/Handle.php | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/library/think/Error.php b/library/think/Error.php index 1ad0c2bf..9c92a876 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -112,6 +112,9 @@ class Error $handle = new $class; } else { $handle = new Handle; + if ($class instanceof \Closure) { + $handle->setRender($class); + } } } diff --git a/library/think/exception/Handle.php b/library/think/exception/Handle.php index 366074bc..db0b51b7 100644 --- a/library/think/exception/Handle.php +++ b/library/think/exception/Handle.php @@ -18,11 +18,16 @@ use think\Response; class Handle { - + protected $render; protected $ignoreReport = [ '\\think\\exception\\HttpException', ]; + public function setRender($render) + { + $this->render = $render; + } + /** * Report or log an exception. * @@ -75,6 +80,13 @@ class Handle */ public function render(Exception $e) { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + if ($result) { + return $result; + } + } + if ($e instanceof HttpException) { return $this->renderHttpException($e); } else { -- Gitee From a9b0d9e65f198e17e6ae3c9840b625b1df9262f6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 Aug 2017 15:59:25 +0800 Subject: [PATCH 0474/1384] =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=93=88=E5=B8=8C=E8=A7=84=E5=88=99hash=5Ftype?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index d56ca8f9..5dea04fd 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -25,6 +25,7 @@ class File extends Driver 'cache_subdir' => true, 'prefix' => '', 'path' => '', + 'hash_type' => 'md5', 'data_compress' => false, ]; @@ -72,7 +73,8 @@ class File extends Driver */ protected function getCacheKey($name) { - $name = md5($name); + $name = hash($this->options['hash_type'], $name); + if ($this->options['cache_subdir']) { // 使用子目录 $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); -- Gitee From ef35c25e7165ee1741c81b25980cc024ac9f49fb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 Aug 2017 15:06:53 +0800 Subject: [PATCH 0475/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BLang=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Lang.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/library/think/Lang.php b/library/think/Lang.php index da7313a9..e4476411 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -19,6 +19,8 @@ class Lang private $range = 'zh-cn'; // 语言自动侦测的变量 protected $langDetectVar = 'lang'; + // 语言Cookie变量 + protected $langCookieVar = 'think_var'; // 允许语言列表 protected $allowLangList = []; // Accept-Language转义为对应语言包名称 系统默认配置 @@ -165,6 +167,9 @@ class Lang 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); @@ -185,6 +190,18 @@ class Lang return $this->range; } + /** + * 设置当前语言到Cookie + * @param string $lang 语言 + * @return void + */ + public function saveToCookie($lang = null) + { + $range = $lang ?: $this->range; + + $_COOKIE[$this->langCookieVar] = $range; + } + /** * 设置语言自动侦测的变量 * @param string $var 变量名称 @@ -195,6 +212,16 @@ class Lang $this->langDetectVar = $var; } + /** + * 设置语言的cookie保存变量 + * @param string $var 变量名称 + * @return void + */ + public static function setLangCookieVar($var) + { + $this->langCookieVar = $var; + } + /** * 设置允许的语言列表 * @param array $list 语言列表 -- Gitee From fb134bd2eeef04d0052f0bc283b8711d4cb3dc56 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 Aug 2017 14:17:42 +0800 Subject: [PATCH 0476/1384] =?UTF-8?q?=E7=BC=93=E5=AD=98=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 10 ++++++++-- library/think/Response.php | 3 ++- library/think/cache/Driver.php | 9 ++++++++- library/think/route/Rule.php | 5 +++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 4b04a5c3..ca61d4c2 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1609,10 +1609,16 @@ class Request * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id * @param mixed $expire 缓存有效期 * @param array $except 缓存排除 + * @param string $tag 缓存标签 * @return void */ - public function cache($key, $expire = null, $except = []) + public function cache($key, $expire = null, $except = [], $tag = null) { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + if (false !== $key && $this->isGet() && !$this->isCheckCache) { // 标记请求缓存检查 $this->isCheckCache = true; @@ -1669,7 +1675,7 @@ class Request $response = Response::create($content)->header($header); throw new HttpResponseException($response); } else { - $this->cache = [$key, $expire]; + $this->cache = [$key, $expire, $tag]; } } } diff --git a/library/think/Response.php b/library/think/Response.php index eae3bec5..e203ee32 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -105,7 +105,8 @@ class Response $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'; - Container::get('cache')->set($cache[0], [$data, $this->header], $cache[1]); + + Container::get('cache')->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); } } diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 9b79416a..4ddbeaad 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -142,21 +142,28 @@ abstract class Driver */ public function tag($name, $keys = null, $overlay = false) { - if (is_null($keys)) { + if (is_null($name)) { + + } elseif (is_null($keys)) { $this->tag = $name; } else { $key = 'tag_' . md5($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)); } + return $this; } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 1c5a8d8c..d4056511 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -441,13 +441,14 @@ abstract class Rule protected function parseRequestCache($request, $cache) { if (is_array($cache)) { - list($key, $expire) = $cache; + list($key, $expire, $tag) = array_pad($cache, 3, null); } else { $key = str_replace('|', '/', $url); $expire = $cache; + $tag = null; } - $request->cache($key, $expire); + $request->cache($key, $expire, $tag); } /** -- Gitee From 4eace24a914c3bfdd0f2b1b23a74bab09af230d1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 Aug 2017 16:25:15 +0800 Subject: [PATCH 0477/1384] =?UTF-8?q?remember=E6=96=B9=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=94=81=E5=AE=9A=E6=9C=BA=E5=88=B6=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=B8=94=E6=94=AF=E6=8C=81=E4=BE=9D=E8=B5=96=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/Driver.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 4ddbeaad..8e7f13f5 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -11,6 +11,8 @@ namespace think\cache; +use think\Container; + /** * 缓存基础类 */ @@ -122,13 +124,32 @@ abstract class Driver public function remember($name, $value, $expire = null) { if (!$this->has($name)) { - if ($value instanceof \Closure) { - $value = call_user_func($value); + while ($this->has($name . '.lock')) { + // 存在锁定则等待 + } + + try { + // 锁定 + $this->set($name . '.lock', true); + + if ($value instanceof \Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } + + // 缓存数据 + $this->set($name, $value, $expire); + + // 解锁 + $this->rm($name . '.lock'); + } catch (\Exception $e) { + // 解锁 + $this->rm($name . '.lock'); } - $this->set($name, $value, $expire); } else { $value = $this->get($name); } + return $value; } -- Gitee From 5ecef3a1f6d780ae122836b30a6cefad4263de73 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 Aug 2017 00:01:39 +0800 Subject: [PATCH 0478/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bchunk=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 0e29bafe..682e4caa 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2386,9 +2386,10 @@ class Query * @param integer $count 每次处理的数据数量 * @param callable $callback 处理回调方法 * @param string $column 分批处理的字段名 + * @param string $order 字段排序 * @return boolean */ - public function chunk($count, $callback, $column = null) + public function chunk($count, $callback, $column = null, $order = 'asc') { $options = $this->getOptions(); @@ -2398,9 +2399,13 @@ class Query $table = ''; } - $column = $column ?: $this->getPk($table); + $column = $column ?: $this->getPk($table); + if (is_array($column)) { + $column = $column[0]; + } + $bind = $this->bind; - $resultSet = $this->limit($count)->order($column, 'asc')->select(); + $resultSet = $this->limit($count)->order($column, $order)->select(); if (strpos($column, '.')) { list($alias, $key) = explode('.', $column); @@ -2423,8 +2428,8 @@ class Query $resultSet = $this->options($options) ->limit($count) ->bind($bind) - ->where($column, '>', $lastId) - ->order($column, 'asc') + ->where($column, 'asc' == $order ? '>' : '<', $lastId) + ->order($column, $order) ->select(); if ($resultSet instanceof Collection) { -- Gitee From d2052bc7398fa07fc82a167316ee127a81cc8ca4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 21 Aug 2017 11:16:33 +0800 Subject: [PATCH 0479/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bchunk=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=9A=84order=E5=8F=82=E6=95=B0=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 682e4caa..3ac00f53 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2428,7 +2428,7 @@ class Query $resultSet = $this->options($options) ->limit($count) ->bind($bind) - ->where($column, 'asc' == $order ? '>' : '<', $lastId) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId) ->order($column, $order) ->select(); -- Gitee From 500405c6701106598ffe0e4bc8c45453cc4e0837 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 28 Aug 2017 11:32:06 +0800 Subject: [PATCH 0480/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmodel=E7=B1=BB?= =?UTF-8?q?=E7=9A=84save=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 6811e5c0..25386713 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -267,7 +267,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } if (!empty($where)) { - $this->isUpdate = true; + $this->isUpdate = true; + $this->updateWhere = $where; } } -- Gitee From 273683736170d64bf8e75111e69e427196032243 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 28 Aug 2017 17:00:48 +0800 Subject: [PATCH 0481/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BFile=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 5dea04fd..73dfa934 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -132,7 +132,7 @@ class File extends Driver return $default; } - $content = substr($content, 20, -3); + $content = substr($content, 32); if ($this->options['data_compress'] && function_exists('gzcompress')) { //启用数据压缩 $content = gzuncompress($content); @@ -175,7 +175,7 @@ class File extends Driver $data = gzcompress($data, 3); } - $data = ""; + $data = "\n" . $data; $result = file_put_contents($filename, $data); if ($result) { -- Gitee From bc8b09d003b02cce7dbbb71bd4c522e6f645004d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 Aug 2017 11:50:35 +0800 Subject: [PATCH 0482/1384] =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=B1=BB=E7=9A=84s?= =?UTF-8?q?et=E6=96=B9=E6=B3=95=E6=9C=89=E6=95=88=E6=9C=9F=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BD=BF=E7=94=A8DateTime=E7=B1=BB=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E8=BF=87=E6=9C=9F=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 12 ++++++++---- library/think/cache/driver/Lite.php | 18 ++++++++++-------- library/think/cache/driver/Memcache.php | 10 +++++++--- library/think/cache/driver/Memcached.php | 12 +++++++----- library/think/cache/driver/Redis.php | 10 +++++++--- library/think/cache/driver/Sqlite.php | 12 ++++++++---- library/think/cache/driver/Wincache.php | 10 +++++++--- library/think/cache/driver/Xcache.php | 10 +++++++--- 8 files changed, 61 insertions(+), 33 deletions(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 73dfa934..c1563661 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -126,7 +126,7 @@ class File extends Driver if (false !== $content) { $expire = (int) substr($content, 8, 12); - if (0 != $expire && $_SERVER['REQUEST_TIME'] > filemtime($filename) + $expire) { + if (0 != $expire && time() > filemtime($filename) + $expire) { //缓存过期删除缓存文件 $this->unlink($filename); return $default; @@ -149,9 +149,9 @@ class File extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间 0为永久 * @return boolean */ public function set($name, $value, $expire = null) @@ -162,6 +162,10 @@ class File extends Driver $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $filename = $this->getCacheKey($name); if ($this->tag && !is_file($filename)) { diff --git a/library/think/cache/driver/Lite.php b/library/think/cache/driver/Lite.php index 93c6083e..9b94f474 100644 --- a/library/think/cache/driver/Lite.php +++ b/library/think/cache/driver/Lite.php @@ -82,7 +82,7 @@ class Lite extends Driver // 判断是否过期 $mtime = filemtime($filename); - if ($mtime < $_SERVER['REQUEST_TIME']) { + if ($mtime < time()) { // 清除已经过期的文件 unlink($filename); return $default; @@ -97,9 +97,9 @@ class Lite extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间 0为永久 * @return bool */ public function set($name, $value, $expire = null) @@ -110,9 +110,11 @@ class Lite extends Driver $expire = $this->options['expire']; } - // 模拟永久 - if (0 === $expire) { - $expire = 10 * 365 * 24 * 3600; + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; } $filename = $this->getCacheKey($name); @@ -126,7 +128,7 @@ class Lite extends Driver // 通过设置修改时间实现有效期 if ($ret) { isset($first) && $this->setTagItem($filename); - touch($filename, $_SERVER['REQUEST_TIME'] + $expire); + touch($filename, $expire); } return $ret; diff --git a/library/think/cache/driver/Memcache.php b/library/think/cache/driver/Memcache.php index 95b1210d..8b6b9413 100644 --- a/library/think/cache/driver/Memcache.php +++ b/library/think/cache/driver/Memcache.php @@ -91,9 +91,9 @@ class Memcache extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) @@ -104,6 +104,10 @@ class Memcache extends Driver $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { $first = true; } diff --git a/library/think/cache/driver/Memcached.php b/library/think/cache/driver/Memcached.php index 0d20c07e..90fb5440 100644 --- a/library/think/cache/driver/Memcached.php +++ b/library/think/cache/driver/Memcached.php @@ -105,9 +105,9 @@ class Memcached extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) @@ -118,14 +118,16 @@ class Memcached extends Driver $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; - if ($this->handler->set($key, $value, $expire)) { isset($first) && $this->setTagItem($key); return true; diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 5adc8dfd..a9f637d4 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -99,9 +99,9 @@ class Redis extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) @@ -112,6 +112,10 @@ class Redis extends Driver $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { $first = true; } diff --git a/library/think/cache/driver/Sqlite.php b/library/think/cache/driver/Sqlite.php index 8db6d400..cbd9f885 100644 --- a/library/think/cache/driver/Sqlite.php +++ b/library/think/cache/driver/Sqlite.php @@ -108,9 +108,9 @@ class Sqlite extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) @@ -125,7 +125,11 @@ class Sqlite extends Driver $expire = $this->options['expire']; } - $expire = (0 == $expire) ? 0 : ($_SERVER['REQUEST_TIME'] + $expire); //缓存有效期为0表示永久缓存 + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } if (function_exists('gzcompress')) { //数据压缩 diff --git a/library/think/cache/driver/Wincache.php b/library/think/cache/driver/Wincache.php index dfba3866..25af8358 100644 --- a/library/think/cache/driver/Wincache.php +++ b/library/think/cache/driver/Wincache.php @@ -75,9 +75,9 @@ class Wincache extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) @@ -88,6 +88,10 @@ class Wincache extends Driver $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $key = $this->getCacheKey($name); if ($this->tag && !$this->has($name)) { diff --git a/library/think/cache/driver/Xcache.php b/library/think/cache/driver/Xcache.php index 08db962f..58b9f232 100644 --- a/library/think/cache/driver/Xcache.php +++ b/library/think/cache/driver/Xcache.php @@ -73,9 +73,9 @@ class Xcache extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) @@ -86,6 +86,10 @@ class Xcache extends Driver $expire = $this->options['expire']; } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { $first = true; } -- Gitee From 8820fa20cac101a55db1ee1098b1c968d5bccf33 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 Aug 2017 14:15:06 +0800 Subject: [PATCH 0483/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BBuilder=E7=B1=BB?= =?UTF-8?q?=E7=9A=84parseData=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ab82ed5b..64de793f 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -100,8 +100,8 @@ abstract class Builder $result[$item] = $val; } else { $key = str_replace('.', '_', $key); - $query->bind('__data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':__data__' . $key; + $query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':data__' . $key; } } } -- Gitee From e47192b507ab3f46569cd4c47a2c926c13010204 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 Aug 2017 15:39:38 +0800 Subject: [PATCH 0484/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BUrl=E7=9A=84?= =?UTF-8?q?=E5=9F=9F=E5=90=8D=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 7 ++++++- library/think/db/Builder.php | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/library/think/Url.php b/library/think/Url.php index f68a9e41..3019ed86 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -271,7 +271,12 @@ class Url } } - $scheme = $this->app['request']->isSsl() || $this->app['config']->get('is_https') ? 'https://' : 'http://'; + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $this->app['request']->isSsl() || $this->app['config']->get('is_https') ? 'https://' : 'http://'; + + } return $scheme . $domain; } diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 64de793f..333d7992 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -365,6 +365,11 @@ abstract class Builder $bindName = md5($bindName); } + if (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { -- Gitee From 3ef934f93b9fed0cd06d072e8a10cf503930c651 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 15:12:13 +0800 Subject: [PATCH 0485/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/Driver.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 8e7f13f5..abac0f16 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -124,13 +124,13 @@ abstract class Driver public function remember($name, $value, $expire = null) { if (!$this->has($name)) { - while ($this->has($name . '.lock')) { + while ($this->has($name . '_lock')) { // 存在锁定则等待 } try { // 锁定 - $this->set($name . '.lock', true); + $this->set($name . '_lock', true); if ($value instanceof \Closure) { // 获取缓存数据 @@ -141,10 +141,10 @@ abstract class Driver $this->set($name, $value, $expire); // 解锁 - $this->rm($name . '.lock'); + $this->rm($name . '_lock'); } catch (\Exception $e) { // 解锁 - $this->rm($name . '.lock'); + $this->rm($name . '_lock'); } } else { $value = $this->get($name); -- Gitee From 6d35a0b20d470257c907fd5c08e850abd8ecbc55 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 15:58:07 +0800 Subject: [PATCH 0486/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BBchunk?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E9=81=BF=E5=85=8D=E5=8F=97order=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=BD=B1=E5=93=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3ac00f53..c5f6f2f1 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2404,8 +2404,12 @@ class Query $column = $column[0]; } + if (isset($options['order'])) { + unset($options['order']); + } + $bind = $this->bind; - $resultSet = $this->limit($count)->order($column, $order)->select(); + $resultSet = $this->options($options)->limit($count)->order($column, $order)->select(); if (strpos($column, '.')) { list($alias, $key) = explode('.', $column); -- Gitee From e7c9c5244f5fd067ca2ff25400d712f8258c0b5b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 16:15:48 +0800 Subject: [PATCH 0487/1384] =?UTF-8?q?chunk=E6=96=B9=E6=B3=95=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 4 ++++ library/think/db/Query.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index e959df1a..911c1e75 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -62,4 +62,8 @@ return [ 'sae mc write error' => 'SAE mc 写入错误', 'route name not exists' => '路由标识不存在(或参数不够)', 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', ]; diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c5f6f2f1..01fabc3a 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2388,6 +2388,7 @@ class Query * @param string $column 分批处理的字段名 * @param string $order 字段排序 * @return boolean + * @throws DbException */ public function chunk($count, $callback, $column = null, $order = 'asc') { @@ -2405,6 +2406,7 @@ class Query } if (isset($options['order'])) { + Container::get('app')->isDebug() && throw new DbException('chunk not support call order'); unset($options['order']); } -- Gitee From 1b353f96297220ce6801b7338d83bec901534cb1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 16:57:39 +0800 Subject: [PATCH 0488/1384] =?UTF-8?q?Pivot=E6=9E=B6=E6=9E=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=B7=BB=E5=8A=A0=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Pivot.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/Pivot.php b/library/think/model/Pivot.php index 869d87f9..3efb7185 100644 --- a/library/think/model/Pivot.php +++ b/library/think/model/Pivot.php @@ -28,7 +28,7 @@ class Pivot extends Model * @param array|object $data 数据 * @param string $table 中间数据表名 */ - public function __construct(Model $parent, $data = [], $table = '') + public function __construct(Model $parent = null, $data = [], $table = '') { $this->parent = $parent; -- Gitee From 0b256fbf6b5e31e81b62a40368bb6a806ce62f6a Mon Sep 17 00:00:00 2001 From: rozbo <75200306@qq.com> Date: Thu, 31 Aug 2017 17:08:53 +0800 Subject: [PATCH 0489/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## make方法支持缓存闭包函数的返回结果及依赖注入。 ```php $app->bind('xxxx',function(\Think\App $app){ return new \XXXX; }); ``` 使用时当`$newInstance=false`时则不缓存当次执行结果。 ## 绑定实例时自动修正标识到真正的类名。 这其实算是个bug,重现方式如下: ```php $app->bind("xxx",'\my\xxxx'); $app->instance("xxx",$xxx); class abc{ public function __construct(\my\xxxx $b) { } } ``` 此时,其实已经存在这个实例了,但是还是会重新new一个。。。 --- library/think/Container.php | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index 3bb48ec7..cc02ff65 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -90,6 +90,9 @@ class Container */ public function instance($abstract, $instance) { + if(isset($this->bind[$abstract])){ + $abstract=$this->bind[$abstract]; + } $this->instances[$abstract] = $instance; return $this; @@ -121,25 +124,31 @@ class Container $newInstance = true; $vars = []; } - + //如果之前实例过,直接返回 if (isset($this->instances[$abstract]) && !$newInstance) { $object = $this->instances[$abstract]; - } elseif (isset($this->bind[$abstract])) { - $concrete = $this->bind[$abstract]; + }else{ + //如果没有实例过,但绑定过具体的类,就make一个 + if (isset($this->bind[$abstract])) { + $concrete = $this->bind[$abstract]; - if ($concrete instanceof \Closure) { - $object = call_user_func_array($concrete, $vars); - } else { - $object = $this->make($concrete, $vars, $newInstance); + if ($concrete instanceof \Closure) { + $object = $this->invokeFunction($concrete, $vars); + } else { + $object = $this->make($concrete, $vars, $newInstance); + } } - } else { - $object = $this->invokeClass($abstract, $vars); - + //如果是彻底没有弄过,就把它当成一个类来反射 + else { + $object = $this->invokeClass($abstract, $vars); + } + //如果不是每次创建新的实例的话,就缓存下来。 if (!$newInstance) { $this->instances[$abstract] = $object; } } + return $object; } -- Gitee From 9c43036f548a732e58cc113dbc9f055cbee89bcd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 17:53:19 +0800 Subject: [PATCH 0490/1384] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 17 +++++++---------- library/think/db/Query.php | 4 +++- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index cc02ff65..6b635e79 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -90,9 +90,10 @@ class Container */ public function instance($abstract, $instance) { - if(isset($this->bind[$abstract])){ - $abstract=$this->bind[$abstract]; + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; } + $this->instances[$abstract] = $instance; return $this; @@ -124,11 +125,10 @@ class Container $newInstance = true; $vars = []; } - //如果之前实例过,直接返回 + if (isset($this->instances[$abstract]) && !$newInstance) { $object = $this->instances[$abstract]; - }else{ - //如果没有实例过,但绑定过具体的类,就make一个 + } else { if (isset($this->bind[$abstract])) { $concrete = $this->bind[$abstract]; @@ -137,18 +137,15 @@ class Container } else { $object = $this->make($concrete, $vars, $newInstance); } - } - //如果是彻底没有弄过,就把它当成一个类来反射 - else { + } else { $object = $this->invokeClass($abstract, $vars); } - //如果不是每次创建新的实例的话,就缓存下来。 + if (!$newInstance) { $this->instances[$abstract] = $object; } } - return $object; } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 01fabc3a..ca5b2c86 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2406,7 +2406,9 @@ class Query } if (isset($options['order'])) { - Container::get('app')->isDebug() && throw new DbException('chunk not support call order'); + if (Container::get('app')->isDebug()) { + throw new DbException('chunk not support call order'); + } unset($options['order']); } -- Gitee From 4da41c1f1601f489be82414adefe1e9fc4352f4a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 18:10:42 +0800 Subject: [PATCH 0491/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BModel=E7=B1=BB?= =?UTF-8?q?=E7=9A=84save=E6=96=B9=E6=B3=95=E5=AF=B9sequence=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/think/Model.php b/library/think/Model.php index 25386713..afd28f76 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -231,6 +231,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function save($data = [], $where = [], $sequence = null) { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + if (!$this->checkBeforeSave($data, $where)) { return false; } -- Gitee From 1a02ef6147f65b2d01ec8ea2ac111bff94e82a5f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 31 Aug 2017 19:23:51 +0800 Subject: [PATCH 0492/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bsession=E7=B1=BB?= =?UTF-8?q?=E7=9A=84prefix=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/think/Session.php b/library/think/Session.php index a52f2c57..6cf1b18f 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -25,6 +25,8 @@ class Session */ public function prefix($prefix = '') { + empty($this->init) && $this->boot(); + if (empty($prefix) && null !== $prefix) { return $this->prefix; } else { -- Gitee From fea1b15035963c61bbbab5d0df4e4e1963ce3b0f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 1 Sep 2017 17:26:39 +0800 Subject: [PATCH 0493/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BB?= =?UTF-8?q?=E7=9A=84cache=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 2 +- library/think/cache/driver/Lite.php | 2 +- library/think/cache/driver/Memcached.php | 2 +- library/think/cache/driver/Redis.php | 2 +- library/think/cache/driver/Sqlite.php | 2 +- library/think/cache/driver/Wincache.php | 2 +- library/think/cache/driver/Xcache.php | 2 +- library/think/db/Query.php | 8 ++++---- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index c1563661..3bcf6db0 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -151,7 +151,7 @@ class File extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param int|DateTime $expire 有效时间 0为永久 + * @param int|\DateTime $expire 有效时间 0为永久 * @return boolean */ public function set($name, $value, $expire = null) diff --git a/library/think/cache/driver/Lite.php b/library/think/cache/driver/Lite.php index 9b94f474..d04aeed6 100644 --- a/library/think/cache/driver/Lite.php +++ b/library/think/cache/driver/Lite.php @@ -99,7 +99,7 @@ class Lite extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param int|DateTime $expire 有效时间 0为永久 + * @param int|\DateTime $expire 有效时间 0为永久 * @return bool */ public function set($name, $value, $expire = null) diff --git a/library/think/cache/driver/Memcached.php b/library/think/cache/driver/Memcached.php index 90fb5440..21d0abbf 100644 --- a/library/think/cache/driver/Memcached.php +++ b/library/think/cache/driver/Memcached.php @@ -107,7 +107,7 @@ class Memcached extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param integer|DateTime $expire 有效时间(秒) + * @param integer|\DateTime $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index a9f637d4..d444f5f6 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -101,7 +101,7 @@ class Redis extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param integer|DateTime $expire 有效时间(秒) + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) diff --git a/library/think/cache/driver/Sqlite.php b/library/think/cache/driver/Sqlite.php index cbd9f885..58d5cf22 100644 --- a/library/think/cache/driver/Sqlite.php +++ b/library/think/cache/driver/Sqlite.php @@ -110,7 +110,7 @@ class Sqlite extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param integer|DateTime $expire 有效时间(秒) + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) diff --git a/library/think/cache/driver/Wincache.php b/library/think/cache/driver/Wincache.php index 25af8358..86de9cfe 100644 --- a/library/think/cache/driver/Wincache.php +++ b/library/think/cache/driver/Wincache.php @@ -77,7 +77,7 @@ class Wincache extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param integer|DateTime $expire 有效时间(秒) + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) diff --git a/library/think/cache/driver/Xcache.php b/library/think/cache/driver/Xcache.php index 58b9f232..131a22e1 100644 --- a/library/think/cache/driver/Xcache.php +++ b/library/think/cache/driver/Xcache.php @@ -75,7 +75,7 @@ class Xcache extends Driver * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 - * @param integer|DateTime $expire 有效时间(秒) + * @param integer|\DateTime $expire 有效时间(秒) * @return boolean */ public function set($name, $value, $expire = null) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index ca5b2c86..35677187 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1514,15 +1514,15 @@ class Query /** * 查询缓存 * @access public - * @param mixed $key 缓存key - * @param integer $expire 缓存有效期 - * @param string $tag 缓存标签 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 * @return $this */ public function cache($key = true, $expire = null, $tag = null) { // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) - if (is_numeric($key) && is_null($expire)) { + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { $expire = $key; $key = true; } -- Gitee From c7755d41cd8f84e0b7aa9a27a13d897274f984f9 Mon Sep 17 00:00:00 2001 From: rozbo <75200306@qq.com> Date: Sat, 2 Sep 2017 12:04:34 +0800 Subject: [PATCH 0494/1384] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dinsert=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=9A=84sql=E8=AF=AD=E5=8F=A5=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E3=80=82=20=E4=BF=AE=E5=A4=8D=E6=A8=A1=E5=9E=8B=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=A1=A8=E5=89=8D=E7=BC=80=E6=97=A0=E6=95=88?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 ++ library/think/db/Query.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 333d7992..f6226fe4 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -838,6 +838,8 @@ abstract class Builder $value = array_values($data); $values[] = 'SELECT ' . implode(',', $value); } + //上面使用的同名变量,这里先释放掉 + unset($fields); foreach (array_keys(reset($dataSet)) as $field) { $fields[] = $this->parseKey($query, $field); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 35677187..b73ea7f9 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -143,7 +143,7 @@ class Query public function setConnection(Connection $connection) { $this->connection = $connection; - + $this->prefix = $this->connection->getConfig('prefix'); return $this; } @@ -217,7 +217,7 @@ class Query public function connect($config = [], $name = false) { $this->connection = Connection::instance($config, $name); - + $this->prefix = $this->connection->getConfig('prefix'); return $this; } -- Gitee From c1663e056cdcacf7c6af3ef7e077175ea3d71d2f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 3 Sep 2017 19:50:58 +0800 Subject: [PATCH 0495/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E7=B1=BB=E5=AF=B9swf=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/File.php b/library/think/File.php index c3248e71..c0db372d 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -243,7 +243,7 @@ class File extends SplFileObject $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); /* 对图像文件进行严格检测 */ - if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6])) { + if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) { return false; } -- Gitee From cc1f8319ebf64d630f0217f64974bdd3f99e16da Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Sep 2017 11:11:18 +0800 Subject: [PATCH 0496/1384] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 3 +-- library/think/db/Query.php | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index f6226fe4..cc37f05d 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -838,9 +838,8 @@ abstract class Builder $value = array_values($data); $values[] = 'SELECT ' . implode(',', $value); } - //上面使用的同名变量,这里先释放掉 - unset($fields); + $fields = []; foreach (array_keys(reset($dataSet)) as $field) { $fields[] = $this->parseKey($query, $field); } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index b73ea7f9..03fb8433 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -143,7 +143,8 @@ class Query public function setConnection(Connection $connection) { $this->connection = $connection; - $this->prefix = $this->connection->getConfig('prefix'); + $this->prefix = $this->connection->getConfig('prefix'); + return $this; } @@ -217,7 +218,8 @@ class Query public function connect($config = [], $name = false) { $this->connection = Connection::instance($config, $name); - $this->prefix = $this->connection->getConfig('prefix'); + $this->prefix = $this->connection->getConfig('prefix'); + return $this; } -- Gitee From b10c2e4a17ed159776577e52406ee5b561520463 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Sep 2017 11:40:59 +0800 Subject: [PATCH 0497/1384] =?UTF-8?q?Controller=E7=B1=BB=E7=9A=84=5Finitia?= =?UTF-8?q?lize=E6=96=B9=E6=B3=95=E6=9B=B4=E6=94=B9=E4=B8=BAinitialize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index 0355b150..5bc73f6d 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -61,7 +61,7 @@ class Controller $this->app = $app; // 控制器初始化 - $this->_initialize(); + $this->initialize(); // 前置操作方法 if ($this->beforeActionList) { @@ -74,7 +74,7 @@ class Controller } // 初始化 - protected function _initialize() + protected function initialize() {} /** -- Gitee From 9383037d9ac74bbd810a183b9b4e7b1dd796fcb9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Sep 2017 19:10:44 +0800 Subject: [PATCH 0498/1384] =?UTF-8?q?Query=E7=B1=BB=E5=A2=9E=E5=8A=A0curso?= =?UTF-8?q?r=E6=96=B9=E6=B3=95=E4=BD=BF=E7=94=A8=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=99=A8=E8=BF=94=E5=9B=9E=E6=95=B0=E6=8D=AE=20=E4=BE=BF?= =?UTF-8?q?=E4=BA=8E=E5=A4=A7=E9=87=8F=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 90 +++++++++++++++++++++++++++++++++ library/think/db/Query.php | 32 ++++++++++-- 2 files changed, 118 insertions(+), 4 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 66d0a44e..14fef044 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -515,6 +515,73 @@ abstract class Connection } } + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param Model $model 模型对象实例 + * @param array $condition 查询条件 + * @param mixed $relation 关联查询 + * @return \Generator + */ + public function getCursor($sql, $bind = [], $master = false, $model = null, $condition = null, $relation = null) + { + $this->initConnect($master); + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + // 释放前次的查询结果 + if (!empty($this->PDOStatement)) { + $this->free(); + } + + Db::$queryTimes++; + + // 调试开始 + $this->debug(true); + + // 预处理 + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + $instance = $model->newInstance($result, $condition); + + if ($relation) { + $instance->relationQuery($relation); + } + + yield $instance; + } else { + yield $result; + } + } + } + /** * 执行查询 返回数据集 * @access public @@ -746,6 +813,29 @@ abstract class Connection return $result; } + /** + * 使用游标查询记录 + * @access public + * @param Query $query 查询对象 + * @return \Generator + */ + public function cursor(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + $condition = isset($options['where']['AND']) ? $options['where']['AND'] : null; + $relation = isset($options['relaltion']) ? $options['relation'] : null; + + // 执行查询操作 + return $this->getCursor($sql, $bind, $options['master'], $query->getModel(), $condition, $relation); + } + /** * 查找记录 * @access public diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 03fb8433..370cf376 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2195,6 +2195,33 @@ class Query return $this->connection->pdo($this); } + /** + * 使用游标查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return \Generator + */ + public function cursor($data = null) + { + if ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + /** * 查找记录 * @access public @@ -2213,10 +2240,7 @@ class Query $this->parseOptions(); - if (false === $data) { - // 用于子查询 不查询只返回SQL - $this->options['fetch_sql'] = true; - } elseif (!is_null($data)) { + if (!is_null($data)) { // 主键条件分析 $this->parsePkWhere($data); } -- Gitee From f33d446da973cbb4e2fda5dd62dfd5c6b59476bc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 10:30:56 +0800 Subject: [PATCH 0499/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E5=85=B3=E8=81=94=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/SoftDelete.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index e41d4aed..07d4ed8c 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -70,13 +70,27 @@ trait SoftDelete if (!$force) { // 软删除 $this->data($name, $this->autoWriteTimestamp($name)); + $result = $this->isUpdate()->save(); } else { - $result = $this->db(false)->delete($this->getData()); + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db(false)->where($where)->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); } $this->trigger('after_delete', $this); + // 清空数据 + $this->data = []; + $this->origin = []; + return $result; } -- Gitee From ead259157f03d88600a0ce265b3bad9a1047678a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 11:52:58 +0800 Subject: [PATCH 0500/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcache=E7=B1=BB?= =?UTF-8?q?=E7=9A=84tag=E7=BC=93=E5=AD=98=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/Driver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index abac0f16..873da4c7 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -182,7 +182,7 @@ abstract class Driver $value = array_unique(array_merge($this->getTagItem($name), $keys)); } - $this->set($key, implode(',', $value)); + $this->set($key, implode(',', $value), 0); } return $this; @@ -206,7 +206,7 @@ abstract class Driver } else { $value = $name; } - $this->set($key, $value); + $this->set($key, $value, 0); } } -- Gitee From 410e143f9bdb7e12fc00cb2acee6707dd782044e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 12:10:33 +0800 Subject: [PATCH 0501/1384] =?UTF-8?q?mysql=E7=9A=84json=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=94=B9=E4=B8=BA->?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 417c5d23..d071051a 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -32,9 +32,9 @@ 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); + list($field, $name) = explode('->', $key); $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); -- Gitee From bf316ceec6096474db2d363cc46b1e29f7012d6b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 12:49:23 +0800 Subject: [PATCH 0502/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 370cf376..62861fa3 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2240,7 +2240,10 @@ class Query $this->parseOptions(); - if (!is_null($data)) { + if (false === $data) { + // 用于子查询 不查询只返回SQL + $this->options['fetch_sql'] = true; + } elseif (!is_null($data)) { // 主键条件分析 $this->parsePkWhere($data); } -- Gitee From f43688a30ce921df1c7cda771620c0fbe1868e7d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 14:16:36 +0800 Subject: [PATCH 0503/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBfil?= =?UTF-8?q?terExp=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index ca61d4c2..b95c5cd7 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1147,7 +1147,7 @@ class Request public function filterExp(&$value) { // 过滤查询特殊字符 - if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { + if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { $value .= ' '; } // TODO 其他安全过滤 -- Gitee From c6c0c8a4565a72cacb7831fe54f0720ff73c283d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 15:13:07 +0800 Subject: [PATCH 0504/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E7=B1=BB=E5=92=8C=E6=95=B0=E6=8D=AE=E9=9B=86=E7=B1=BB=E7=9A=84?= =?UTF-8?q?each=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Collection.php | 6 +++++- library/think/Paginator.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/library/think/Collection.php b/library/think/Collection.php index da9c5004..a1252d8c 100644 --- a/library/think/Collection.php +++ b/library/think/Collection.php @@ -189,8 +189,12 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria public function each(callable $callback) { foreach ($this->items as $key => $item) { - if ($callback($item, $key) === false) { + $result = $callback($item, $key); + + if (false === $result) { break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; } } diff --git a/library/think/Paginator.php b/library/think/Paginator.php index f8ae5ea5..0201d6b2 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -288,8 +288,12 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J public function each(callable $callback) { foreach ($this->items as $key => $item) { - if ($callback($item, $key) === false) { + $result = $callback($item, $key); + + if (false === $result) { break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; } } -- Gitee From bb79f66d68e203170f823e18f0b759148ebd9eda Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 16:47:47 +0800 Subject: [PATCH 0505/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bsqlsrv=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Sqlsrv.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index d76a7b19..75fa9e4b 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -44,6 +44,8 @@ class Sqlsrv extends Builder $array[] = $this->parseKey($query, $val); } elseif ('[rand]' == $val) { $array[] = $this->parseRand($query); + } else { + $array[] = $val; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; -- Gitee From e5a3beaaff21a30a06c843403810f76d53026f1a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 17:36:35 +0800 Subject: [PATCH 0506/1384] =?UTF-8?q?lock=E6=96=B9=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BC=A0=E5=85=A5=E5=AD=97=E7=AC=A6=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 10 +++++++--- library/think/db/Query.php | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index cc37f05d..5976c15b 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -725,13 +725,17 @@ abstract class Builder /** * 设置锁机制 * @access protected - * @param Query $query 查询对象 - * @param bool $lock + * @param Query $query 查询对象 + * @param bool|string $lock * @return string */ protected function parseLock(Query $query, $lock = false) { - return $lock ? ' FOR UPDATE ' : ''; + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } } /** diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 62861fa3..ab7bbebd 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -922,6 +922,23 @@ class Query return $this; } + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('AND', $field, $op, $condition, $param); + + return $this; + } + /** * 指定OR查询条件 * @access public @@ -1565,7 +1582,7 @@ class Query /** * 指定查询lock * @access public - * @param boolean $lock 是否lock + * @param bool|string $lock 是否lock * @return $this */ public function lock($lock = false) -- Gitee From 2ec37fb75b9947bca5eb1905822e5d522da2bdd5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 18:09:00 +0800 Subject: [PATCH 0507/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=A4=9A=E6=AC=A1=E6=9F=A5=E8=AF=A2=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index ab7bbebd..5bbf2469 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -922,23 +922,6 @@ class Query return $this; } - /** - * 指定AND查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @return $this - */ - public function where($field, $op = null, $condition = null) - { - $param = func_get_args(); - array_shift($param); - $this->parseWhereExp('AND', $field, $op, $condition, $param); - - return $this; - } - /** * 指定OR查询条件 * @access public @@ -1216,20 +1199,22 @@ class Query $where[$field] = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { // null查询 - $where[$field] = [$op, '']; + $where[$field] = [$op, '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } elseif (is_null($condition)) { // 字段相等查询 $where[$field] = ['eq', $op]; - if ('AND' != $logic) { - $this->options['multi'][$logic][$field][] = $where[$field]; - } + + $this->options['multi'][$logic][$field][] = $where[$field]; } else { $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null]; + if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { // 参数绑定 $this->bind($param[2]); } + // 记录一个字段多次查询条件 $this->options['multi'][$logic][$field][] = $where[$field]; } -- Gitee From 4c194faf1d03998149252a9f22e2e9bb597a35bb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 19:13:50 +0800 Subject: [PATCH 0508/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=20where(?= =?UTF-8?q?['id'=3D>1,'name'=3D>['like','think']])=20=E6=94=B9=E4=B8=BAwhe?= =?UTF-8?q?re([['id','=3D',1],['name','like','think']])?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 16 +-------- library/think/db/Builder.php | 14 +++++--- library/think/db/Query.php | 65 +++++++++--------------------------- 3 files changed, 26 insertions(+), 69 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index b95c5cd7..e6702389 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1136,21 +1136,7 @@ class Request } } - return $this->filterExp($value); - } - - /** - * 过滤表单中的表达式 - * @param string $value - * @return void - */ - public function filterExp(&$value) - { - // 过滤查询特殊字符 - if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { - $value .= ' '; - } - // TODO 其他安全过滤 + return $value; } /** diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 5976c15b..34a79358 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -263,16 +263,20 @@ abstract class Builder $where = []; } - if ($where instanceof Query) { - return $this->buildWhere($query, $where->getOptions('where')); - } - $whereStr = ''; $binds = $this->connection->getFieldsBind($query->getOptions('table')); foreach ($where as $key => $val) { $str = []; - foreach ($val as $field => $value) { + + foreach ($val as $value) { + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } + if ($value instanceof \Closure) { // 使用闭包查询 $newQuery = $query->newQuery()->setConnection($this->connection); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 5bbf2469..3e92cf67 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1168,17 +1168,14 @@ class Query { $logic = strtoupper($logic); - if ($field instanceof \Closure) { - $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; - return; - } - if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { $field = $this->options['via'] . '.' . $field; } - if (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { - $where[] = ['exp', $field]; + if ($field instanceof \Closure) { + $where = is_string($op) ? [$op, $field] : $field; + } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { + $where = ['', 'exp', $field]; if (is_array($op)) { // 参数绑定 $this->bind($op); @@ -1187,69 +1184,39 @@ class Query if (is_array($field)) { // 数组批量查询 $where = $field; - foreach ($where as $k => $val) { - $this->options['multi'][$logic][$k][] = $val; + if (isset($this->options['where'][$logic])) { + $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + } else { + $this->options['where'][$logic] = $where; } + return; } elseif ($field && is_string($field)) { // 字符串查询 - $where[$field] = ['null', '']; - $this->options['multi'][$logic][$field][] = $where[$field]; + $where = [$field, 'null', '']; } } elseif (is_array($op)) { - $where[$field] = $param; + array_unshift($param, $field); + $where = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { // null查询 - $where[$field] = [$op, '']; - - $this->options['multi'][$logic][$field][] = $where[$field]; + $where = [$field, $op, '']; } elseif (is_null($condition)) { // 字段相等查询 - $where[$field] = ['eq', $op]; - - $this->options['multi'][$logic][$field][] = $where[$field]; + $where = [$field, 'eq', $op]; } else { - $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null]; + $where = [$field, $op, $condition, isset($param[2]) ? $param[2] : null]; if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { // 参数绑定 $this->bind($param[2]); } - - // 记录一个字段多次查询条件 - $this->options['multi'][$logic][$field][] = $where[$field]; } if (!empty($where)) { - if (!isset($this->options['where'][$logic])) { - $this->options['where'][$logic] = []; - } - - if (is_string($field) && $this->checkMultiField($field, $logic)) { - $where[$field] = $this->options['multi'][$logic][$field]; - } elseif (is_array($field)) { - foreach ($field as $key => $val) { - if ($this->checkMultiField($key, $logic)) { - $where[$key] = $this->options['multi'][$logic][$key]; - } - } - } - - $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + $this->options['where'][$logic][] = $where; } } - /** - * 检查是否存在一个字段多次查询条件 - * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return bool - */ - private function checkMultiField($field, $logic) - { - return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; - } - /** * 去除某个查询条件 * @access public -- Gitee From bddf55ffbe94c3a5cbf822a940de32e47e7b3bf4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Sep 2017 23:46:57 +0800 Subject: [PATCH 0509/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4model=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index afd28f76..56cf4e88 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -369,10 +369,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $pk = $this->getPk(); if (is_string($pk) && isset($data[$pk])) { - if (!isset($where[$pk])) { - unset($where); - $where[$pk] = $data[$pk]; - } + unset($where); + $where[] = [$pk, '=', $data[$pk]]; unset($data[$pk]); } @@ -506,7 +504,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $pk = $this->getPk(); if (is_string($pk) && isset($this->data[$pk])) { - $where = [$pk => $this->data[$pk]]; + $where[] = [$pk, '=', $this->data[$pk]]; } elseif (!empty($this->updateWhere)) { $where = $this->updateWhere; } else { @@ -721,10 +719,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { $result = self::with($with)->cache($cache); - if (is_array($data) && key($data) !== 0) { - $result = $result->where($data); - $data = null; - } elseif ($data instanceof \Closure) { + if ($data instanceof \Closure) { $data($result); $data = null; } elseif ($data instanceof Query) { @@ -746,10 +741,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $model = new static(); $query = $model->db(); - if (is_array($data) && key($data) !== 0) { - $query->where($data); - $data = null; - } elseif ($data instanceof \Closure) { + + if ($data instanceof \Closure) { $data($query); $data = null; } elseif (empty($data) && 0 !== $data) { -- Gitee From 563caa3278239c72df042b6627c4c049b8d5f70b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 08:42:49 +0800 Subject: [PATCH 0510/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 56cf4e88..a52f5417 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -255,6 +255,23 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $result; } + /** + * 解析查询条件 + * @access protected + * @param array $where 保存条件 + * @return array + */ + protected function parseWhere($where) + { + if (key($where) !== 0) { + foreach ($where as $key => $val) { + $item[] = [$key, '=', $val]; + } + return $item; + } + return $where; + } + /** * 写入之前检查数据 * @access protected @@ -273,7 +290,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (!empty($where)) { $this->isUpdate = true; - $this->updateWhere = $where; + $this->updateWhere = $this->parseWhere($where); } } @@ -556,8 +573,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 是否为更新数据 * @access public - * @param bool $update - * @param mixed $where + * @param mixed $update + * @param mixed $where * @return $this */ public function isUpdate($update = true, $where = null) @@ -566,11 +583,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->isUpdate = $update; if (!empty($where)) { - $this->updateWhere = $where; + $this->updateWhere = $this->parseWhere($where); } } else { $this->isUpdate = true; - $this->updateWhere = $update; + $this->updateWhere = $this->parseWhere($update); } return $this; @@ -719,7 +736,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { $result = self::with($with)->cache($cache); - if ($data instanceof \Closure) { + if (is_array($data) && key($data) !== 0) { + $result = $result->where($this->parseWhere($data)); + $data = null; + } elseif ($data instanceof \Closure) { $data($result); $data = null; } elseif ($data instanceof Query) { @@ -742,7 +762,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query = $model->db(); - if ($data instanceof \Closure) { + if (is_array($data) && key($data) !== 0) { + $query->where($this->parseWhere($data)); + $data = null; + } elseif ($data instanceof \Closure) { $data($query); $data = null; } elseif (empty($data) && 0 !== $data) { -- Gitee From 7db9492c756258873750b9b40c8fa51830623db2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 10:31:18 +0800 Subject: [PATCH 0511/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Query=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3e92cf67..6553ae09 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -93,16 +93,12 @@ class Query return Container::getInstance()->invoke(self::$extend[strtolower($method)], $args); } elseif (strtolower(substr($method, 0, 5)) == 'getby') { // 根据某个字段获取记录 - $field = Loader::parseName(substr($method, 5)); - $where[$field] = $args[0]; - - return $this->where($where)->find(); + $field = Loader::parseName(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { // 根据某个字段获取记录的某个值 - $name = Loader::parseName(substr($method, 10)); - $where[$name] = $args[0]; - - return $this->where($where)->value($args[1]); + $name = Loader::parseName(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { // 动态调用命名范围 $method = 'scope' . $method; @@ -2486,7 +2482,7 @@ class Query if (isset($options['where'][$logic])) { foreach ($options['where'][$logic] as $key => $val) { if (array_key_exists($key, $options['map'])) { - $options['where'][$logic][$options['map'][$key]] = $val; + $options['where'][$logic][] = [$options['map'][$key], '=', $val]; unset($options['where'][$logic][$key]); } } @@ -2540,16 +2536,17 @@ class Query $key = isset($alias) ? $alias . '.' . $pk : $pk; // 根据主键查询 if (is_array($data)) { - $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; + + $where[] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; } else { - $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; + $where[] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; } } elseif (is_array($pk) && is_array($data) && !empty($data)) { // 根据复合主键查询 foreach ($pk as $key) { if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[$attr] = $data[$key]; + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[] = [$attr, '=', $data[$key]]; } else { throw new Exception('miss complex primary data'); } -- Gitee From a5de3db5a98f9369ff54905f4d290566ad86550f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 10:58:18 +0800 Subject: [PATCH 0512/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Relation.php | 4 +-- library/think/model/concern/SoftDelete.php | 2 +- library/think/model/relation/BelongsTo.php | 9 +++--- .../think/model/relation/BelongsToMany.php | 20 ++++++------- library/think/model/relation/HasMany.php | 20 +++++-------- library/think/model/relation/HasOne.php | 9 +++--- library/think/model/relation/MorphMany.php | 28 +++++++++---------- library/think/model/relation/MorphOne.php | 18 ++++++------ 8 files changed, 52 insertions(+), 58 deletions(-) diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index 5fb5d662..e8dc484e 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -96,8 +96,8 @@ abstract class Relation protected function getQueryWhere(&$where, $relation) { foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; + if (is_string($key) && false === strpos($key, '.')) { + $where[] = [$relation . '.' . $key, '=', $val]; unset($where[$key]); } } diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 07d4ed8c..b6afdc15 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -107,7 +107,7 @@ trait SoftDelete $query = self::withTrashed(); if (is_array($data) && key($data) !== 0) { - $query->where($data); + $query->where($this->parseWhere($data)); $data = null; } elseif ($data instanceof \Closure) { call_user_func_array($data, [ & $query]); diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 6272b668..37ed2554 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -126,10 +126,7 @@ class BelongsTo extends OneToOne if (!empty($range)) { $data = $this->eagerlyWhere($this, [ - $localKey => [ - 'in', - $range, - ], + [$localKey, 'in', $range], ], $localKey, $relation, $subRelation, $closure); // 关联属性名 @@ -170,7 +167,9 @@ class BelongsTo extends OneToOne { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere($this, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + $data = $this->eagerlyWhere($this, [ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $relation, $subRelation, $closure); // 关联模型 if (!isset($data[$result->$foreignKey])) { diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index 5c1bd139..0b470380 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -115,7 +115,7 @@ class BelongsToMany extends Relation // 关联查询 $pk = $this->parent->getPk(); - $condition['pivot.' . $localKey] = $this->parent->$pk; + $condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk]; return $this->belongsToManyQuery($foreignKey, $localKey, $condition); } @@ -271,10 +271,7 @@ class BelongsToMany extends Relation if (!empty($range)) { // 查询关联数据 $data = $this->eagerlyManyToMany([ - 'pivot.' . $localKey => [ - 'in', - $range, - ], + ['pivot.' . $localKey, 'in', $range], ], $relation, $subRelation); // 关联属性名 @@ -307,7 +304,9 @@ class BelongsToMany extends Relation 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); // 关联数据封装 if (!isset($data[$pk])) { @@ -332,7 +331,9 @@ class BelongsToMany extends Relation if (isset($result->$pk)) { $pk = $result->$pk; - $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->count(); } return $count; @@ -347,9 +348,8 @@ class BelongsToMany extends Relation public function getRelationCountQuery($closure) { return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ - 'pivot.' . $this->localKey => [ - 'exp', - '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + [ + 'pivot.' . $this->localKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), ], ])->fetchSql()->count(); } diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 27d1be2a..aeacaf0c 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -80,10 +80,7 @@ class HasMany extends Relation if (!empty($range)) { $data = $this->eagerlyOneToMany($this, [ - $this->foreignKey => [ - 'in', - $range, - ], + [$this->foreignKey, 'in', $range], ], $relation, $subRelation, $closure); // 关联属性名 @@ -120,7 +117,9 @@ class HasMany extends Relation if (isset($result->$localKey)) { $pk = $result->$localKey; - $data = $this->eagerlyOneToMany($this, [$this->foreignKey => $pk], $relation, $subRelation, $closure); + $data = $this->eagerlyOneToMany($this, [ + [$this->foreignKey, '=', $pk], + ], $relation, $subRelation, $closure); // 关联数据封装 if (!isset($data[$pk])) { @@ -152,7 +151,7 @@ class HasMany extends Relation call_user_func_array($closure, [ & $this->query]); } - $count = $this->query->where([$this->foreignKey => $result->$localKey])->count(); + $count = $this->query->where($this->foreignKey, '=', $result->$localKey)->count(); } return $count; @@ -171,12 +170,7 @@ class HasMany extends Relation } return $this->query - ->where([ - $this->foreignKey => [ - 'exp', - '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), - ], - ]) + ->where($this->foreignKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) ->fetchSql() ->count(); } @@ -305,7 +299,7 @@ class HasMany extends Relation if (empty($this->baseQuery)) { if (isset($this->parent->{$this->localKey})) { // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); } $this->baseQuery = true; diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 794b85ea..81a3bc28 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -132,10 +132,7 @@ class HasOne extends OneToOne if (!empty($range)) { $data = $this->eagerlyWhere($this, [ - $foreignKey => [ - 'in', - $range, - ], + [$foreignKey, 'in', $range], ], $foreignKey, $relation, $subRelation, $closure); // 关联属性名 @@ -176,7 +173,9 @@ class HasOne extends OneToOne { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere($this, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + $data = $this->eagerlyWhere($this, [ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $relation, $subRelation, $closure); // 关联模型 if (!isset($data[$result->$localKey])) { diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index 3064c009..b474deff 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -118,8 +118,8 @@ class MorphMany extends Relation if (!empty($range)) { $data = $this->eagerlyMorphToMany([ - $morphKey => ['in', $range], - $morphType => $type, + [$morphKey, 'in', $range], + [$morphType, '=', $type], ], $relation, $subRelation, $closure); // 关联属性名 @@ -157,8 +157,8 @@ class MorphMany extends Relation if (isset($result->$pk)) { $key = $result->$pk; $data = $this->eagerlyMorphToMany([ - $this->morphKey => $key, - $this->morphType => $this->type, + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], ], $relation, $subRelation, $closure); if (!isset($data[$key])) { @@ -192,7 +192,10 @@ class MorphMany extends Relation } $count = $this->query - ->where([$this->morphKey => $result->$pk, $this->morphType => $this->type]) + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) ->count(); } @@ -213,11 +216,8 @@ class MorphMany extends Relation return $this->query ->where([ - $this->morphKey => [ - 'exp', - '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), - ], - $this->morphType => $this->type, + [$this->morphKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()], + [$this->morphType, '=', $this->type], ]) ->fetchSql() ->count(); @@ -299,10 +299,10 @@ class MorphMany extends Relation 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); + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); $this->baseQuery = true; } diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index 018874dd..cf1ce9bb 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -116,8 +116,8 @@ class MorphOne extends Relation if (!empty($range)) { $data = $this->eagerlyMorphToOne([ - $morphKey => ['in', $range], - $morphType => $type, + [$morphKey, 'in', $range], + [$morphType, '=', $type], ], $relation, $subRelation, $closure); // 关联属性名 @@ -154,8 +154,8 @@ class MorphOne extends Relation if (isset($result->$pk)) { $pk = $result->$pk; $data = $this->eagerlyMorphToOne([ - $this->morphKey => $pk, - $this->morphType => $this->type, + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], ], $relation, $subRelation, $closure); if (isset($data[$pk])) { @@ -228,10 +228,12 @@ class MorphOne extends Relation protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); $this->baseQuery = true; } } -- Gitee From c6f61edd619e60d02496edc42cc8527ff8896fdf Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 11:01:48 +0800 Subject: [PATCH 0513/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index a52f5417..c17dcb0c 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -258,12 +258,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 解析查询条件 * @access protected - * @param array $where 保存条件 - * @return array + * @param array|null $where 保存条件 + * @return array|null */ protected function parseWhere($where) { - if (key($where) !== 0) { + if (is_array($where) && key($where) !== 0) { foreach ($where as $key => $val) { $item[] = [$key, '=', $val]; } -- Gitee From 29303f8fbb28a197c7498aed735f9e6a331611ea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 11:12:03 +0800 Subject: [PATCH 0514/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 1 + library/think/db/Query.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index c17dcb0c..441e0d6e 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -264,6 +264,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected function parseWhere($where) { if (is_array($where) && key($where) !== 0) { + $item = []; foreach ($where as $key => $val) { $item[] = [$key, '=', $val]; } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 6553ae09..7778ab25 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -578,6 +578,7 @@ class Query // 延迟写入 $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { // 清空查询条件 $this->options = []; @@ -2536,7 +2537,6 @@ class Query $key = isset($alias) ? $alias . '.' . $pk : $pk; // 根据主键查询 if (is_array($data)) { - $where[] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; } else { $where[] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; -- Gitee From cf5f3542d9f9ba533a9633475260db67d7ba2df0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 11:15:07 +0800 Subject: [PATCH 0515/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Relation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index e8dc484e..b26692ac 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -96,8 +96,8 @@ abstract class Relation protected function getQueryWhere(&$where, $relation) { foreach ($where as $key => $val) { - if (is_string($key) && false === strpos($key, '.')) { - $where[] = [$relation . '.' . $key, '=', $val]; + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; unset($where[$key]); } } -- Gitee From 108df9d9d30220f50da9f99389ad1fbafef56751 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 14:25:46 +0800 Subject: [PATCH 0516/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BBuilder=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 34a79358..fb10e842 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -266,7 +266,7 @@ abstract class Builder $whereStr = ''; $binds = $this->connection->getFieldsBind($query->getOptions('table')); - foreach ($where as $key => $val) { + foreach ($where as $logic => $val) { $str = []; foreach ($val as $value) { @@ -275,6 +275,8 @@ abstract class Builder throw new Exception('where express error:' . var_export($value, true)); } $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); } if ($value instanceof \Closure) { @@ -284,7 +286,7 @@ abstract class Builder $whereClause = $this->buildWhere($query, $newQuery->getOptions('where')); if (!empty($whereClause)) { - $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; } } elseif (strpos($field, '|')) { // 不同字段使用相同查询条件(OR) @@ -295,7 +297,7 @@ abstract class Builder $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); } - $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + $str[] = ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; } elseif (strpos($field, '&')) { // 不同字段使用相同查询条件(AND) $array = explode('&', $field); @@ -305,15 +307,15 @@ abstract class Builder $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); } - $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; } else { // 对字段使用表达式查询 $field = is_string($field) ? $field : ''; - $str[] = ' ' . $key . ' ' . $this->parseWhereItem($query, $field, $value, $key, $binds); + $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds); } } - $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); } return $whereStr; -- Gitee From d161bac226711957b931b1649004a8fd7eda1ff0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 15:39:09 +0800 Subject: [PATCH 0517/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bin=E5=92=8Cbetween?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=9A=84=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 64 ++++++++++++++++-------------------- library/think/db/Query.php | 18 ---------- 2 files changed, 29 insertions(+), 53 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index fb10e842..0fa6931a 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -422,53 +422,47 @@ abstract class Builder $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($query, $value); } else { $value = array_unique(is_array($value) ? $value : explode(',', $value)); - if (array_key_exists($field, $binds)) { - $bind = []; - $array = []; - $i = 0; - foreach ($value as $k => $v) { - $i++; - if ($query->isBind($bindName . '_in_' . $i)) { - $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; - } else { - $bindKey = $bindName . '_in_' . $i; - } - $bind[$bindKey] = [$v, $bindType]; - $array[] = ':' . $bindKey; - } - $query->bind($bind); - $zone = implode(',', $array); - } else { - $zone = implode(',', $this->parseValue($query, $value, $field)); + $bind = []; + $array = []; + $i = 0; + + foreach ($value as $k => $v) { + $i++; + if ($query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; } + $zone = implode(',', $array); + $query->bind($bind); + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; } } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { // BETWEEN 查询 $data = is_array($value) ? $value : explode(',', $value); - if (array_key_exists($field, $binds)) { - if ($query->isBind($bindName . '_between_1')) { - $bindKey1 = $bindName . '_between_1' . uniqid(); - $bindKey2 = $bindName . '_between_2' . uniqid(); - } else { - $bindKey1 = $bindName . '_between_1'; - $bindKey2 = $bindName . '_between_2'; - } + if ($query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } - $bind = [ - $bindKey1 => [$data[0], $bindType], - $bindKey2 => [$data[1], $bindType], - ]; + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; - $query->bind($bind); + $query->bind($bind); - $between = ':' . $bindKey1 . ' AND :' . $bindKey2; - } else { - $between = $this->parseValue($query, $data[0], $field) . ' AND ' . $this->parseValue($query, $data[1], $field); - } + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; $whereStr .= $key . ' ' . $exp . ' ' . $between; } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 7778ab25..42b2e73f 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1214,24 +1214,6 @@ class Query } } - /** - * 去除某个查询条件 - * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function removeWhereField($field, $logic = 'AND') - { - $logic = strtoupper($logic); - - if (isset($this->options['where'][$logic][$field])) { - unset($this->options['where'][$logic][$field]); - } - - return $this; - } - /** * 去除查询参数 * @access public -- Gitee From 8fb98e157b9558fbefeb126dc54413c1cf96d077 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 15:47:59 +0800 Subject: [PATCH 0518/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Query.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 441e0d6e..50f9472e 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -266,7 +266,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_array($where) && key($where) !== 0) { $item = []; foreach ($where as $key => $val) { - $item[] = [$key, '=', $val]; + $item[] = [$key, '=', (string) $val]; } return $item; } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 42b2e73f..f1caee21 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1179,8 +1179,16 @@ class Query } } elseif (is_null($op) && is_null($condition)) { if (is_array($field)) { - // 数组批量查询 - $where = $field; + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + $where[] = [$key, '=', (string) $val]; + } + } else { + // 数组批量查询 + $where = $field; + } + if (isset($this->options['where'][$logic])) { $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); } else { -- Gitee From d45fd1ff5b41431b99b553051933ac715a1243b0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Sep 2017 15:51:58 +0800 Subject: [PATCH 0519/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bwhere=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E8=A7=A3=E6=9E=90=E4=B8=A5=E8=B0=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Builder.php | 4 ++++ library/think/db/Query.php | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 50f9472e..441e0d6e 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -266,7 +266,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_array($where) && key($where) !== 0) { $item = []; foreach ($where as $key => $val) { - $item[] = [$key, '=', (string) $val]; + $item[] = [$key, '=', $val]; } return $item; } diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 0fa6931a..76c1a244 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -392,6 +392,10 @@ abstract class Builder $whereStr = ''; if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + // 比较运算 if ($value instanceof \Closure) { $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($query, $value); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index f1caee21..17a26020 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1182,7 +1182,7 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - $where[] = [$key, '=', (string) $val]; + $where[] = [$key, '=', $val]; } } else { // 数组批量查询 -- Gitee From 6a681b0db70346134a8e0816f7451939e65354ba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 10:55:54 +0800 Subject: [PATCH 0520/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BhasMany=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=9A=84has=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/HasMany.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index aeacaf0c..a1b9ccc5 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -254,12 +254,15 @@ class HasMany extends Relation */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { - $table = $this->query->getTable(); + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); return $this->parent->db() - ->alias('a') - ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $joinType) - ->group('b.' . $this->foreignKey) + ->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) ->having('count(' . $id . ')' . $operator . $count); } -- Gitee From 44856d9d072605871e5aa842e59d36d96cf2eebc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 11:24:31 +0800 Subject: [PATCH 0521/1384] =?UTF-8?q?=E5=85=B3=E8=81=94=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/HasMany.php | 1 - library/think/model/relation/HasOne.php | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index a1b9ccc5..9a3dda4a 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -260,7 +260,6 @@ class HasMany extends Relation return $this->parent->db() ->alias($model) - ->field($model . '.*') ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) ->group($relation . '.' . $this->foreignKey) ->having('count(' . $id . ')' . $operator . $count); diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 81a3bc28..d270c00b 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -70,15 +70,17 @@ class HasOne extends OneToOne public function has() { $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); $localKey = $this->localKey; $foreignKey = $this->foreignKey; return $this->parent->db() - ->alias('a') - ->whereExists(function ($query) use ($table, $localKey, $foreignKey) { - $query->table([$table => 'b']) - ->field('b.' . $foreignKey) - ->whereExp('a.' . $localKey, '=b.' . $foreignKey); + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); }); } -- Gitee From 101a89158299afeaeffc9e0984302931e5f768b6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 11:35:24 +0800 Subject: [PATCH 0522/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3whereExists?= =?UTF-8?q?=E5=92=8CwhereNotExists=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 17a26020..540058d9 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -990,7 +990,7 @@ class Query */ public function whereExists($condition, $logic = 'AND') { - $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + $this->options['where'][strtoupper($logic)][] = ['', 'exists', $condition]; return $this; } @@ -1004,7 +1004,7 @@ class Query */ public function whereNotExists($condition, $logic = 'AND') { - $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + $this->options['where'][strtoupper($logic)][] = ['', 'not exists', $condition]; return $this; } -- Gitee From 8bc85b8c88d81d64345d44beb045d97cce8496e1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 11:48:23 +0800 Subject: [PATCH 0523/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BB?= =?UTF-8?q?=E7=9A=84group=E6=96=B9=E6=B3=95=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- library/think/model/relation/HasMany.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 76c1a244..dfe64d98 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -640,7 +640,7 @@ abstract class Builder */ protected function parseGroup(Query $query, $group) { - return !empty($group) ? ' GROUP BY ' . $group : ''; + return !empty($group) ? ' GROUP BY ' . $this->parseKey($query, $group) : ''; } /** diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 9a3dda4a..a1b9ccc5 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -260,6 +260,7 @@ class HasMany extends Relation return $this->parent->db() ->alias($model) + ->field($model . '.*') ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) ->group($relation . '.' . $this->foreignKey) ->having('count(' . $id . ')' . $operator . $count); -- Gitee From 1d9a2997538b30ccc99c9d6d021a7af6598cfcb8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 11:59:33 +0800 Subject: [PATCH 0524/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index 5c172bfe..d71d0004 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -124,14 +124,14 @@ trait RelationShip * @param mixed $operator 比较操作符 * @param integer $count 个数 * @param string $id 关联表的统计字段 - * @return Relation|Query + * @return Query */ public static function has($relation, $operator = '>=', $count = 1, $id = '*') { $relation = (new static())->$relation(); if (is_array($operator) || $operator instanceof \Closure) { - return $relation->hasWhere($operator, $count); + return $relation->hasWhere($operator); } return $relation->has($operator, $count, $id); @@ -143,7 +143,7 @@ trait RelationShip * @param string $relation 关联方法名 * @param mixed $where 查询条件(数组或者闭包) * @param mixed $fields 字段 - * @return Relation|Query + * @return Query */ public static function hasWhere($relation, $where = [], $fields = '*') { -- Gitee From b70ef6568ad9f639562a6f96901c0a12d87aa72d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 14:55:39 +0800 Subject: [PATCH 0525/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3update=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E4=BC=A0=E5=85=A5=E4=B8=BB=E9=94=AE=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 13 ++++++++----- library/think/db/Query.php | 30 ++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 14fef044..1e92022b 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -11,6 +11,7 @@ namespace think\db; +use InvalidArgumentException; use PDO; use PDOStatement; use think\Container; @@ -166,7 +167,7 @@ abstract class Connection $options = self::parseConfig($config); if (empty($options['type'])) { - throw new \InvalidArgumentException('Undefined db type'); + throw new InvalidArgumentException('Undefined db type'); } $class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']); @@ -1023,7 +1024,7 @@ abstract class Connection if (empty($options['where'])) { // 如果存在主键数据 则自动作为更新条件 if (is_string($pk) && isset($data[$pk])) { - $where[$pk] = $data[$pk]; + $where[$pk] = [$pk, '=', $data[$pk]]; if (!isset($key)) { $key = 'think:' . $options['table'] . '|' . $data[$pk]; } @@ -1032,7 +1033,7 @@ abstract class Connection // 增加复合主键支持 foreach ($pk as $field) { if (isset($data[$field])) { - $where[$field] = $data[$field]; + $where[$field] = [$field, '=', $data[$field]]; } else { // 如果缺少复合主键数据则不执行 throw new Exception('miss complex primary data'); @@ -1052,9 +1053,11 @@ abstract class Connection $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $query->getBind(false)); } - // 生成UPDATE SQL语句 - $sql = $this->builder->update($query); + // 更新数据 + $query->setOption('data', $data); + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); $bind = $query->getBind(); if ($options['fetch_sql']) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 540058d9..831a0c10 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1182,7 +1182,7 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - $where[] = [$key, '=', $val]; + $where[$key] = [$key, '=', $val]; } } else { // 数组批量查询 @@ -1218,10 +1218,28 @@ class Query } if (!empty($where)) { - $this->options['where'][$logic][] = $where; + $this->options['where'][$logic][$field] = $where; } } + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + } + + return $this; + } + /** * 去除查询参数 * @access public @@ -2527,16 +2545,16 @@ class Query $key = isset($alias) ? $alias . '.' . $pk : $pk; // 根据主键查询 if (is_array($data)) { - $where[] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; } else { - $where[] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; } } elseif (is_array($pk) && is_array($data) && !empty($data)) { // 根据复合主键查询 foreach ($pk as $key) { if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[] = [$attr, '=', $data[$key]]; + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$key] = [$attr, '=', $data[$key]]; } else { throw new Exception('miss complex primary data'); } -- Gitee From d5364c03a778e8de72daaad64388f4b57aa3ccc6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Sep 2017 15:07:07 +0800 Subject: [PATCH 0526/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 831a0c10..398cc3a7 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1218,7 +1218,11 @@ class Query } if (!empty($where)) { - $this->options['where'][$logic][$field] = $where; + if (isset($this->options['where'][$logic][$field])) { + $this->options['where'][$logic][] = $where; + } else { + $this->options['where'][$logic][$field] = $where; + } } } -- Gitee From 941eb6ac6a9183b4a1a3e87c2f8bf4edf336cca7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Sep 2017 14:36:04 +0800 Subject: [PATCH 0527/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 096d5894..8105aed9 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\exception\HttpResponseException; */ class App implements \ArrayAccess { - const VERSION = '5.1.0beta'; + const VERSION = '5.1.0RC1'; /** * @var string 当前模块路径 -- Gitee From 2a8b03c5a79e6c9efdc25e78b8a51977aa9df37b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Sep 2017 14:42:42 +0800 Subject: [PATCH 0528/1384] =?UTF-8?q?=E6=9B=B4=E6=96=B0readme=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c72896fe..65f5890e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ThinkPHP 5.1Beta +ThinkPHP 5.1RC1 =============== ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: -- Gitee From 4905dd71c024f89fd64681edb7b95c999c23e676 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Sep 2017 16:20:21 +0800 Subject: [PATCH 0529/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 441e0d6e..3ce37831 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -261,7 +261,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param array|null $where 保存条件 * @return array|null */ - protected function parseWhere($where) + protected static function parseWhere($where) { if (is_array($where) && key($where) !== 0) { $item = []; @@ -291,7 +291,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (!empty($where)) { $this->isUpdate = true; - $this->updateWhere = $this->parseWhere($where); + $this->updateWhere = self::parseWhere($where); } } @@ -584,11 +584,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->isUpdate = $update; if (!empty($where)) { - $this->updateWhere = $this->parseWhere($where); + $this->updateWhere = self::parseWhere($where); } } else { $this->isUpdate = true; - $this->updateWhere = $this->parseWhere($update); + $this->updateWhere = self::parseWhere($update); } return $this; @@ -738,7 +738,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $result = self::with($with)->cache($cache); if (is_array($data) && key($data) !== 0) { - $result = $result->where($this->parseWhere($data)); + $result = $result->where(self::parseWhere($data)); $data = null; } elseif ($data instanceof \Closure) { $data($result); @@ -764,7 +764,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query = $model->db(); if (is_array($data) && key($data) !== 0) { - $query->where($this->parseWhere($data)); + $query->where(self::parseWhere($data)); $data = null; } elseif ($data instanceof \Closure) { $data($query); -- Gitee From ec342f1c08eba2020e71679ca660b251cf96a1ff Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 9 Sep 2017 08:55:26 +0800 Subject: [PATCH 0530/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=A9=BA=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E6=95=B0=E7=BB=84=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 398cc3a7..03c64b2c 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1189,11 +1189,14 @@ class Query $where = $field; } - if (isset($this->options['where'][$logic])) { - $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); - } else { - $this->options['where'][$logic] = $where; + if (!empty($where)) { + if (isset($this->options['where'][$logic])) { + $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + } else { + $this->options['where'][$logic] = $where; + } } + return; } elseif ($field && is_string($field)) { // 字符串查询 -- Gitee From 5cb9cebdb66d8374bf24a1bc4c653a517629db15 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 9 Sep 2017 09:25:20 +0800 Subject: [PATCH 0531/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 03c64b2c..73ec83f8 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1210,7 +1210,7 @@ class Query $where = [$field, $op, '']; } elseif (is_null($condition)) { // 字段相等查询 - $where = [$field, 'eq', $op]; + $where = [$field, '=', $op]; } else { $where = [$field, $op, $condition, isset($param[2]) ? $param[2] : null]; @@ -2498,7 +2498,9 @@ class Query if (isset($options['where'][$logic])) { foreach ($options['where'][$logic] as $key => $val) { if (array_key_exists($key, $options['map'])) { - $options['where'][$logic][] = [$options['map'][$key], '=', $val]; + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; unset($options['where'][$logic][$key]); } } -- Gitee From 60a1d87151a6e106ba753c7ccd09eb89d28cf379 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 9 Sep 2017 21:11:51 +0800 Subject: [PATCH 0532/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index dd74a171..93adab0b 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -39,7 +39,7 @@ class Resource extends RuleGroup $this->parent = $group; $this->resource = $name; $this->route = $route; - $this->name = strpos($rule, '.') ? strstr($rule, '.', true) : $rule; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; // 资源路由默认为完整匹配 $option['complete_match'] = true; -- Gitee From aa72fe227a6230ddb3468e10512f672efdb88aa8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Sep 2017 08:31:13 +0800 Subject: [PATCH 0533/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3HasMany=E5=85=B3?= =?UTF-8?q?=E8=81=94=20=E4=BF=AE=E6=AD=A3where=E6=96=B9=E6=B3=95=E7=9A=84?= =?UTF-8?q?=E9=97=AD=E5=8C=85=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 1 + library/think/model/relation/HasMany.php | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 73ec83f8..c1eb4a8d 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1171,6 +1171,7 @@ class Query if ($field instanceof \Closure) { $where = is_string($op) ? [$op, $field] : $field; + $field = ''; } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { $where = ['', 'exp', $field]; if (is_array($op)) { diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index a1b9ccc5..1d8418bf 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -47,7 +47,11 @@ class HasMany extends Relation call_user_func_array($closure, [ & $this->query]); } - $list = $this->query->relation($subRelation)->select(); + $list = $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select(); + $parent = clone $this->parent; foreach ($list as &$model) { -- Gitee From 848b42a584bbfb922ad0654d453b284a085e27a4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Sep 2017 12:43:17 +0800 Subject: [PATCH 0534/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Relation.php | 3 ++- library/think/model/relation/HasManyThrough.php | 2 +- library/think/model/relation/MorphMany.php | 2 +- library/think/model/relation/MorphOne.php | 2 +- library/think/model/relation/MorphTo.php | 7 ------- library/think/model/relation/OneToOne.php | 7 ------- 6 files changed, 5 insertions(+), 18 deletions(-) diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index b26692ac..f6a1e9b5 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -108,7 +108,8 @@ abstract class Relation * @access protected * @return void */ - abstract protected function baseQuery(); + protected function baseQuery() + {} public function __call($method, $args) { diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php index c71d20c9..c7a1bc40 100644 --- a/library/think/model/relation/HasManyThrough.php +++ b/library/think/model/relation/HasManyThrough.php @@ -57,7 +57,7 @@ class HasManyThrough extends Relation call_user_func_array($closure, [ & $this->query]); } - return $this->query->relation($subRelation)->select(); + return $this->relation($subRelation)->select(); } /** diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index b474deff..dfe32c54 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -56,7 +56,7 @@ class MorphMany extends Relation call_user_func_array($closure, [ & $this->query]); } - $list = $this->query->relation($subRelation)->select(); + $list = $this->relation($subRelation)->select(); $parent = clone $this->parent; foreach ($list as &$model) { diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index cf1ce9bb..99f1da69 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -55,7 +55,7 @@ class MorphOne extends Relation if ($closure) { call_user_func_array($closure, [ & $this->query]); } - $relationModel = $this->query->relation($subRelation)->find(); + $relationModel = $this->relation($subRelation)->find(); if ($relationModel) { $relationModel->setParent(clone $this->parent); diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 9e864406..365a832b 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -286,11 +286,4 @@ class MorphTo extends Relation return $this->parent->setRelation($this->relation, null); } - /** - * 执行基础查询(仅执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - {} } diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 7c840e59..07b92d5c 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -325,11 +325,4 @@ abstract class OneToOne extends Relation return $data; } - /** - * 执行基础查询(仅执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - {} } -- Gitee From c8e0fd4c40880b7f7ba2dd14fdbe076d8425c94b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Sep 2017 15:21:18 +0800 Subject: [PATCH 0535/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/HasManyThrough.php | 4 +++- library/think/model/relation/MorphMany.php | 4 +++- library/think/model/relation/MorphOne.php | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php index c7a1bc40..66912c26 100644 --- a/library/think/model/relation/HasManyThrough.php +++ b/library/think/model/relation/HasManyThrough.php @@ -57,7 +57,9 @@ class HasManyThrough extends Relation call_user_func_array($closure, [ & $this->query]); } - return $this->relation($subRelation)->select(); + $this->baseQuery(); + + return $this->query->relation($subRelation)->select(); } /** diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index dfe32c54..833431b2 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -56,7 +56,9 @@ class MorphMany extends Relation call_user_func_array($closure, [ & $this->query]); } - $list = $this->relation($subRelation)->select(); + $this->baseQuery(); + + $list = $this->query->relation($subRelation)->select(); $parent = clone $this->parent; foreach ($list as &$model) { diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index 99f1da69..cd785c5e 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -55,7 +55,10 @@ class MorphOne extends Relation if ($closure) { call_user_func_array($closure, [ & $this->query]); } - $relationModel = $this->relation($subRelation)->find(); + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); if ($relationModel) { $relationModel->setParent(clone $this->parent); -- Gitee From 19791fede855967de5e266d6441eba74e3b29188 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Sep 2017 15:29:28 +0800 Subject: [PATCH 0536/1384] =?UTF-8?q?=E4=B8=80=E5=AF=B9=E4=B8=80=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=BB=91=E5=AE=9A=E5=B1=9E=E6=80=A7=E5=88=B0=E7=88=B6?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=90=8E=20=E5=85=B3=E8=81=94=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E4=B8=8D=E5=86=8D=E4=BF=9D=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsTo.php | 16 ++++++++-------- library/think/model/relation/OneToOne.php | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 37ed2554..58f30883 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -145,11 +145,11 @@ class BelongsTo extends OneToOne if (!empty($this->bindAttr)) { // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); } - - // 设置关联属性 - $result->setRelation($attr, $relationModel); } } } @@ -182,11 +182,11 @@ class BelongsTo extends OneToOne if (!empty($this->bindAttr)) { // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); } - - // 设置关联属性 - $result->setRelation(Loader::parseName($relation), $relationModel); } /** diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 07b92d5c..218e72e7 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -276,13 +276,12 @@ abstract class OneToOne extends Relation * @access protected * @param Model $model 关联模型对象 * @param Model $result 父模型对象 - * @param array $bindAttr 绑定属性 * @return void * @throws Exception */ - protected function bindAttr($model, &$result, $bindAttr) + protected function bindAttr($model, &$result) { - foreach ($bindAttr as $key => $attr) { + foreach ($this->bindAttr as $key => $attr) { $key = is_numeric($key) ? $attr : $key; if (isset($result->$key)) { throw new Exception('bind attr has exists:' . $key); -- Gitee From 968ecf5f4471df586a3abba9f324e4fdb696ea57 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Sep 2017 16:28:07 +0800 Subject: [PATCH 0537/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index dfe64d98..2e065277 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -288,6 +288,14 @@ abstract class Builder if (!empty($whereClause)) { $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; } + } elseif (is_array($field)) { + array_unshift($value, $field); + $str2 = []; + foreach ($value as $item) { + $str2[] = $this->parseWhereItem($query, array_shift($item), $item, $logic, $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $str2) . ' )'; } elseif (strpos($field, '|')) { // 不同字段使用相同查询条件(OR) $array = explode('|', $field); -- Gitee From 2fa141c28f0c23c50050e621b052baf62c4f6e55 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Sep 2017 18:49:03 +0800 Subject: [PATCH 0538/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E5=91=BD=E4=BB=A4=E8=A1=8C=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Console.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/Console.php b/library/think/Console.php index 65f52fb1..545c9d95 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -69,8 +69,10 @@ class Console $console = new self('Think Console', '0.1'); // 读取指令集 - if (is_file(Container::get('env')->get('config_path') . 'command.php')) { - $commands = include Container::get('env')->get('config_path') . 'command.php'; + $file = Container::get('env')->get('app_path') . 'command.php'; + + if (is_file($file)) { + $commands = include $file; if (is_array($commands)) { foreach ($commands as $command) { -- Gitee From 0b7b85e89b68c90897b88df87f3e31761d6f3471 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 10:51:08 +0800 Subject: [PATCH 0539/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BConnection=E7=B1=BB?= =?UTF-8?q?=E7=9A=84getCacheKey=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 1e92022b..4f9d41b5 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1992,7 +1992,7 @@ abstract class Connection { if (is_scalar($value)) { $data = $value; - } elseif (is_array($value) && 'eq' == strtolower($value[0])) { + } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { $data = $value[1]; } -- Gitee From 7f56cf4ea1662ac57c5e1921d45ef8bc93433535 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 14:55:02 +0800 Subject: [PATCH 0540/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=9A=84=E9=9D=9E=E6=B3=95=E5=9B=BE=E5=83=8F?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/File.php | 8 ++++++-- library/think/Validate.php | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/library/think/File.php b/library/think/File.php index c0db372d..5cc23494 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -256,8 +256,12 @@ class File extends SplFileObject if (function_exists('exif_imagetype')) { return exif_imagetype($image); } else { - $info = getimagesize($image); - return $info[2]; + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } } } diff --git a/library/think/Validate.php b/library/think/Validate.php index 7a38e21d..b6703878 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -684,8 +684,12 @@ class Validate if (function_exists('exif_imagetype')) { return exif_imagetype($image); } else { - $info = getimagesize($image); - return $info[2]; + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } } } -- Gitee From 98f2d07ba5b4103eee535a6c518e5ddea1d65882 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 15:04:52 +0800 Subject: [PATCH 0541/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Paginator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 0201d6b2..9ad64795 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -138,7 +138,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J */ public static function getCurrentPage($varPage = 'page', $default = 1) { - $page = Container::get('request')->request($varPage); + $page = Container::get('request')->param($varPage); if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { return $page; -- Gitee From 66d3da985b060896d8de4966ae541a550d407d1e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 15:14:17 +0800 Subject: [PATCH 0542/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=E7=9A=84unique=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index b6703878..bd04fa44 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -904,20 +904,20 @@ class Validate // 支持多个字段验证 $fields = explode('^', $key); foreach ($fields as $key) { - $map[$key] = $data[$key]; + $map[$key] = [$key, '=', $data[$key]]; } } elseif (strpos($key, '=')) { parse_str($key, $map); } else { - $map[$key] = $data[$field]; + $map[$key] = [$key, '=', $data[$field]]; } $pk = strval(isset($rule[3]) ? $rule[3] : $db->getPk()); if (isset($rule[2])) { - $map[$pk] = ['neq', $rule[2]]; + $map[$pk] = [$pk, '<>', $rule[2]]; } elseif (isset($data[$pk])) { - $map[$pk] = ['neq', $data[$pk]]; + $map[$pk] = [$pk, '<>', $data[$pk]]; } if ($db->where($map)->field($pk)->find()) { -- Gitee From 08b23dc4585ab47ee9a88310333584673b02bef1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 16:04:11 +0800 Subject: [PATCH 0543/1384] =?UTF-8?q?Config=E7=B1=BBget=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=8E=B7=E5=8F=96=E4=B8=80=E7=BA=A7=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/think/Config.php b/library/think/Config.php index 0d9b0141..4afe3564 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -116,6 +116,8 @@ class Config if (!strpos($name, '.')) { $name = $this->prefix . '.' . $name; + } elseif ('.' == substr($name, -1)) { + return $this->pull(substr($name, 0, -1)); } $name = explode('.', strtolower($name)); -- Gitee From 17df0a0d481bbef3245838cd6054ebee12d5aa87 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 17:12:15 +0800 Subject: [PATCH 0544/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=E7=9A=84unique=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index bd04fa44..bc28152d 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -904,20 +904,20 @@ class Validate // 支持多个字段验证 $fields = explode('^', $key); foreach ($fields as $key) { - $map[$key] = [$key, '=', $data[$key]]; + $map[] = [$key, '=', $data[$key]]; } } elseif (strpos($key, '=')) { parse_str($key, $map); } else { - $map[$key] = [$key, '=', $data[$field]]; + $map[] = [$key, '=', $data[$field]]; } $pk = strval(isset($rule[3]) ? $rule[3] : $db->getPk()); if (isset($rule[2])) { - $map[$pk] = [$pk, '<>', $rule[2]]; + $map[] = [$pk, '<>', $rule[2]]; } elseif (isset($data[$pk])) { - $map[$pk] = [$pk, '<>', $data[$pk]]; + $map[] = [$pk, '<>', $data[$pk]]; } if ($db->where($map)->field($pk)->find()) { -- Gitee From a8ef3b3e09bd3fd6fa452da68d276e833f457758 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Sep 2017 17:45:45 +0800 Subject: [PATCH 0545/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 14 +++++++------- library/think/model/relation/HasOne.php | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index bc28152d..1bdf20c5 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -906,18 +906,18 @@ class Validate foreach ($fields as $key) { $map[] = [$key, '=', $data[$key]]; } - } elseif (strpos($key, '=')) { - parse_str($key, $map); } else { $map[] = [$key, '=', $data[$field]]; } - $pk = strval(isset($rule[3]) ? $rule[3] : $db->getPk()); + $pk = strval(!empty($rule[3]) ? $rule[3] : $db->getPk()); - if (isset($rule[2])) { - $map[] = [$pk, '<>', $rule[2]]; - } elseif (isset($data[$pk])) { - $map[] = [$pk, '<>', $data[$pk]]; + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } } if ($db->where($map)->field($pk)->find()) { diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index d270c00b..d8990c5f 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -154,10 +154,10 @@ class HasOne extends OneToOne if (!empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); } - - // 设置关联属性 - $result->setRelation($attr, $relationModel); } } } @@ -191,9 +191,9 @@ class HasOne extends OneToOne if (!empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); } - - $result->setRelation(Loader::parseName($relation), $relationModel); } } -- Gitee From 986494ad0fd54790c5efc1d9df12a3faf329ea1f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 13 Sep 2017 11:19:26 +0800 Subject: [PATCH 0546/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3count=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=AF=B9fetchSql=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c1eb4a8d..d9e91b54 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -420,7 +420,9 @@ class Query $result = $this->connection->value($this, $field, $default); - if ($force) { + if (!empty($this->options['fetch_sql'])) { + return $result; + } elseif ($force) { $result += 0; } @@ -454,7 +456,13 @@ class Query $options = $this->getOptions(); $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); - return $this->newQuery()->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + if (!empty($options['fetch_sql'])) { + $query->fetchSql(true); + } + + return $query->value('COUNT(*) AS tp_count', 0, true); } return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); -- Gitee From f02aa3d3b6e67442a7835bda95f765828977733b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 13 Sep 2017 11:29:50 +0800 Subject: [PATCH 0547/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3mysql=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=E5=AF=B9socket=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/connector/Mysql.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/think/db/connector/Mysql.php b/library/think/db/connector/Mysql.php index f5dc155c..5bb63464 100644 --- a/library/think/db/connector/Mysql.php +++ b/library/think/db/connector/Mysql.php @@ -53,12 +53,14 @@ class Mysql extends Connection */ protected function parseDsn($config) { - $dsn = 'mysql:dbname=' . $config['database'] . ';host=' . $config['hostname']; - if (!empty($config['hostport'])) { - $dsn .= ';port=' . $config['hostport']; - } elseif (!empty($config['socket'])) { - $dsn .= ';unix_socket=' . $config['socket']; + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; } + $dsn .= ';dbname=' . $config['database']; if (!empty($config['charset'])) { $dsn .= ';charset=' . $config['charset']; -- Gitee From 3396b21e3dd06f93caeebac786987c7169661464 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 14 Sep 2017 18:28:57 +0800 Subject: [PATCH 0548/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BConnection=E7=B1=BB?= =?UTF-8?q?=E7=9A=84getRealSql=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 4f9d41b5..744b8eaf 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1368,8 +1368,8 @@ abstract class Connection $sql = is_numeric($key) ? substr_replace($sql, $value, strpos($sql, '?'), 1) : str_replace( - [':' . $key . ')', ':' . $key . ',', ':' . $key . ' '], - [$value . ')', $value . ',', $value . ' '], + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], + [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], $sql . ' '); } -- Gitee From 344eb45a2e9b37dd80c9944322e3be8de6aac312 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Sep 2017 12:22:03 +0800 Subject: [PATCH 0549/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3view=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/response/View.php b/library/think/response/View.php index c289b7a4..a6f9b362 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -32,7 +32,7 @@ class View extends Response { // 渲染模板输出 return Container::get('view') - ->init(Container::get('app')->config('template'), Container::get('app')->config('view_replace_str')) + ->init(Container::get('app')->pull('template'), Container::get('app')->config('view_replace_str')) ->fetch($data, $this->vars, $this->replace); } -- Gitee From fb7019758e63cb84b2012d51e6de33528d114d45 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Sep 2017 12:36:22 +0800 Subject: [PATCH 0550/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/response/View.php b/library/think/response/View.php index a6f9b362..3f423c07 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -32,7 +32,7 @@ class View extends Response { // 渲染模板输出 return Container::get('view') - ->init(Container::get('app')->pull('template'), Container::get('app')->config('view_replace_str')) + ->init(Container::get('config')->pull('template'), Container::get('app')->config('view_replace_str')) ->fetch($data, $this->vars, $this->replace); } -- Gitee From 418a71ea8b8c99c9d8cb2d354e33d56c95059f23 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Sep 2017 12:39:39 +0800 Subject: [PATCH 0551/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/View.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/response/View.php b/library/think/response/View.php index 3f423c07..cc04a65d 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -31,8 +31,9 @@ class View extends Response protected function output($data) { // 渲染模板输出 + $config = Container::get('config'); return Container::get('view') - ->init(Container::get('config')->pull('template'), Container::get('app')->config('view_replace_str')) + ->init($config->pull('template'), $config->get('view_replace_str')) ->fetch($data, $this->vars, $this->replace); } @@ -79,7 +80,7 @@ class View extends Response public function exists($name) { return Container::get('view') - ->init(Container::get('app')->config('template')) + ->init(Container::get('config')->pull('template')) ->exists($name); } -- Gitee From 7c14e71f020156d1dd611b1e743c9d5044cbb3ce Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Sep 2017 14:14:28 +0800 Subject: [PATCH 0552/1384] =?UTF-8?q?Query=E7=B1=BB=E5=A2=9E=E5=8A=A0leftJ?= =?UTF-8?q?oin=20rightJoin=20=E5=92=8C=20fullJoin=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index d9e91b54..51ec70cc 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -655,6 +655,42 @@ class Query return $this; } + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function leftJoin($join, $condition = null) + { + return $this->join($join, $condition, 'LEFT'); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function rightJoin($join, $condition = null) + { + return $this->join($join, $condition, 'RIGHT'); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function fullJoin($join, $condition = null) + { + return $this->join($join, $condition, 'FULL'); + } + /** * 获取Join表名及别名 支持 * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' -- Gitee From 9ef1ad622099c2c8669ead1705d1558f14355b00 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Sep 2017 16:39:49 +0800 Subject: [PATCH 0553/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bapp=5Fnamespace?= =?UTF-8?q?=E7=9A=84=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/App.php b/library/think/App.php index 8105aed9..3ba60357 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -140,6 +140,7 @@ class App implements \ArrayAccess } $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); // 注册应用命名空间 Loader::addNamespace($this->namespace, $this->appPath); -- Gitee From a4cd3f7b37521834b718d2e98f4dbbf080822217 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Sep 2017 18:13:04 +0800 Subject: [PATCH 0554/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bappend=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=AF=B9=E4=B8=80=E5=AF=B9=E4=B8=80bind=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/Attribute.php | 20 +++++++++++++++++--- library/think/model/concern/Conversion.php | 5 ++++- library/think/model/relation/OneToOne.php | 10 ++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index 3105ebe8..dadcbbee 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -364,10 +364,11 @@ trait Attribute * 获取器 获取数据对象的值 * @access public * @param string $name 名称 + * @param array $item 数据 * @return mixed * @throws InvalidArgumentException */ - public function getAttr($name) + public function getAttr($name, &$item = null) { try { $notFound = false; @@ -408,15 +409,28 @@ trait Attribute if ($modelRelation instanceof Relation) { $value = $this->getRelationData($modelRelation); + if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->$attr : null; + } + } + return false; + } + // 保存关联对象值 $this->relation[$name] = $value; + return $value; } } - throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); } - return $value; } diff --git a/library/think/model/concern/Conversion.php b/library/think/model/concern/Conversion.php index 9ebb9c38..b8f18bd6 100644 --- a/library/think/model/concern/Conversion.php +++ b/library/think/model/concern/Conversion.php @@ -159,7 +159,10 @@ trait Conversion $relation = $this->getAttr($key); $item[$key] = $relation->append([$attr])->toArray(); } else { - $item[$name] = $this->getAttr($name); + $value = $this->getAttr($name, $item); + if (false !== $value) { + $item[$name] = $value; + } } } } diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 218e72e7..2847e9c2 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -225,6 +225,16 @@ abstract class OneToOne extends Relation return $this; } + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + /** * 关联统计 * @access public -- Gitee From 8e4d407a7714ae0af5a743c27b80aa64cfa19007 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 16 Sep 2017 20:35:36 +0800 Subject: [PATCH 0555/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=9A=84saveall=E6=96=B9=E6=B3=95=E7=9A=84=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsToMany.php | 10 +++++----- library/think/model/relation/HasMany.php | 8 ++++---- library/think/model/relation/MorphMany.php | 9 +++++---- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index 0b470380..cce5049a 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -424,7 +424,7 @@ class BelongsToMany extends Relation * @access public * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @param array $pivot 中间表额外数据 - * @return integer + * @return array|Pivot */ public function save($data, array $pivot = []) { @@ -438,11 +438,11 @@ class BelongsToMany extends Relation * @param array $dataSet 数据集 * @param array $pivot 中间表额外数据 * @param bool $samePivot 额外数据是否相同 - * @return integer + * @return array|false */ public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) { - $result = false; + $result = []; foreach ($dataSet as $key => $data) { if (!$samePivot) { @@ -451,10 +451,10 @@ class BelongsToMany extends Relation $pivotData = $pivot; } - $result = $this->attach($data, $pivotData); + $result[] = $this->attach($data, $pivotData); } - return $result; + return empty($result) ? false : $result; } /** diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 1d8418bf..c003b035 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -234,17 +234,17 @@ class HasMany extends Relation * 批量保存当前关联数据对象 * @access public * @param array $dataSet 数据集 - * @return integer + * @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; } /** diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index 833431b2..7151676e 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -279,16 +279,17 @@ class MorphMany extends Relation * 批量保存当前关联数据对象 * @access public * @param array $dataSet 数据集 - * @return integer + * @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; } /** -- Gitee From 58dd4b5fad9681817f8b362368f53eb349a69512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=BB=94=E6=BB=94?= Date: Mon, 11 Sep 2017 02:28:50 +0800 Subject: [PATCH 0556/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=A0=87=E8=AF=86?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=BC=82=E5=B8=B8=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit list($name, $rule) = $rule; 中$rule变量名相同 在php5.6下$name获取到正确值 --- library/think/Route.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index fbd7ea63..8a307e42 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -368,7 +368,8 @@ class Route { // 读取路由标识 if (is_array($rule)) { - list($name, $rule) = $rule; + $name = $rule[0]; + $rule = $rule[1]; } elseif ($this->ruleName) { $name = $this->ruleName; -- Gitee From 6e612ed23b1907edeae5657df580e0928409bebf Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 17 Sep 2017 15:45:26 +0800 Subject: [PATCH 0557/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRoute=E7=B1=BBrule?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index fbd7ea63..8a307e42 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -368,7 +368,8 @@ class Route { // 读取路由标识 if (is_array($rule)) { - list($name, $rule) = $rule; + $name = $rule[0]; + $rule = $rule[1]; } elseif ($this->ruleName) { $name = $this->ruleName; -- Gitee From 1adf4cc02a7f0914fbf0f24c9f33fa755850b015 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 18 Sep 2017 10:45:04 +0800 Subject: [PATCH 0558/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=9A=84table=E5=B1=9E=E6=80=A7=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Query.php | 4 ++++ library/think/db/builder/Mysql.php | 2 +- library/think/db/builder/Pgsql.php | 2 +- library/think/db/builder/Sqlite.php | 2 +- library/think/db/builder/Sqlsrv.php | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 3ce37831..01bcefa6 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -322,7 +322,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检测字段 if (empty($this->field) || true === $this->field) { $query = $this->db(false); - $table = $query->getTable(); + $table = $this->table ?: $query->getTable(); $this->field = $query->getConnection()->getTableFields($table); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 51ec70cc..78fa6fa6 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -198,6 +198,10 @@ class Query */ public function getTable($name = '') { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + $name = $name ?: $this->name; return $this->prefix . Loader::parseName($name); diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index d071051a..e2b12675 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -42,7 +42,7 @@ class Mysql extends Builder if (isset($alias[$table])) { $table = $alias[$table]; } elseif ('__TABLE__' == $table) { - $table = $query->getTable(); + $table = $query->getOptions('table'); } } diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index e9b6d48c..19851092 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -67,7 +67,7 @@ class Pgsql extends Builder if (isset($alias[$table])) { $table = $alias[$table]; } elseif ('__TABLE__' == $table) { - $table = $query->getTable(); + $table = $query->getOptions('table'); } } diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 7a305239..8bc5fbc0 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -70,7 +70,7 @@ class Sqlite extends Builder if (isset($alias[$table])) { $table = $alias[$table]; } elseif ('__TABLE__' == $table) { - $table = $query->getTable(); + $table = $query->getOptions('table'); } } diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index 75fa9e4b..d51e4fde 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -87,7 +87,7 @@ class Sqlsrv extends Builder if (isset($alias[$table])) { $table = $alias[$table]; } elseif ('__TABLE__' == $table) { - $table = $query->getTable(); + $table = $query->getOptions('table'); } } -- Gitee From 94a616ab559dc0036a1f6843cd340b24931d4cb3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 18 Sep 2017 18:50:05 +0800 Subject: [PATCH 0559/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomposer=20autofile?= =?UTF-8?q?=E7=9A=84=E5=8A=A0=E8=BD=BD=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base.php | 3 +++ library/think/Loader.php | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/base.php b/base.php index 3e72b838..52065dd2 100644 --- a/base.php +++ b/base.php @@ -98,3 +98,6 @@ Loader::addClassAlias([ // 加载惯例配置文件 facade\Config::set(include __DIR__ . '/convention.php'); + +// 加载composer autofile文件 +Loader::loadComposerAutoloadFiles(); diff --git a/library/think/Loader.php b/library/think/Loader.php index 644fc10d..636c2627 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -31,6 +31,8 @@ class Loader // 自动加载的文件 private static $autoloadFiles = []; + private static $composerPath; + // 注册自动加载机制 public static function register($autoload = '') { @@ -55,9 +57,11 @@ class Loader self::addClassMap(__include_file($rootPath . 'runtime/classmap.php')); } + self::$composerPath = $rootPath . 'vendor/composer/'; + // Composer自动加载支持 - if (is_dir($rootPath . 'vendor/composer')) { - self::registerComposerLoader($rootPath . 'vendor/composer/'); + if (is_dir(self::$composerPath)) { + self::registerComposerLoader(self::$composerPath); } // 自动加载extend目录 @@ -290,9 +294,13 @@ class Loader self::addClassMap($classMap); } } + } - if (is_file($composerPath . 'autoload_files.php')) { - $includeFiles = require $composerPath . 'autoload_files.php'; + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + if (is_file(self::$composerPath . 'autoload_files.php')) { + $includeFiles = require self::$composerPath . 'autoload_files.php'; foreach ($includeFiles as $fileIdentifier => $file) { if (empty(self::$autoloadFiles[$fileIdentifier])) { __require_file($file); -- Gitee From db6f86a41135ee8dc8fb99a8d658e57a8022c38c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 19 Sep 2017 11:29:22 +0800 Subject: [PATCH 0560/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bexception=5Fhandle?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=AF=B9=E9=97=AD=E5=8C=85=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Error.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Error.php b/library/think/Error.php index 9c92a876..cf518e39 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -108,7 +108,7 @@ class Error if (!$handle) { // 异常处理handle $class = Container::get('app')->config('exception_handle'); - if ($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; -- Gitee From 25854fdb650089a8d1db837de682035f09dc3ceb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 19 Sep 2017 11:52:32 +0800 Subject: [PATCH 0561/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bapp=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E5=87=BD=E6=95=B0=E5=A2=9E=E5=8A=A0=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 5 +++-- library/think/Error.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/helper.php b/helper.php index 13f74d5a..521acab0 100644 --- a/helper.php +++ b/helper.php @@ -66,11 +66,12 @@ if (!function_exists('app')) { * 快速获取容器中的实例 支持依赖注入 * @param string $name 类名或标识 默认获取当前应用实例 * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 * @return object */ - function app($name = 'think\App', $args = []) + function app($name = 'think\App', $args = [], $newInstance = false) { - return Container::getInstance()->make($name, $args); + return Container::get($name, $args, $newInstance); } } diff --git a/library/think/Error.php b/library/think/Error.php index cf518e39..00ac7299 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -107,7 +107,7 @@ class Error if (!$handle) { // 异常处理handle - $class = Container::get('app')->config('exception_handle'); + $class = Container::get('config')->get('exception_handle'); if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { $handle = new $class; } else { -- Gitee From 6cc6029d40730521bcc69f50062491cfb91141b6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 19 Sep 2017 14:19:34 +0800 Subject: [PATCH 0562/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomposer=E7=9A=84?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E8=B7=AF=E5=BE=84=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/Loader.php | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 3ba60357..5994f116 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -109,7 +109,7 @@ class App implements \ArrayAccess $this->beginMem = memory_get_usage(); $this->thinkPath = dirname(dirname(__DIR__)) . '/'; $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; - $this->rootPath = dirname(realpath($this->appPath)) . '/'; + $this->rootPath = defined('ROOT_PATH') ? ROOT_PATH : dirname(realpath($this->appPath)) . '/'; $this->runtimePath = $this->rootPath . 'runtime/'; $this->routePath = $this->rootPath . 'route/'; $this->configPath = $this->rootPath . 'config/'; diff --git a/library/think/Loader.php b/library/think/Loader.php index 636c2627..5f6782ac 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -45,11 +45,15 @@ class Loader 'traits' => __DIR__ . '/../traits/', ]); - $path = dirname($_SERVER['SCRIPT_FILENAME']); - if (PHP_SAPI == 'cli') { - $rootPath = realpath($path) . '/'; + if (defined('ROOT_PATH')) { + $rootPath = ROOT_PATH; } else { - $rootPath = realpath($path . '/../') . '/'; + $path = dirname($_SERVER['SCRIPT_FILENAME']); + if (PHP_SAPI == 'cli' || is_file('./think')) { + $rootPath = realpath($path) . '/'; + } else { + $rootPath = realpath($path . '/../') . '/'; + } } // 加载类库映射文件 -- Gitee From 400c0098a448be664043e2575112e10f361ffd97 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 20 Sep 2017 14:43:12 +0800 Subject: [PATCH 0563/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3url=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 3 ++- library/think/Url.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index 5bc73f6d..a18b7f92 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -47,7 +47,8 @@ class Controller /** * 构造方法 - * @param Request $request Request对象 + * @param Request $request Request对象 + * @param App $app App * @access public */ public function __construct(Request $request, App $app) diff --git a/library/think/Url.php b/library/think/Url.php index 3019ed86..c6680d6c 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -306,7 +306,7 @@ class Url foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key . '', ':' . $key . '$', '<' . $key . '>'], urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>'], urlencode($vars[$key]), $url); unset($vars[$key]); $result = [$url, $domain, $suffix]; } elseif (2 == $val) { -- Gitee From e7ee6107d48e46824921c37f9fc7f9a0c688d48a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 20 Sep 2017 17:27:17 +0800 Subject: [PATCH 0564/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=BB=84=E5=90=88?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index d4056511..e6748eac 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -358,32 +358,6 @@ abstract class Rule return $this; } - /** - * 解析路由变量 - * @access public - * @param array $rule 路由规则 - * @param array $paths URL - * @return array - */ - protected function parseRuleVars($rule, &$paths) - { - $matches = []; - foreach ($rule as $item) { - $fun = ''; - if (0 === strpos($item, '[:')) { - $item = substr($item, 1, -1); - } - if (0 === strpos($item, ':')) { - $var = substr($item, 1); - $matches[$var] = array_shift($paths); - } else { - // 过滤URL中的静态变量 - array_shift($paths); - } - } - return $matches; - } - /** * 路由绑定模型实例 * @access public @@ -468,11 +442,9 @@ abstract class Rule if ($rule) { $rule = explode('/', $rule); // 获取URL地址中的参数 - $paths = explode('|', $url); - $matches = $this->parseRuleVars($rule, $paths); + $paths = explode('|', $url); } else { - $paths = explode('|', $url); - $matches = []; + $paths = explode('|', $url); } if (is_string($route) && isset($option['prefix'])) { -- Gitee From 612e015f7cfc4fa54ee1c423debda73dbf35be03 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 20 Sep 2017 17:36:49 +0800 Subject: [PATCH 0565/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index e6748eac..096c065e 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -438,15 +438,6 @@ abstract class Rule */ public function parseRule($request, $rule, $route, $url, $option = [], $matches = []) { - // 解析路由规则 - if ($rule) { - $rule = explode('/', $rule); - // 获取URL地址中的参数 - $paths = explode('|', $url); - } else { - $paths = explode('|', $url); - } - if (is_string($route) && isset($option['prefix'])) { // 路由地址前缀 $route = $option['prefix'] . $route; @@ -477,7 +468,7 @@ abstract class Rule } // 解析额外参数 - $this->parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); + $this->parseUrlParams($url, $matches); // 记录匹配的路由信息 $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); -- Gitee From cab12a5c27b1a10c2a2c56da0be7df02ecd83888 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 20 Sep 2017 18:31:16 +0800 Subject: [PATCH 0566/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96Connection=E7=B1=BB?= =?UTF-8?q?getCacheKey=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 744b8eaf..42f71d3c 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -745,7 +745,7 @@ abstract class Connection $pk = $query->getPk($options); if (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $query->getBind(false)); + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } $data = $options['data']; @@ -755,12 +755,10 @@ abstract class Connection // 判断查询缓存 $cache = $options['cache']; - if (true === $cache['key'] && !is_null($data) && !is_array($data)) { - $key = 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } elseif (is_string($cache['key'])) { + if (is_string($cache['key'])) { $key = $cache['key']; } elseif (!isset($key)) { - $key = md5(serialize($options) . serialize($query->getBind(false))); + $key = $this->getCacheKey($data, $options, $query->getBind(false)); } $result = Container::get('cache')->get($key); @@ -1026,7 +1024,7 @@ abstract class Connection if (is_string($pk) && isset($data[$pk])) { $where[$pk] = [$pk, '=', $data[$pk]]; if (!isset($key)) { - $key = 'think:' . $options['table'] . '|' . $data[$pk]; + $key = $this->getCacheKey($data[$pk], $options); } unset($data[$pk]); } elseif (is_array($pk)) { @@ -1050,7 +1048,7 @@ abstract class Connection $query->setOption('where', ['AND' => $where]); } } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $query->getBind(false)); + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } // 更新数据 @@ -1106,8 +1104,8 @@ abstract class Connection // 分析查询表达式 $options = $query->getOptions(); $pk = $query->getPk($options); + $data = $options['data']; - $data = $options['data']; if (isset($options['cache']) && is_string($options['cache']['key'])) { $key = $options['cache']['key']; } @@ -1115,13 +1113,13 @@ abstract class Connection if (!is_null($data) && true !== $data) { if (!isset($key) && !is_array($data)) { // 缓存标识 - $key = 'think:' . $options['table'] . '|' . $data; + $key = $this->getCacheKey($data, $options); } // AR模式分析主键条件 $query->parsePkWhere($data); } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $query->getBind(false)); + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } if (true !== $data && empty($options['where'])) { @@ -1992,12 +1990,10 @@ abstract class Connection { if (is_scalar($value)) { $data = $value; - } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { - $data = $value[1]; } if (isset($data)) { - return 'think:' . $options['table'] . '|' . $data; + return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; } else { return md5(serialize($options) . serialize($bind)); } -- Gitee From d3f58a4be37cc4ce3c312c37258648fd0a47ff3b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 11:41:15 +0800 Subject: [PATCH 0567/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1U?= =?UTF-8?q?RL=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Url.php b/library/think/Url.php index c6680d6c..d96dfb0b 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -306,7 +306,7 @@ class Url foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>'], urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], urlencode($vars[$key]), $url); unset($vars[$key]); $result = [$url, $domain, $suffix]; } elseif (2 == $val) { -- Gitee From c044338cae45a01cc9d7fcf3ab972290cd11d7eb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 12:27:51 +0800 Subject: [PATCH 0568/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index d96dfb0b..2fd5807d 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -306,11 +306,11 @@ class Url foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], urlencode($vars[$key]), $url); unset($vars[$key]); $result = [$url, $domain, $suffix]; } elseif (2 == $val) { - $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>'], '', $url); + $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>'], '', $url); $result = [$url, $domain, $suffix]; } else { break; -- Gitee From 800c9b36d6718af412d846e5bc5386c078047375 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 12:42:22 +0800 Subject: [PATCH 0569/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BwhereTime=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 73 ++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 78fa6fa6..8ae7f984 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1802,49 +1802,54 @@ class Query * 查询日期或者时间 * @access public * @param string $field 日期字段名 - * @param string $op 比较运算符或者表达式 + * @param string|array $op 比较运算符或者表达式 * @param string|array $range 比较范围 * @return $this */ public function whereTime($field, $op, $range = null) { if (is_null($range)) { - // 使用日期表达式 - $date = getdate(); - switch (strtolower($op)) { - case 'today': - case 'd': - $range = ['today', 'tomorrow']; - break; - case 'week': - case 'w': - $range = 'this week 00:00:00'; - break; - case 'month': - case 'm': - $range = mktime(0, 0, 0, $date['mon'], 1, $date['year']); - break; - case 'year': - case 'y': - $range = mktime(0, 0, 0, 1, 1, $date['year']); - break; - case 'yesterday': - $range = ['yesterday', 'today']; - break; - case 'last week': - $range = ['last week 00:00:00', 'this week 00:00:00']; - break; - case 'last month': - $range = [date('y-m-01', strtotime('-1 month')), mktime(0, 0, 0, $date['mon'], 1, $date['year'])]; - break; - case 'last year': - $range = [mktime(0, 0, 0, 1, 1, $date['year'] - 1), mktime(0, 0, 0, 1, 1, $date['year'])]; - break; - default: - $range = $op; + if (is_array($op)) { + $range = $op; + } else { + // 使用日期表达式 + switch (strtolower($op)) { + case 'today': + case 'd': + $range = ['today', 'tomorrow']; + break; + case 'week': + case 'w': + $range = ['this week 00:00:00', 'next week 00:00:00']; + break; + case 'month': + case 'm': + $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; + break; + case 'year': + case 'y': + $range = ['this year 1/1', 'next year 1/1']; + break; + case 'yesterday': + $range = ['yesterday', 'today']; + break; + case 'last week': + $range = ['last week 00:00:00', 'this week 00:00:00']; + break; + case 'last month': + $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; + break; + case 'last year': + $range = ['last year 1/1', 'this year 1/1']; + break; + default: + $range = $op; + } } + $op = is_array($range) ? 'between' : '>'; } + $this->where($field, strtolower($op) . ' time', $range); return $this; -- Gitee From 26cdcecbc6fcfd113847eb7f071d50c934cab5d5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 15:14:16 +0800 Subject: [PATCH 0570/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BwhereTime=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=94=AF=E6=8C=81=E6=89=A9=E5=B1=95=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 92 ++++++++++++++------------------------ 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 8ae7f984..d5e0ae9f 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -50,6 +50,20 @@ class Query // 扩展查询方法 private static $extend = []; + // 日期查询快捷方式 + protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year']; + // 日期查询表达式 + protected $timeRule = [ + 'today' => ['today', 'tomorrow'], + 'yesterday' => ['yesterday', 'today'], + 'week' => ['this week 00:00:00', 'next week 00:00:00'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'], + 'year' => ['this year 1/1', 'next year 1/1'], + 'last year' => ['last year 1/1', 'this year 1/1'], + ]; + /** * 架构函数 * @access public @@ -876,7 +890,6 @@ class Query public function exp($field, $value) { $this->data($field, ['exp', $value]); - return $this; } @@ -1011,7 +1024,6 @@ class Query public function whereNull($field, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'null', null); - return $this; } @@ -1025,7 +1037,6 @@ class Query public function whereNotNull($field, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'notnull', null); - return $this; } @@ -1039,7 +1050,6 @@ class Query public function whereExists($condition, $logic = 'AND') { $this->options['where'][strtoupper($logic)][] = ['', 'exists', $condition]; - return $this; } @@ -1053,7 +1063,6 @@ class Query public function whereNotExists($condition, $logic = 'AND') { $this->options['where'][strtoupper($logic)][] = ['', 'not exists', $condition]; - return $this; } @@ -1068,7 +1077,6 @@ class Query public function whereIn($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'in', $condition); - return $this; } @@ -1083,7 +1091,6 @@ class Query public function whereNotIn($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'not in', $condition); - return $this; } @@ -1098,7 +1105,6 @@ class Query public function whereLike($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'like', $condition); - return $this; } @@ -1113,7 +1119,6 @@ class Query public function whereNotLike($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'not like', $condition); - return $this; } @@ -1128,7 +1133,6 @@ class Query public function whereBetween($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'between', $condition); - return $this; } @@ -1143,7 +1147,6 @@ class Query public function whereNotBetween($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'not between', $condition); - return $this; } @@ -1195,7 +1198,6 @@ class Query public function whereExp($field, $condition, $logic = 'AND') { $this->parseWhereExp($logic, $field, 'exp', $condition); - return $this; } @@ -1498,7 +1500,6 @@ class Query public function using($using) { $this->options['using'] = $using; - return $this; } @@ -1575,7 +1576,6 @@ class Query public function group($group) { $this->options['group'] = $group; - return $this; } @@ -1588,7 +1588,6 @@ class Query public function having($having) { $this->options['having'] = $having; - return $this; } @@ -1615,7 +1614,6 @@ class Query public function distinct($distinct) { $this->options['distinct'] = $distinct; - return $this; } @@ -1656,7 +1654,6 @@ class Query public function force($force) { $this->options['force'] = $force; - return $this; } @@ -1669,7 +1666,6 @@ class Query public function comment($comment) { $this->options['comment'] = $comment; - return $this; } @@ -1682,7 +1678,6 @@ class Query public function fetchSql($fetch = true) { $this->options['fetch_sql'] = $fetch; - return $this; } @@ -1695,7 +1690,6 @@ class Query public function fetchPdo($pdo = true) { $this->options['fetch_pdo'] = $pdo; - return $this; } @@ -1707,7 +1701,6 @@ class Query public function master() { $this->options['master'] = true; - return $this; } @@ -1720,7 +1713,6 @@ class Query public function strict($strict = true) { $this->options['strict'] = $strict; - return $this; } @@ -1733,7 +1725,6 @@ class Query public function failException($fail = true) { $this->options['fail'] = $fail; - return $this; } @@ -1746,7 +1737,6 @@ class Query public function sequence($sequence = null) { $this->options['sequence'] = $sequence; - return $this; } @@ -1794,7 +1784,19 @@ class Query public function pk($pk) { $this->pk = $pk; + return $this; + } + /** + * 查询日期或者时间 + * @access public + * @param string $name 时间表达式 + * @param string|array $rule 时间范围 + * @return $this + */ + public function timeRule($name, $rule) + { + $this->timeRule[$name] = $rule; return $this; } @@ -1812,38 +1814,14 @@ class Query if (is_array($op)) { $range = $op; } else { - // 使用日期表达式 - switch (strtolower($op)) { - case 'today': - case 'd': - $range = ['today', 'tomorrow']; - break; - case 'week': - case 'w': - $range = ['this week 00:00:00', 'next week 00:00:00']; - break; - case 'month': - case 'm': - $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; - break; - case 'year': - case 'y': - $range = ['this year 1/1', 'next year 1/1']; - break; - case 'yesterday': - $range = ['yesterday', 'today']; - break; - case 'last week': - $range = ['last week 00:00:00', 'this week 00:00:00']; - break; - case 'last month': - $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; - break; - case 'last year': - $range = ['last year 1/1', 'this year 1/1']; - break; - default: - $range = $op; + if (isset($this->timeExp[strtolower($op)])) { + $op = $this->timeExp[strtolower($op)]; + } + + if (isset($this->timeRule[strtolower($op)])) { + $range = $this->timeRule[strtolower($op)]; + } else { + $range = $op; } } @@ -1931,7 +1909,6 @@ class Query protected function options(array $options) { $this->options = $options; - return $this; } @@ -1960,7 +1937,6 @@ class Query public function setOption($option, $value) { $this->options[$option] = $value; - return $this; } -- Gitee From 8b286d2be6de8f8842e31c9b2836e336d39873da Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 16:45:01 +0800 Subject: [PATCH 0571/1384] =?UTF-8?q?File=E7=B1=BB=E7=9A=84move=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=AC=AC=E4=BA=8C=E4=B8=AA=E5=8F=82=E6=95=B0=E6=94=AF?= =?UTF-8?q?=E6=8C=81false?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/File.php b/library/think/File.php index 5cc23494..44370f51 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -382,7 +382,7 @@ class File extends SplFileObject } } } - } elseif ('' === $savename) { + } elseif ('' === $savename || false === $savename) { $savename = $this->getInfo('name'); } -- Gitee From 56798a3cfaabecd17c2e4eaf05cefc8ab214e6d5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 18:42:32 +0800 Subject: [PATCH 0572/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BConfig=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 45 ++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/library/think/Config.php b/library/think/Config.php index 4afe3564..5ae94ead 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -49,7 +49,7 @@ class Config } /** - * 加载配置文件(PHP格式) + * 加载配置文件(多种格式) * @access public * @param string $file 配置文件名 * @param string $name 配置名(如设置即表示二级配置) @@ -73,6 +73,31 @@ class Config } } + /** + * 自动加载配置文件(PHP格式) + * @access public + * @param string $name 配置名 + * @return void + */ + protected function autoLoad($name) + { + // 如果尚未载入 则动态加载配置文件 + $module = Container::get('request')->module(); + $module = $module ? $module . '/' : ''; + $app = Container::get('app'); + $path = $app->getAppPath() . $module; + + if (is_dir($path . 'config')) { + $file = $path . 'config/' . $name . $app->getConfigExt(); + } elseif (is_dir($app->getConfigPath() . $module)) { + $file = $app->getConfigPath() . $module . $name . $app->getConfigExt(); + } + + if (isset($file) && is_file($file)) { + $this->load($file, $name); + } + } + /** * 检测配置是否存在 * @access public @@ -98,6 +123,11 @@ class Config { $name = strtolower($name); + if (!isset($this->config[$name])) { + // 如果尚未载入 则动态加载配置文件 + $this->autoLoad($name); + } + return isset($this->config[$name]) ? $this->config[$name] : []; } @@ -125,18 +155,7 @@ class Config if (!isset($config[$name[0]])) { // 如果尚未载入 则动态加载配置文件 - $module = Container::get('request')->module(); - $module = $module ? $module . '/' : ''; - $path = Container::get('app')->getAppPath() . $module; - if (is_dir($path . 'config')) { - $file = $path . 'config/' . $name[0] . Container::get('app')->getConfigExt(); - } elseif (is_dir(Container::get('app')->getConfigPath() . $module)) { - $file = Container::get('app')->getConfigPath() . $module . $name[0] . Container::get('app')->getConfigExt(); - } - - if (isset($file) && is_file($file)) { - $this->load($file, $name[0]); - } + $this->autoLoad($name[0]); } // 按.拆分成多维数组进行判断 -- Gitee From 828077298409c45b96c2691778da094fda73d0f4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 21 Sep 2017 23:09:46 +0800 Subject: [PATCH 0573/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BModel=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 01bcefa6..e87d4017 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -584,11 +584,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->isUpdate = $update; if (!empty($where)) { - $this->updateWhere = self::parseWhere($where); + $this->updateWhere = $where; } } else { $this->isUpdate = true; - $this->updateWhere = self::parseWhere($update); + $this->updateWhere = $update; } return $this; -- Gitee From 54d40513a0bbbac3b70298e585ac74ea4aac032e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 22 Sep 2017 17:04:31 +0800 Subject: [PATCH 0574/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 18 +++++++++++++ library/think/db/Query.php | 2 +- library/think/model/Relation.php | 4 ++- library/think/model/relation/BelongsTo.php | 7 ++--- .../think/model/relation/BelongsToMany.php | 5 +--- library/think/model/relation/HasMany.php | 27 ++++++++++--------- .../think/model/relation/HasManyThrough.php | 2 +- library/think/model/relation/HasOne.php | 6 ++--- library/think/model/relation/MorphMany.php | 23 +++++++++------- library/think/model/relation/MorphOne.php | 4 +-- library/think/model/relation/OneToOne.php | 14 +++++----- 11 files changed, 69 insertions(+), 43 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index e87d4017..09e9fb13 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -54,6 +54,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected static $initialized = []; + protected $queryInstance; + /** * 架构函数 * @access public @@ -149,6 +151,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $query; } + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param Query $query 查询对象实例 + * @return $this + */ + public function setQuery($query) + { + $this->queryInstance = $query; + return $this; + } + /** * 获取当前模型的数据库查询对象 * @access public @@ -157,6 +171,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function db($useBaseQuery = true) { + if ($this->queryInstance) { + return $this->queryInstance; + } + $query = $this->buildQuery(); if ($useBaseQuery) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index d5e0ae9f..9bde6ec0 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -188,7 +188,7 @@ class Query */ public function getModel() { - return $this->model; + return $this->model->setQuery($this); } /** diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index f6a1e9b5..6478a1c8 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -117,7 +117,9 @@ abstract class Relation // 执行基础查询 $this->baseQuery(); - return call_user_func_array([$this->query->getModel(), $method], $args); + $result = call_user_func_array([$this->query->getModel(), $method], $args); + + return $result === $this->query ? $this : $result; } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 58f30883..3f1b780d 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -46,7 +46,7 @@ class BelongsTo extends OneToOne public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } $foreignKey = $this->foreignKey; @@ -125,7 +125,7 @@ class BelongsTo extends OneToOne } if (!empty($range)) { - $data = $this->eagerlyWhere($this, [ + $data = $this->eagerlyWhere([ [$localKey, 'in', $range], ], $localKey, $relation, $subRelation, $closure); @@ -167,7 +167,8 @@ class BelongsTo extends OneToOne { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere($this, [ + + $data = $this->eagerlyWhere([ [$localKey, '=', $result->$foreignKey], ], $localKey, $relation, $subRelation, $closure); diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index cce5049a..ae3353dd 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -63,7 +63,6 @@ class BelongsToMany extends Relation public function pivot($pivot) { $this->pivotName = $pivot; - return $this; } @@ -239,9 +238,7 @@ class BelongsToMany extends Relation */ 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; } diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index c003b035..2d2eb535 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -44,7 +44,7 @@ class HasMany extends Relation public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } $list = $this->query @@ -83,9 +83,10 @@ class HasMany extends Relation } if (!empty($range)) { - $data = $this->eagerlyOneToMany($this, [ + $where = [ [$this->foreignKey, 'in', $range], - ], $relation, $subRelation, $closure); + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -120,10 +121,11 @@ class HasMany extends Relation $localKey = $this->localKey; if (isset($result->$localKey)) { - $pk = $result->$localKey; - $data = $this->eagerlyOneToMany($this, [ + $pk = $result->$localKey; + $where = [ [$this->foreignKey, '=', $pk], - ], $relation, $subRelation, $closure); + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); // 关联数据封装 if (!isset($data[$pk])) { @@ -152,7 +154,7 @@ class HasMany extends Relation if (isset($result->$localKey)) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } $count = $this->query->where($this->foreignKey, '=', $result->$localKey)->count(); @@ -170,7 +172,7 @@ class HasMany extends Relation public function getRelationCountQuery($closure) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } return $this->query @@ -182,23 +184,24 @@ class HasMany extends Relation /** * 一对多 关联模型预查询 * @access public - * @param object $model 关联模型对象 * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool $closure * @return array */ - protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = false) { $foreignKey = $this->foreignKey; + $this->query->removeWhereField($this->foreignKey); + // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $model]); + $closure($this->query); } - $list = $model->where($where)->with($subRelation)->select(); + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php index 66912c26..70f974c6 100644 --- a/library/think/model/relation/HasManyThrough.php +++ b/library/think/model/relation/HasManyThrough.php @@ -54,7 +54,7 @@ class HasManyThrough extends Relation public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } $this->baseQuery(); diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index d8990c5f..30bb244e 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -46,7 +46,7 @@ class HasOne extends OneToOne $localKey = $this->localKey; if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } // 判断关联类型执行查询 @@ -133,7 +133,7 @@ class HasOne extends OneToOne } if (!empty($range)) { - $data = $this->eagerlyWhere($this, [ + $data = $this->eagerlyWhere([ [$foreignKey, 'in', $range], ], $foreignKey, $relation, $subRelation, $closure); @@ -175,7 +175,7 @@ class HasOne extends OneToOne { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere($this, [ + $data = $this->eagerlyWhere([ [$foreignKey, '=', $result->$localKey], ], $foreignKey, $relation, $subRelation, $closure); diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index 7151676e..12306c3b 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -53,7 +53,7 @@ class MorphMany extends Relation public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } $this->baseQuery(); @@ -119,10 +119,11 @@ class MorphMany extends Relation } if (!empty($range)) { - $data = $this->eagerlyMorphToMany([ + $where = [ [$morphKey, 'in', $range], [$morphType, '=', $type], - ], $relation, $subRelation, $closure); + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -157,11 +158,12 @@ class MorphMany extends Relation $pk = $result->getPk(); if (isset($result->$pk)) { - $key = $result->$pk; - $data = $this->eagerlyMorphToMany([ + $key = $result->$pk; + $where = [ [$this->morphKey, '=', $key], [$this->morphType, '=', $this->type], - ], $relation, $subRelation, $closure); + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); if (!isset($data[$key])) { $data[$key] = []; @@ -190,7 +192,7 @@ class MorphMany extends Relation if (isset($result->$pk)) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closur($this->query); } $count = $this->query @@ -213,7 +215,7 @@ class MorphMany extends Relation public function getRelationCountQuery($closure) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } return $this->query @@ -237,9 +239,12 @@ class MorphMany extends Relation protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 + $this->query->removeOptions('where'); + if ($closure) { - call_user_func_array($closure, [ & $this]); + $closure($this->query); } + $list = $this->query->where($where)->with($subRelation)->select(); $morphKey = $this->morphKey; diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index cd785c5e..16adce2b 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -53,7 +53,7 @@ class MorphOne extends Relation public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $closure($this->query); } $this->baseQuery(); @@ -186,7 +186,7 @@ class MorphOne extends Relation { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $this]); + $closure($this->query); } $list = $this->query->where($where)->with($subRelation)->find(); diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 2847e9c2..b29164dd 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -87,7 +87,7 @@ abstract class OneToOne extends Relation if ($closure) { // 执行闭包查询 - call_user_func_array($closure, [ & $query]); + $closure($query); // 使用withField指定获取关联的字段,如 // $query->where(['id'=>1])->withField('id,name'); if ($query->getOptions('with_field')) { @@ -304,7 +304,6 @@ abstract class OneToOne extends Relation /** * 一对一 关联模型预查询(IN方式) * @access public - * @param object $model 关联模型对象 * @param array $where 关联预查询条件 * @param string $key 关联键名 * @param string $relation 关联名 @@ -312,17 +311,18 @@ abstract class OneToOne extends Relation * @param bool|\Closure $closure * @return array */ - protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $model]); - if ($field = $model->getOptions('with_field')) { - $model->field($field)->removeOption('with_field'); + $closure($this->query); + + if ($field = $this->query->getOptions('with_field')) { + $this->query->field($field)->removeOption('with_field'); } } - $list = $model->where($where)->with($subRelation)->select(); + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; -- Gitee From 6b7f14dd082e82a39a439f87660f03993aaebd9a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 24 Sep 2017 12:27:49 +0800 Subject: [PATCH 0575/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/Driver.php | 3 +++ library/think/cache/driver/File.php | 2 +- library/think/cache/driver/Lite.php | 2 +- library/think/cache/driver/Sqlite.php | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 873da4c7..327e6b51 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -126,6 +126,7 @@ abstract class Driver if (!$this->has($name)) { while ($this->has($name . '_lock')) { // 存在锁定则等待 + usleep(100000); } try { @@ -145,6 +146,8 @@ abstract class Driver } catch (\Exception $e) { // 解锁 $this->rm($name . '_lock'); + } catch (\throwable $e) { + $this->rm($name . '_lock'); } } else { $value = $this->get($name); diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 3bcf6db0..16056f78 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -221,7 +221,7 @@ class File extends Driver if ($this->has($name)) { $value = $this->get($name) - $step; } else { - $value = $step; + $value = -$step; } return $this->set($name, $value, 0) ? $value : false; diff --git a/library/think/cache/driver/Lite.php b/library/think/cache/driver/Lite.php index d04aeed6..568ee6b7 100644 --- a/library/think/cache/driver/Lite.php +++ b/library/think/cache/driver/Lite.php @@ -164,7 +164,7 @@ class Lite extends Driver if ($this->has($name)) { $value = $this->get($name) - $step; } else { - $value = $step; + $value = -$step; } return $this->set($name, $value, 0) ? $value : false; diff --git a/library/think/cache/driver/Sqlite.php b/library/think/cache/driver/Sqlite.php index 58d5cf22..8a7bd237 100644 --- a/library/think/cache/driver/Sqlite.php +++ b/library/think/cache/driver/Sqlite.php @@ -182,7 +182,7 @@ class Sqlite extends Driver if ($this->has($name)) { $value = $this->get($name) - $step; } else { - $value = $step; + $value = -$step; } return $this->set($name, $value, 0) ? $value : false; -- Gitee From 91b74dd5c9051bceb013277d45bb8671e15f6af9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 24 Sep 2017 12:39:28 +0800 Subject: [PATCH 0576/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/Driver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/cache/Driver.php b/library/think/cache/Driver.php index 327e6b51..092b0ce5 100644 --- a/library/think/cache/Driver.php +++ b/library/think/cache/Driver.php @@ -124,7 +124,8 @@ abstract class Driver public function remember($name, $value, $expire = null) { if (!$this->has($name)) { - while ($this->has($name . '_lock')) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { // 存在锁定则等待 usleep(100000); } @@ -144,7 +145,6 @@ abstract class Driver // 解锁 $this->rm($name . '_lock'); } catch (\Exception $e) { - // 解锁 $this->rm($name . '_lock'); } catch (\throwable $e) { $this->rm($name . '_lock'); -- Gitee From 97cfb20697cdcf4935b4b0051ea5b89f7a5ec8a0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 26 Sep 2017 17:55:59 +0800 Subject: [PATCH 0577/1384] =?UTF-8?q?=E6=83=AF=E4=BE=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=B0=83=E6=95=B4=20Url=E7=B1=BB=E5=BD=93?= =?UTF-8?q?=E6=99=AE=E9=80=9A=E6=A8=A1=E5=BC=8F=E5=8F=82=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E5=80=99=E4=B8=8D=E5=81=9Aurlencode=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/Url.php | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/convention.php b/convention.php index 37f4191c..e6a9dd71 100644 --- a/convention.php +++ b/convention.php @@ -89,6 +89,8 @@ return [ 'url_lazy_route' => false, // 是否强制使用路由 'url_route_must' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, // 域名根,如thinkphp.cn 'url_domain_root' => '', // 是否自动转换URL中的控制器和操作名 diff --git a/library/think/Url.php b/library/think/Url.php index 2fd5807d..b9909282 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -160,7 +160,7 @@ class Url if (!empty($vars)) { // 添加参数 if ($this->app['config']->get('url_common_param')) { - $vars = urldecode(http_build_query($vars)); + $vars = http_build_query($vars); $url .= $suffix . '?' . $vars . $anchor; } else { $paramType = $this->app['config']->get('url_param_type'); @@ -304,10 +304,13 @@ class Url return [$url, $domain, $suffix]; } + $type = $this->app['config']->get('url_common_param'); + foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); unset($vars[$key]); + $result = [$url, $domain, $suffix]; } elseif (2 == $val) { $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>'], '', $url); -- Gitee From 530eb39f893544f4cf7bee1f12d9e4e5a1ed8797 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 26 Sep 2017 19:21:52 +0800 Subject: [PATCH 0578/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88ROOT=5FPATH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/Loader.php | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 5994f116..3ba60357 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -109,7 +109,7 @@ class App implements \ArrayAccess $this->beginMem = memory_get_usage(); $this->thinkPath = dirname(dirname(__DIR__)) . '/'; $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; - $this->rootPath = defined('ROOT_PATH') ? ROOT_PATH : dirname(realpath($this->appPath)) . '/'; + $this->rootPath = dirname(realpath($this->appPath)) . '/'; $this->runtimePath = $this->rootPath . 'runtime/'; $this->routePath = $this->rootPath . 'route/'; $this->configPath = $this->rootPath . 'config/'; diff --git a/library/think/Loader.php b/library/think/Loader.php index 5f6782ac..d5a68895 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -45,15 +45,11 @@ class Loader 'traits' => __DIR__ . '/../traits/', ]); - if (defined('ROOT_PATH')) { - $rootPath = ROOT_PATH; + $path = dirname($_SERVER['SCRIPT_FILENAME']); + if (PHP_SAPI == 'cli' || is_file('./think')) { + $rootPath = realpath($path) . '/'; } else { - $path = dirname($_SERVER['SCRIPT_FILENAME']); - if (PHP_SAPI == 'cli' || is_file('./think')) { - $rootPath = realpath($path) . '/'; - } else { - $rootPath = realpath($path . '/../') . '/'; - } + $rootPath = realpath($path . '/../') . '/'; } // 加载类库映射文件 -- Gitee From a6f08c2c349483529748087bb6a41f923de09dc9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 27 Sep 2017 11:19:56 +0800 Subject: [PATCH 0579/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88APP=5FPATH=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E5=AE=9A=E4=B9=89=20=E5=A6=82=E9=9C=80=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E5=BA=94=E7=94=A8=E7=9B=AE=E5=BD=95=20=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E9=87=8D=E6=96=B0=E5=AE=9A=E4=B9=89=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console.php | 2 +- start.php | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/console.php b/console.php index 29e3fbf0..d7fb21fa 100644 --- a/console.php +++ b/console.php @@ -16,5 +16,5 @@ namespace think; require __DIR__ . '/base.php'; // 执行应用 -Container::get('app', [defined('APP_PATH') ? APP_PATH : ''])->initialize(); +Container::get('app')->initialize(); Console::init(); diff --git a/start.php b/start.php index 75370b09..b6c9883c 100644 --- a/start.php +++ b/start.php @@ -17,6 +17,4 @@ require __DIR__ . '/base.php'; // 支持事先使用静态方法设置Request对象和Config对象 // 执行应用并响应 -Container::get('app', [defined('APP_PATH') ? APP_PATH : '']) - ->run() - ->send(); +Container::get('app')->run()->send(); -- Gitee From 180d0ecf55f59efdf991f97a33fcd1ee4a628984 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 27 Sep 2017 12:34:32 +0800 Subject: [PATCH 0580/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0app=5Fdebug?= =?UTF-8?q?=E7=9A=84Env=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/App.php b/library/think/App.php index 3ba60357..9da3f605 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -155,6 +155,7 @@ class App implements \ArrayAccess // 应用调试模式 $this->debug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->debug); if (!$this->debug) { ini_set('display_errors', 'Off'); -- Gitee From b8f0adab7bea8d45704383e297f50d0c342eeefd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 11:37:01 +0800 Subject: [PATCH 0581/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=B3=9B=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index 8a307e42..87a087ff 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -167,7 +167,7 @@ class Route // 支持多个域名使用相同路由规则 $domain = is_array($name) ? array_shift($name) : $name; - if (!strpos($domain, '.')) { + if ('*' != $domain && !strpos($domain, '.')) { $root = $this->config->get('app.url_domain_root'); if (!$root) { $item = explode('.', $this->host); -- Gitee From f5811073516b5fd736ae40071d97423254150409 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 16:55:03 +0800 Subject: [PATCH 0582/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E7=9A=84=E8=A7=A3=E6=9E=90=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=20mysql=E5=A2=9E=E5=8A=A0regexp=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=20=E6=94=AF=E6=8C=81=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 354 +++++++++++++++++++++-------- library/think/db/builder/Mysql.php | 29 +++ 2 files changed, 294 insertions(+), 89 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 2e065277..cf10b251 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -19,8 +19,21 @@ abstract class Builder // connection对象实例 protected $connection; - // 数据库表达式 - protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + // 查询表达式映射 + protected $exp = ['EQ' => '=', 'NEQ' => '<>', 'GT' => '>', 'EGT' => '>=', 'LT' => '<', 'ELT' => '<=', 'NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + ]; // SQL表达式 protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%'; @@ -53,6 +66,19 @@ abstract class Builder return $this->connection; } + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser($name, $parser) + { + $this->parser[$name] = $parser; + return $this; + } + /** * 数据分析 * @access protected @@ -362,14 +388,9 @@ abstract class Builder } // 检测操作符 - if (!in_array($exp, $this->exp)) { - $exp = strtolower($exp); - - if (isset($this->exp[$exp])) { - $exp = $this->exp[$exp]; - } else { - throw new Exception('where express error:' . $exp); - } + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; } $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field); @@ -399,105 +420,260 @@ abstract class Builder $whereStr = ''; - if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { - if (is_array($value)) { - throw new Exception('where express error:' . $exp . var_export($value, true)); + // 解析查询表达式 + foreach ($this->parser as $fun => $val) { + if (in_array($exp, $val)) { + $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindName, $bindType); + break; } + } - // 比较运算 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($query, $value); - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($query, $value, $field); - } - } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { - // 模糊匹配 - if (is_array($value)) { - foreach ($value as $item) { - $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($query, $item, $field); - } + return $whereStr; + } - $logic = isset($val[2]) ? $val[2] : 'AND'; - $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($query, $value, $field); + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseLike(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $item; } - } elseif ('EXP' == $exp) { - // 表达式查询 - $whereStr .= '( ' . $key . ' ' . $value . ' )'; - } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { - // NULL 查询 - $whereStr .= $key . ' IS ' . $exp; - } elseif (in_array($exp, ['NOT IN', 'IN'])) { - // IN 查询 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($query, $value); - } else { - $value = array_unique(is_array($value) ? $value : explode(',', $value)); - $bind = []; - $array = []; - $i = 0; + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + return $whereStr; + } - foreach ($value as $k => $v) { - $i++; - if ($query->isBind($bindName . '_in_' . $i)) { - $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; - } else { - $bindKey = $bindName . '_in_' . $i; - } - $bind[$bindKey] = [$v, $bindType]; - $array[] = ':' . $bindKey; - } + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // 表达式查询 + return '( ' . $key . ' ' . $value . ' )'; + } - $zone = implode(',', $array); - $query->bind($bind); + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // NULL 查询 + return $key . ' IS ' . $exp; + } - $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; - } - } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { - // BETWEEN 查询 - $data = is_array($value) ? $value : explode(',', $value); + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); - if ($query->isBind($bindName . '_between_1')) { - $bindKey1 = $bindName . '_between_1' . uniqid(); - $bindKey2 = $bindName . '_between_2' . uniqid(); - } else { - $bindKey1 = $bindName . '_between_1'; - $bindKey2 = $bindName . '_between_2'; - } + if ($query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } - $bind = [ - $bindKey1 => [$data[0], $bindType], - $bindKey2 => [$data[1], $bindType], - ]; + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; - $query->bind($bind); + $query->bind($bind); - $between = ':' . $bindKey1 . ' AND :' . $bindKey2; + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; - $whereStr .= $key . ' ' . $exp . ' ' . $between; - } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { - // EXISTS 查询 - if ($value instanceof \Closure) { - $whereStr .= $exp . ' ' . $this->parseClosure($query, $value); - } else { - $whereStr .= $exp . ' (' . $value . ')'; - } - } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { - $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindName, $bindType); - } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { - if (is_string($value)) { - $value = explode(',', $value); + return $key . ' ' . $exp . ' ' . $between; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // EXISTS 查询 + if ($value instanceof \Closure) { + $whereStr = $exp . ' ' . $this->parseClosure($query, $value); + } else { + $whereStr = $exp . ' (' . $value . ')'; + } + return $whereStr; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindName, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof \Closure) { + $whereStr = $key . ' ' . $exp . ' ' . $this->parseClosure($query, $value); + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($query, $value[0], $field, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($query, $value[1], $field, $bindName . '_between_2', $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + { + // IN 查询 + if ($value instanceof \Closure) { + $whereStr = $key . ' ' . $exp . ' ' . $this->parseClosure($query, $value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + + $bind = []; + $array = []; + $i = 0; + + foreach ($value as $k => $v) { + $i++; + if ($query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; } - $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($query, $value[0], $field, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($query, $value[1], $field, $bindName . '_between_2', $bindType); + $zone = implode(',', $array); + $query->bind($bind); + + $whereStr = $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; } return $whereStr; } - // 执行闭包子查询 + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ protected function parseClosure(Query $query, $call, $show = true) { $newQuery = $query->newQuery()->setConnection($this->connection); diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index e2b12675..174f31c9 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -19,8 +19,37 @@ use think\db\Query; */ 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'], + ]; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + /** + * 正则查询 + * @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) + { + return $key . ' ' . $exp . ' ' . $value; + } + /** * 字段和表名处理 * @access protected -- Gitee From 5438560ec94288bff77b71a8103962b8815e9c89 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 17:45:54 +0800 Subject: [PATCH 0583/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3console?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console.php b/console.php index d7fb21fa..bdfc93dc 100644 --- a/console.php +++ b/console.php @@ -16,5 +16,5 @@ namespace think; require __DIR__ . '/base.php'; // 执行应用 -Container::get('app')->initialize(); +Container::get('app', [__DIR__ . '/../application/'])->initialize(); Console::init(); -- Gitee From c53f2a2b3334ec331029559d6cece0d0737bb4db Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 17:46:10 +0800 Subject: [PATCH 0584/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E7=9A=84=E5=BC=82=E5=B8=B8=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index cf10b251..926fad57 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -418,8 +418,6 @@ abstract class Builder } } - $whereStr = ''; - // 解析查询表达式 foreach ($this->parser as $fun => $val) { if (in_array($exp, $val)) { @@ -428,6 +426,10 @@ abstract class Builder } } + if (!isset($whereStr)) { + throw new Exception('where express error:' . $exp); + } + return $whereStr; } -- Gitee From 6463cf79e893b66583f87d6969905e721dc5ef8c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 17:59:26 +0800 Subject: [PATCH 0585/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmodel=E7=B1=BB?= =?UTF-8?q?=E7=9A=84destroy=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 09e9fb13..b624a609 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -781,14 +781,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query = $model->db(); - if (is_array($data) && key($data) !== 0) { + if (empty($data) && 0 !== $data) { + return 0; + } elseif (is_array($data) && key($data) !== 0) { $query->where(self::parseWhere($data)); $data = null; } elseif ($data instanceof \Closure) { $data($query); $data = null; - } elseif (empty($data) && 0 !== $data) { - return 0; } $resultSet = $query->select($data); -- Gitee From c3c30be80cc4ce86acc5c5df9814fe38d0e06ef1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 18:25:00 +0800 Subject: [PATCH 0586/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3builder=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 926fad57..84830add 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -407,7 +407,7 @@ abstract class Builder $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; - if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { if (strpos($value, ':') !== 0 || !$query->isBind(substr($value, 1))) { if ($query->isBind($bindName)) { $bindName .= '_' . str_replace('.', '_', uniqid('', true)); -- Gitee From 760750c2ada9573c7b1e3fa0a87173ead7393526 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 18:44:56 +0800 Subject: [PATCH 0587/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BBuilder=E7=B1=BB=20?= =?UTF-8?q?=E5=8F=96=E6=B6=88parseValue=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 58 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 84830add..fb8be815 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -122,19 +122,36 @@ abstract class Builder $result[$item] = $val; } elseif (is_scalar($val)) { // 过滤非标量数据 - if (0 === strpos($val, ':') && $query->isBind(substr($val, 1))) { - $result[$item] = $val; - } else { - $key = str_replace('.', '_', $key); - $query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':data__' . $key; - } + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); } } return $result; } + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @param string $suffix + * @return string + */ + protected function parseDataBind(Query $query, $key, $data, $bind = [], $suffix = '') + { + // 过滤非标量数据 + if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) { + return $data; + } else { + $key = str_replace('.', '_', $key); + $name = 'data__' . $key . $suffix; + $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + return ':' . $name; + } + } + /** * 数组数据解析 * @access protected @@ -168,27 +185,6 @@ abstract class Builder return $key; } - /** - * value分析 - * @access protected - * @param Query $query 查询对象 - * @param mixed $value - * @param string $field - * @return string|array - */ - protected function parseValue(Query $query, $value, $field = '') - { - if (is_string($value)) { - $value = strpos($value, ':') === 0 && $query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); - } elseif (is_bool($value)) { - $value = $value ? '1' : '0'; - } elseif (is_null($value)) { - $value = 'null'; - } - - return $value; - } - /** * field分析 * @access protected @@ -1008,8 +1004,10 @@ abstract class Builder } else { $fields = $options['field']; } + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); - foreach ($dataSet as &$data) { + foreach ($dataSet as $k => &$data) { foreach ($data as $key => $val) { if (!in_array($key, $fields, true)) { if ($options['strict']) { @@ -1019,7 +1017,7 @@ abstract class Builder } elseif (is_null($val)) { $data[$key] = 'NULL'; } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($query, $val, $key); + $data[$key] = $this->parseDataBind($query, $key, $val, $bind, '_' . $k); } elseif (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 $data[$key] = $val->__toString(); -- Gitee From a00aa57bca2ec42df147b02d7cd06f8e22395c14 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 19:50:21 +0800 Subject: [PATCH 0588/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3like=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index fb8be815..51fc0880 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -445,15 +445,20 @@ abstract class Builder { // 模糊匹配 if (is_array($value)) { - foreach ($value as $item) { - $array[] = $key . ' ' . $exp . ' ' . $item; + foreach ($value as $k => $item) { + $bindKey = $bindName . '_' . $k; + $bind[$bindKey] = [$item, $bindType]; + $array[] = $key . ' ' . $exp . ' :' . $bindKey; } + $query->bind($bind); + $logic = isset($val[2]) ? $val[2] : 'AND'; $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; } else { $whereStr = $key . ' ' . $exp . ' ' . $value; } + return $whereStr; } -- Gitee From 4cab6b3b958b4d0f87c5e73a51d4e7aace5415da Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 28 Sep 2017 19:58:33 +0800 Subject: [PATCH 0589/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 51fc0880..6211e708 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -415,9 +415,9 @@ abstract class Builder } // 解析查询表达式 - foreach ($this->parser as $fun => $val) { - if (in_array($exp, $val)) { - $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindName, $bindType); + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindName, $bindType, isset($val[2]) ? $val[2] : 'AND'); break; } } @@ -439,9 +439,10 @@ abstract class Builder * @param string $field * @param string $bindName * @param integer $bindType + * @param string $logic * @return string */ - protected function parseLike(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + protected function parseLike(Query $query, $key, $exp, $value, $field, $bindName, $bindType, $logic) { // 模糊匹配 if (is_array($value)) { @@ -453,7 +454,6 @@ abstract class Builder $query->bind($bind); - $logic = isset($val[2]) ? $val[2] : 'AND'; $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; } else { $whereStr = $key . ' ' . $exp . ' ' . $value; -- Gitee From 09062efacf2be217595ede1605c7d3e7003d8805 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 11:08:59 +0800 Subject: [PATCH 0590/1384] =?UTF-8?q?console=E5=92=8Cstart=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=A7=BB=E5=87=BA=E6=A0=B8=E5=BF=83=20=E7=BA=B3?= =?UTF-8?q?=E5=85=A5=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console.php | 20 -------------------- start.php | 20 -------------------- 2 files changed, 40 deletions(-) delete mode 100644 console.php delete mode 100644 start.php diff --git a/console.php b/console.php deleted file mode 100644 index bdfc93dc..00000000 --- a/console.php +++ /dev/null @@ -1,20 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -// ThinkPHP 引导文件 -// 加载基础文件 -require __DIR__ . '/base.php'; - -// 执行应用 -Container::get('app', [__DIR__ . '/../application/'])->initialize(); -Console::init(); diff --git a/start.php b/start.php deleted file mode 100644 index b6c9883c..00000000 --- a/start.php +++ /dev/null @@ -1,20 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace think; - -// ThinkPHP 引导文件 -// 加载基础文件 -require __DIR__ . '/base.php'; - -// 支持事先使用静态方法设置Request对象和Config对象 - -// 执行应用并响应 -Container::get('app')->run()->send(); -- Gitee From 3f07dd96d30ee04fb5c3041297e60bd5f209ffa2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 11:51:17 +0800 Subject: [PATCH 0591/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDb=E7=B1=BB?= =?UTF-8?q?=E4=B8=BB=E9=94=AE=E5=88=A0=E9=99=A4=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 14 +++----------- library/think/db/Query.php | 10 +++++----- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 42f71d3c..e2fa0f81 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1108,17 +1108,9 @@ abstract class Connection if (isset($options['cache']) && is_string($options['cache']['key'])) { $key = $options['cache']['key']; - } - - if (!is_null($data) && true !== $data) { - if (!isset($key) && !is_array($data)) { - // 缓存标识 - $key = $this->getCacheKey($data, $options); - } - - // AR模式分析主键条件 - $query->parsePkWhere($data); - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + } elseif (!is_null($data) && true !== $data && !is_array($data)) { + $key = $this->getCacheKey($data, $options); + } elseif (is_string($pk) && isset($options['where']['AND'][$pk])) { $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 9bde6ec0..088b5fc8 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2175,13 +2175,13 @@ class Query { $this->parseOptions(); + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + if (!empty($this->options['soft_delete'])) { // 软删除 - if (!is_null($data) && true !== $data) { - // AR模式分析主键条件 - $this->parsePkWhere($data); - } - list($field, $condition) = $this->options['soft_delete']; unset($this->options['soft_delete']); $this->options['data'] = [$field => $condition]; -- Gitee From 0bee6822915fe30c4e7a81e5e9aa950759119e0f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 12:39:14 +0800 Subject: [PATCH 0592/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=9B=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E7=BB=91=E5=AE=9A=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 87a087ff..09219aa0 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -153,6 +153,22 @@ class Route return $this; } + /** + * 获取当前根域名 + * @access protected + * @return string + */ + protected function getRootDomain() + { + $root = $this->config->get('app.url_domain_root'); + if (!$root) { + $item = explode('.', $this->host); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + return $root; + } + /** * 注册域名路由 * @access public @@ -168,13 +184,7 @@ class Route $domain = is_array($name) ? array_shift($name) : $name; if ('*' != $domain && !strpos($domain, '.')) { - $root = $this->config->get('app.url_domain_root'); - if (!$root) { - $item = explode('.', $this->host); - $count = count($item); - $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; - } - $domain .= '.' . $root; + $domain .= '.' . $this->getRootDomain(); } $route = $this->config->get('url_lazy_route') ? $rule : null; @@ -252,7 +262,17 @@ class Route $domain = $this->domain; } - return isset($this->bind[$domain]) ? $this->bind[$domain] : null; + // TODO 泛三级域名支持 + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; } /** -- Gitee From 59342c4ebb0b97f33209e5200133bacca1c14bae Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 13:41:48 +0800 Subject: [PATCH 0593/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88BIND=5FMODULE?= =?UTF-8?q?=E5=B8=B8=E9=87=8F=20=E6=94=B9=E4=B8=BA=E5=9C=A8=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E6=96=87=E4=BB=B6=E4=BD=BF=E7=94=A8bind=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 9da3f605..ee4474eb 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -102,6 +102,11 @@ class App implements \ArrayAccess */ protected $container; + /** + * @var string 绑定模块(控制器) + */ + protected $bind; + public function __construct($appPath = '') { $this->container = Container::getInstance(); @@ -116,8 +121,22 @@ class App implements \ArrayAccess } + /** + * 绑定模块或者控制器 + * @access public + * @param string $bind + * @return $this + */ + public function bind($bind) + { + $this->bind = $bind; + return $this; + } + /** * 初始化应用 + * @access public + * @return void */ public function initialize() { @@ -252,9 +271,9 @@ class App implements \ArrayAccess $this->initialize(); try { - if (defined('BIND_MODULE')) { + if ($this->bind) { // 模块/控制器绑定 - BIND_MODULE && $this->route->bind(BIND_MODULE); + $this->route->bind($this->bind); } elseif ($this->config('app.auto_bind_module')) { // 入口自动绑定 $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); @@ -342,7 +361,6 @@ class App implements \ArrayAccess public function dispatch(Dispatch $dispatch) { $this->dispatch = $dispatch; - return $this; } @@ -406,7 +424,6 @@ class App implements \ArrayAccess public function routeMust($must = false) { $this->routeMust = $must; - return $this; } @@ -451,7 +468,6 @@ class App implements \ArrayAccess } $this->__set($guid, $class); - return $model; } @@ -711,7 +727,6 @@ class App implements \ArrayAccess public function setNamespace($namespace) { $this->namespace = $namespace; - return $this; } -- Gitee From 86cd35792d3132708d84b4c137e4b3e4129037f1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 14:16:11 +0800 Subject: [PATCH 0594/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 088b5fc8..6774be6f 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1230,15 +1230,7 @@ class Query } } elseif (is_null($op) && is_null($condition)) { if (is_array($field)) { - if (key($field) !== 0) { - $where = []; - foreach ($field as $key => $val) { - $where[$key] = [$key, '=', $val]; - } - } else { - // 数组批量查询 - $where = $field; - } + $where = $field; if (!empty($where)) { if (isset($this->options['where'][$logic])) { -- Gitee From 4b992ee6a2fd6bc7380c851ff306ced8b51798b6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 17:26:37 +0800 Subject: [PATCH 0595/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E7=9A=84=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/View.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/library/think/View.php b/library/think/View.php index 4957bc34..1db3698c 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -158,9 +158,13 @@ class View ob_implicit_flush(0); // 渲染输出 - $method = $renderContent ? 'display' : 'fetch'; - - $this->engine->$method($template, $vars, $config); + try { + $method = $renderContent ? 'display' : 'fetch'; + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } // 获取并清空缓存 $content = ob_get_clean(); -- Gitee From 2dbecb29df13732a94094007dc1546cbb2017d1b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 29 Sep 2017 17:47:25 +0800 Subject: [PATCH 0596/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E5=9F=BA=E7=B1=BB=E7=9A=84=E6=9E=B6=E6=9E=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index a18b7f92..a9119f1c 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -47,20 +47,17 @@ class Controller /** * 构造方法 - * @param Request $request Request对象 - * @param App $app App * @access public */ - public function __construct(Request $request, App $app) + public function __construct() { - $this->view = Container::get('view')->init( - $app['config']->pull('template'), - $app['config']->get('view_replace_str') + $this->request = Container::get('request'); + $this->app = Container::get('app'); + $this->view = Container::get('view')->init( + $this->app['config']->pull('template'), + $this->app['config']->get('view_replace_str') ); - $this->request = $request; - $this->app = $app; - // 控制器初始化 $this->initialize(); -- Gitee From 2a87fcdbdc4530d6631a340c74268ea2590af524 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 8 Oct 2017 12:34:10 +0800 Subject: [PATCH 0597/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BController=E7=B1=BB?= =?UTF-8?q?=E7=9A=84success=E5=92=8Cerror=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/traits/controller/Jump.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/traits/controller/Jump.php b/library/traits/controller/Jump.php index 25461519..c4cd03fa 100644 --- a/library/traits/controller/Jump.php +++ b/library/traits/controller/Jump.php @@ -51,7 +51,7 @@ trait Jump if ('html' == strtolower($type)) { $config = Container::get('config'); $result = Container::get('view') - ->init($config->get('template'), $config->get('view_replace_str')) + ->init($config->pull('template'), $config->get('view_replace_str')) ->fetch($config->get('dispatch_success_tmpl'), $result); } @@ -91,7 +91,7 @@ trait Jump if ('html' == strtolower($type)) { $config = Container::get('config'); $result = Container::get('view') - ->init($config->get('template'), $config->get('view_replace_str')) + ->init($config->pull('template'), $config->get('view_replace_str')) ->fetch($config->get('dispatch_error_tmpl'), $result); } @@ -115,7 +115,7 @@ trait Jump $result = [ 'code' => $code, 'msg' => $msg, - 'time' => $_SERVER['REQUEST_TIME'], + 'time' => time(), 'data' => $data, ]; -- Gitee From 573ed03d2f9e62a81abd34144b710d86ae77e1ca Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Oct 2017 18:16:26 +0800 Subject: [PATCH 0598/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AF=B9JSON-Handl?= =?UTF-8?q?e=E6=8F=92=E4=BB=B6=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tpl/think_exception.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpl/think_exception.tpl b/tpl/think_exception.tpl index 997bc778..19ecbdc1 100644 --- a/tpl/think_exception.tpl +++ b/tpl/think_exception.tpl @@ -292,7 +292,7 @@
-

[]

+

[

-- Gitee From 3d9bc4b9b0f5503c167a368344d6bfeb3dce42b3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 11 Oct 2017 16:37:38 +0800 Subject: [PATCH 0599/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index d71d0004..31761b98 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -11,6 +11,7 @@ namespace think\model\concern; +use think\Collection; use think\db\Query; use think\Loader; use think\Model; -- Gitee From 12f31b69a6ce73e2997cbe807d9bc4e2a02ebf20 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 12 Oct 2017 12:00:05 +0800 Subject: [PATCH 0600/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E7=9A=84=E7=A7=BB=E5=8A=A8=E7=AB=AF=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tpl/dispatch_jump.tpl | 1 + 1 file changed, 1 insertion(+) diff --git a/tpl/dispatch_jump.tpl b/tpl/dispatch_jump.tpl index 18ee01bd..583376bb 100644 --- a/tpl/dispatch_jump.tpl +++ b/tpl/dispatch_jump.tpl @@ -2,6 +2,7 @@ + 跳转提示

:)

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

'; + return '

:)

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

'; } } -- Gitee From 0742917f5b216f2b49fcfbf3ea4deff1d7eddddb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 31 Dec 2017 23:48:41 +0800 Subject: [PATCH 0772/1384] =?UTF-8?q?2018=E6=96=B0=E5=B9=B4=E5=BF=AB?= =?UTF-8?q?=E4=B9=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tpl/default_index.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpl/default_index.tpl b/tpl/default_index.tpl index db29c43c..740b1928 100644 --- a/tpl/default_index.tpl +++ b/tpl/default_index.tpl @@ -5,6 +5,6 @@ class Index{$suffix} { public function index() { - return '

:)

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

'; + return '

:) 2018新年快乐

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

'; } } -- Gitee From b6da4e36e7b7dcaaf4d35335e5b354d04b4852e8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 1 Jan 2018 20:28:30 +0800 Subject: [PATCH 0773/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Cookie=E7=B1=BB?= =?UTF-8?q?=E5=AD=98=E5=8F=96=E6=95=B0=E7=BB=84=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cookie.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 462c7c8c..c645fde6 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -106,7 +106,7 @@ class Cookie // 设置cookie if (is_array($value)) { - array_walk_recursive($value, $this->jsonFormatProtect, 'encode'); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode'); $value = 'think:' . json_encode($value); } @@ -186,7 +186,7 @@ class Cookie if (0 === strpos($value, 'think:')) { $value = substr($value, 6); $value = json_decode($value, true); - array_walk_recursive($value, $this->jsonFormatProtect, 'decode'); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode'); } } else { $value = null; -- Gitee From 2bbb864314c67460d383d1a6eb02214abcd9112e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 2 Jan 2018 10:44:09 +0800 Subject: [PATCH 0774/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Controller=E7=9A=84?= =?UTF-8?q?fetch=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index d7c85237..718c2d0b 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -115,13 +115,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 $this->view->fetch($template, $vars, $config); } /** @@ -129,13 +128,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 $this->view->display($content, $vars, $config); } /** -- Gitee From b4d172fe724383db620d3474e70579335524ef01 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 2 Jan 2018 11:15:17 +0800 Subject: [PATCH 0775/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/facade/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/facade/Session.php b/library/think/facade/Session.php index 62593ee8..77b90323 100644 --- a/library/think/facade/Session.php +++ b/library/think/facade/Session.php @@ -22,7 +22,7 @@ use think\Facade; * @method mixed get(string $name,string $prefix = null) static session获取 * @method mixed pull(string $name,string $prefix = null) static session获取并删除 * @method void push(string $key, mixed $value) static 添加数据到一个session数组 - * @method void delete(string $name, string $prefix = null) static 删除session数据 + * @method void set(string $name, mixed $value = '', string $prefix = null) static 设置session数据 * @method void flash(string $name, mixed $value = null) static session设置 下一次请求有效 * @method void flush() static 清空当前请求的session数据 * @method void delete(string $name, string $prefix = null) static 删除session数据 -- Gitee From 59ba9046c532d8d5325d4a90b9304e8f3a2e0ca8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 2 Jan 2018 11:24:09 +0800 Subject: [PATCH 0776/1384] =?UTF-8?q?Session=E7=B1=BBset=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=BB=98=E8=AE=A4=E5=80=BC=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 2 +- library/think/facade/Session.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Session.php b/library/think/Session.php index c9dd4b72..5987c6e5 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -178,7 +178,7 @@ class Session * @param string|null $prefix 作用域(前缀) * @return void */ - public function set($name, $value = '', $prefix = null) + public function set($name, $value, $prefix = null) { $this->lock(); // lock必须先于 $this->boot() diff --git a/library/think/facade/Session.php b/library/think/facade/Session.php index 77b90323..9aad0b69 100644 --- a/library/think/facade/Session.php +++ b/library/think/facade/Session.php @@ -22,7 +22,7 @@ use think\Facade; * @method mixed get(string $name,string $prefix = null) static session获取 * @method mixed pull(string $name,string $prefix = null) static session获取并删除 * @method void push(string $key, mixed $value) static 添加数据到一个session数组 - * @method void set(string $name, mixed $value = '', string $prefix = null) static 设置session数据 + * @method void set(string $name, mixed $value , string $prefix = null) static 设置session数据 * @method void flash(string $name, mixed $value = null) static session设置 下一次请求有效 * @method void flush() static 清空当前请求的session数据 * @method void delete(string $name, string $prefix = null) static 删除session数据 -- Gitee From d7678af0b9a70b53fa4a9c46d9785e0694a14056 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 2 Jan 2018 11:57:30 +0800 Subject: [PATCH 0777/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=A8=E5=9F=9F?= =?UTF-8?q?=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index c967df22..16050411 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -381,10 +381,6 @@ abstract class Rule { if (!empty($this->option['cross_domain'])) { - if ($request->method(true) == 'OPTIONS') { - return new ResponseDispatch(Response::create()->code(204)); - } - $header = [ 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE', @@ -396,6 +392,10 @@ abstract class Rule } $this->option['header'] = $header; + + if ($request->method(true) == 'OPTIONS') { + return new ResponseDispatch(Response::create()->code(204)->header($header)); + } } } -- Gitee From 249c0b3f644fc316673065a840d4c7779117b3a7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 2 Jan 2018 17:30:23 +0800 Subject: [PATCH 0778/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3insertAll=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 7 ++++--- library/think/db/builder/Mysql.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ca789f87..ef031e17 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -86,9 +86,10 @@ abstract class Builder * @param array $data 数据 * @param array $fields 字段信息 * @param array $bind 参数绑定 + * @param string $suffix 参数绑定后缀 * @return array */ - protected function parseData(Query $query, $data = [], $fields = [], $bind = []) + protected function parseData(Query $query, $data = [], $fields = [], $bind = [], $suffix = '') { if (empty($data)) { return []; @@ -141,7 +142,7 @@ abstract class Builder } } elseif (is_scalar($val)) { // 过滤非标量数据 - $result[$item] = $this->parseDataBind($query, $key, $val, $bind); + $result[$item] = $this->parseDataBind($query, $key, $val, $bind, $suffix); } } @@ -1012,7 +1013,7 @@ abstract class Builder $bind = $this->connection->getFieldsBind($options['table']); foreach ($dataSet as $k => $data) { - $data = $this->parseData($query, $data, $allowFields, $bind); + $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k); $values[] = 'SELECT ' . implode(',', array_values($data)); diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index d1e932a5..35d76d6b 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -59,7 +59,7 @@ class Mysql extends Builder $bind = $this->connection->getFieldsBind($options['table']); foreach ($dataSet as $k => $data) { - $data = $this->parseData($query, $data, $allowFields, $bind); + $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k); $values[] = '( ' . implode(',', array_values($data)) . ' )'; -- Gitee From 06e4fa1dc5ee9f96d0340435101991640b80dee7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 2 Jan 2018 17:56:18 +0800 Subject: [PATCH 0779/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bchunk=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 6a10ce82..785f808d 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2666,7 +2666,7 @@ class Query $resultSet = $query->order($column, $order)->select(); - while (!empty($resultSet)) { + while (count($resultSet) > 0) { if ($resultSet instanceof Collection) { $resultSet = $resultSet->all(); } -- Gitee From 9080278dd01c3448add8eb7a9463a5137a071ac7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 3 Jan 2018 11:38:09 +0800 Subject: [PATCH 0780/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 3a271ee4..c52878d9 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.0'; + const VERSION = '5.1.1'; /** * 当前模块路径 -- Gitee From 42215c7fc4794f8446eee09e1dbe13d1fa5fd8eb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 4 Jan 2018 17:35:11 +0800 Subject: [PATCH 0781/1384] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 20 ++++++++++---------- library/think/Model.php | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index 986c50ce..21770e08 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -14,17 +14,17 @@ namespace think; /** * 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 table(string $table) static 指定数据表(含前缀) + * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) + * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = 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 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 cache(mixed $key = null , integer $expire = 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,7 +36,7 @@ namespace think; * @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 用于非自动提交状态下面的查询提交 diff --git a/library/think/Model.php b/library/think/Model.php index bb3347ad..4ffa0633 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -764,7 +764,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param mixed $data 主键值或者查询条件(闭包) * @param mixed $with 关联预查询 * @param bool $cache 是否缓存 - * @return Model|null + * @return static|null * @throws exception\DbException */ public static function get($data, $with = [], $cache = false) -- Gitee From 65859ee90e4e0b1f0b63cb77e5f03b99243eed4c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 5 Jan 2018 14:40:54 +0800 Subject: [PATCH 0782/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B5=8C=E5=A5=97?= =?UTF-8?q?=E5=88=86=E7=BB=84=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 22 +++++++++++----------- library/think/route/RuleItem.php | 4 ++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 269c90d1..b71664dc 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -459,28 +459,28 @@ class Route $method = strtolower($method); - // 当前分组名 - $group = $this->group->getName(); - if ($group) { - $rule = $group . '/' . $rule; - } + // 创建路由规则实例 + $ruleItem = new RuleItem($this, $this->group, $rule, $route, $method, $option, $pattern); if (isset($name)) { + // 当前分组名 + $group = $this->group->getName(); + + if ($group) { + $rule = $group . '/' . $rule; + } // 设置路由标识 用于URL快速生成 $this->setRuleName($rule, $name, $option); } - // 创建路由规则实例 - $rule = new RuleItem($this, $this->group, $rule, $route, $method, $option, $pattern); - // 添加到当前分组 - $this->group->addRule($rule, $method); + $this->group->addRule($ruleItem, $method); if (!empty($option['cross_domain'])) { - $this->setCrossDomainRule($rule, $method); + $this->setCrossDomainRule($ruleItem, $method); } - return $rule; + return $ruleItem; } /** diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 9cd5e787..4dbd9a84 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -104,6 +104,10 @@ class RuleItem extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { + if ($this->parent && $groupName = $this->parent->getName()) { + $this->name = $groupName . '/' . $this->name; + } + if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 return $dispatch; -- Gitee From 1d20efd21287828d131ff92acfb81a13c6470931 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 5 Jan 2018 15:09:53 +0800 Subject: [PATCH 0783/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E8=A1=A8=E8=BE=BE=E5=BC=8F=E8=AF=AD=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 14 ++++++++------ library/think/template/TagLib.php | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index e23c7b93..b97c8390 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -85,12 +85,14 @@ class Template */ public function __construct(array $config = []) { - $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; - $this->config = array_merge($this->config, $config); - $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); - $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); - $this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']); - $this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']); + $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; + $this->config = array_merge($this->config, $config); + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); + $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); + $this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']); + $this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']); // 初始化模板编译存储器 $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; diff --git a/library/think/template/TagLib.php b/library/think/template/TagLib.php index 23cbb9dc..3653b7d2 100644 --- a/library/think/template/TagLib.php +++ b/library/think/template/TagLib.php @@ -275,8 +275,8 @@ class TagLib if (!empty($this->tags[$name]['expression'])) { static $_taglibs; if (!isset($_taglibs[$name])) { - $_taglibs[$name][0] = strlen(ltrim($this->tpl->config('taglib_begin'), '\\') . $name); - $_taglibs[$name][1] = strlen(ltrim($this->tpl->config('taglib_end'), '\\')); + $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); } $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); // 清除自闭合标签尾部/ -- Gitee From 51d22abb225598c822d3af416afdb8b05e33af40 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 5 Jan 2018 17:29:00 +0800 Subject: [PATCH 0784/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=87=AA=E5=85=B3?= =?UTF-8?q?=E8=81=94=E6=9F=A5=E8=AF=A2=E7=9A=84=E5=A4=9A=E7=BA=A7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index c6bfa308..682988ed 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -548,7 +548,7 @@ trait RelationShip */ protected function getRelationData(Relation $modelRelation) { - if ($this->parent && get_class($this->parent) == $modelRelation->getModel()) { + if ($this->parent && $modelRelation->getModel() == $this->parent) { $value = $this->parent; } else { // 获取关联数据 -- Gitee From 90525f26e34f0b84d2ecf0dbaa32bec6aed4f9f0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 5 Jan 2018 17:53:08 +0800 Subject: [PATCH 0785/1384] =?UTF-8?q?=E5=85=B3=E8=81=94=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0selfRelation=E7=94=A8=E4=BA=8E=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E6=98=AF=E5=90=A6=E8=87=AA=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Relation.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index 79016248..315fb28d 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -35,6 +35,8 @@ abstract class Relation protected $localKey; // 基础查询 protected $baseQuery; + // 是否为自关联 + protected $selfRelation; /** * 获取关联的所属模型 @@ -56,6 +58,28 @@ abstract class Relation return $this->query->getModel(); } + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + /** * 封装关联数据集 * @access public -- Gitee From 15da6febf17be98472c6651df98d0ee70bbd9254 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 5 Jan 2018 17:53:45 +0800 Subject: [PATCH 0786/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index 682988ed..b3966744 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -548,7 +548,7 @@ trait RelationShip */ protected function getRelationData(Relation $modelRelation) { - if ($this->parent && $modelRelation->getModel() == $this->parent) { + if ($this->parent && $modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) { $value = $this->parent; } else { // 获取关联数据 -- Gitee From b8461053952fea219c4590698a69b3d57b6bd96b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 5 Jan 2018 17:54:20 +0800 Subject: [PATCH 0787/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index b3966744..51611958 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -548,7 +548,7 @@ trait RelationShip */ protected function getRelationData(Relation $modelRelation) { - if ($this->parent && $modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) { $value = $this->parent; } else { // 获取关联数据 -- Gitee From 2d45bc9ca0da5813573fe47a1289041b89edd0dd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 6 Jan 2018 13:44:53 +0800 Subject: [PATCH 0788/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=9A=84null=E6=9D=A1=E4=BB=B6=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 785f808d..ee6701e9 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1357,7 +1357,11 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; + if (is_null($val)) { + $where[$key] = [$key, 'null', '']; + } else { + $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; + } } } else { // 数组批量查询 -- Gitee From 037d2359f52d9f7d5e107d214fd6780480e3d831 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 6 Jan 2018 22:23:27 +0800 Subject: [PATCH 0789/1384] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E6=94=AF=E6=8C=81=E4=B8=89=E7=BA=A7=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=AF=B9=E8=B1=A1=E6=94=AF=E6=8C=81ArrayAcce?= =?UTF-8?q?ss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 56 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/library/think/Config.php b/library/think/Config.php index 3d4e32fb..1f7ca652 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -11,7 +11,7 @@ namespace think; -class Config +class Config implements \ArrayAccess { /** * 配置参数 @@ -181,7 +181,7 @@ class Config /** * 设置配置参数 name为数组则为批量设置 * @access public - * @param string|array $name 配置参数名(支持二级配置 .号分割) + * @param string|array $name 配置参数名(支持三级配置 .号分割) * @param mixed $value 配置值 * @return mixed */ @@ -192,9 +192,14 @@ class Config $name = $this->prefix . '.' . $name; } - $name = explode('.', $name); + $name = explode('.', $name, 3); + + if (count($name) == 2) { + $this->config[strtolower($name[0])][$name[1]] = $value; + } else { + $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value; + } - $this->config[strtolower($name[0])][$name[1]] = $value; return $value; } elseif (is_array($name)) { // 批量设置 @@ -217,6 +222,29 @@ class Config return $result; } + /** + * 重置配置参数 + * @access public + * @param string $name 配置参数名(支持三级配置 .号分割) + * @return true + */ + public function remove($name) + { + if (!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]]); + } + + return true; + } + /** * 重置配置参数 * @access public @@ -265,4 +293,24 @@ class Config 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); + } } -- Gitee From 38416ca7cb9149e897e2c8c2fb3dcf4d39271d33 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 6 Jan 2018 22:26:08 +0800 Subject: [PATCH 0790/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Config.php b/library/think/Config.php index 1f7ca652..75b0457c 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -223,7 +223,7 @@ class Config implements \ArrayAccess } /** - * 重置配置参数 + * 移除配置 * @access public * @param string $name 配置参数名(支持三级配置 .号分割) * @return true -- Gitee From 03ee72746efd0bdde083449bc3ce4564a893a3dd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 6 Jan 2018 22:35:28 +0800 Subject: [PATCH 0791/1384] =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=80=BC=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/Config.php b/library/think/Config.php index 75b0457c..e30471a5 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -226,7 +226,7 @@ class Config implements \ArrayAccess * 移除配置 * @access public * @param string $name 配置参数名(支持三级配置 .号分割) - * @return true + * @return void */ public function remove($name) { @@ -241,8 +241,6 @@ class Config implements \ArrayAccess } else { unset($this->config[strtolower($name[0])][$name[1]][$name[2]]); } - - return true; } /** -- Gitee From 4cb4eaf2ad5a50cf01515851509e147a87b7cf43 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 7 Jan 2018 21:57:04 +0800 Subject: [PATCH 0792/1384] =?UTF-8?q?App=E7=B1=BB=E5=A2=9E=E5=8A=A0path?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=94=A8=E4=BA=8E=E8=AE=BE=E7=BD=AE=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E7=9B=AE=E5=BD=95=20=E5=8F=AA=E8=83=BD=E5=9C=A8?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E6=B3=95=E4=B9=8B=E5=89=8D?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index c52878d9..3282dcdc 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -126,16 +126,7 @@ class App implements \ArrayAccess public function __construct($appPath = '') { - $this->container = Container::getInstance(); - $this->beginTime = microtime(true); - $this->beginMem = memory_get_usage(); - $this->thinkPath = dirname(dirname(__DIR__)) . '/'; - $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; - $this->rootPath = dirname(realpath($this->appPath)) . '/'; - $this->runtimePath = $this->rootPath . 'runtime/'; - $this->routePath = $this->rootPath . 'route/'; - $this->configPath = $this->rootPath . 'config/'; - + $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; } /** @@ -150,6 +141,18 @@ class App implements \ArrayAccess return $this; } + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path; + return $this; + } + /** * 初始化应用 * @access public @@ -157,6 +160,15 @@ class App implements \ArrayAccess */ public function initialize() { + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + $this->container = Container::getInstance(); + $this->thinkPath = dirname(dirname(__DIR__)) . '/'; + $this->rootPath = dirname(realpath($this->appPath)) . '/'; + $this->runtimePath = $this->rootPath . 'runtime/'; + $this->routePath = $this->rootPath . 'route/'; + $this->configPath = $this->rootPath . 'config/'; + // 设置路径环境变量 $this->env->set([ 'think_path' => $this->thinkPath, @@ -167,7 +179,6 @@ class App implements \ArrayAccess 'runtime_path' => $this->runtimePath, 'extend_path' => $this->rootPath . 'extend/', 'vendor_path' => $this->rootPath . 'vendor/', - ]); // 加载环境变量配置文件 -- Gitee From a377095b493db9e395b7c5a921b0eb5078fae4fe Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 8 Jan 2018 12:49:32 +0800 Subject: [PATCH 0793/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Query=E7=B1=BB?= =?UTF-8?q?=E4=B8=80=E5=A4=84=E5=8F=AF=E8=83=BD=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index ee6701e9..af5fc616 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -931,7 +931,7 @@ class Query } if (isset($this->options['field'])) { - $field = array_merge($this->options['field'], $field); + $field = array_merge((array) $this->options['field'], $field); } $this->options['field'] = array_unique($field); @@ -2845,8 +2845,10 @@ class Query $options['field'] = '*'; } - if (!isset($options['data'])) { - $options['data'] = []; + foreach (['data', 'order'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } } if (!isset($options['strict'])) { @@ -2859,7 +2861,7 @@ class Query } } - foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + foreach (['join', 'union', 'group', 'having', 'limit', 'force', 'comment'] as $name) { if (!isset($options[$name])) { $options[$name] = ''; } -- Gitee From da0c4bdbca02b1a83753bc9e98911e19ba864deb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 8 Jan 2018 15:09:28 +0800 Subject: [PATCH 0794/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BparseOrder=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ef031e17..b7be1a36 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -793,6 +793,10 @@ abstract class Builder */ protected function parseOrder(Query $query, $order) { + if (empty($order)) { + return ''; + } + if (is_array($order)) { $array = []; @@ -814,7 +818,7 @@ abstract class Builder $order = implode(',', $array); } - return !empty($order) ? ' ORDER BY ' . $order : ''; + return ' ORDER BY ' . $order; } /** -- Gitee From 0f2f83cd8b21bcb63500e8ee71d1e904a731f9d4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 8 Jan 2018 15:35:21 +0800 Subject: [PATCH 0795/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 3282dcdc..a190cce0 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.1'; + const VERSION = '5.1.2'; /** * 当前模块路径 -- Gitee From 83681a87770c1731d7b99410bc31372017664cad Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 9 Jan 2018 19:41:37 +0800 Subject: [PATCH 0796/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0env=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/helper.php b/helper.php index 3f44e465..1124845f 100644 --- a/helper.php +++ b/helper.php @@ -21,6 +21,7 @@ use think\facade\Cache; use think\facade\Config; use think\facade\Cookie; use think\facade\Debug; +use think\facade\Env; use think\facade\Hook; use think\facade\Lang; use think\facade\Log; @@ -315,6 +316,20 @@ if (!function_exists('dump')) { } } +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env($name = null, $default = null) + { + return Env::get($name, $default); + } +} + if (!function_exists('exception')) { /** * 抛出异常处理 -- Gitee From 11a088ced372473a084916b36116eef03aff084c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 10 Jan 2018 09:38:52 +0800 Subject: [PATCH 0797/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index a190cce0..4b83db71 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -126,7 +126,8 @@ class App implements \ArrayAccess public function __construct($appPath = '') { - $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; + $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; + $this->container = Container::getInstance(); } /** @@ -162,7 +163,6 @@ class App implements \ArrayAccess { $this->beginTime = microtime(true); $this->beginMem = memory_get_usage(); - $this->container = Container::getInstance(); $this->thinkPath = dirname(dirname(__DIR__)) . '/'; $this->rootPath = dirname(realpath($this->appPath)) . '/'; $this->runtimePath = $this->rootPath . 'runtime/'; -- Gitee From d21760b82c551036e4fbc127473fd4336b9382d7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 10 Jan 2018 11:45:45 +0800 Subject: [PATCH 0798/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=94=9F=E6=88=90=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=92=8C=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=E5=8A=A0=E8=BD=BD=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 43 ++++++++++--------- .../think/console/command/optimize/Config.php | 9 +++- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index a190cce0..bd6bac4f 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -225,9 +225,6 @@ class App implements \ArrayAccess // 注册类库别名 Loader::addClassAlias($this->config->pull('alias')); - // 加载系统助手函数 - include $this->thinkPath . 'helper.php'; - // 设置系统时区 date_default_timezone_set($this->config('app.default_timezone')); @@ -253,23 +250,6 @@ class App implements \ArrayAccess } elseif (is_file($this->runtimePath . $module . 'init.php')) { include $this->runtimePath . $module . 'init.php'; } else { - // 自动读取配置文件 - if (is_dir($path . 'config')) { - $dir = $path . 'config'; - } elseif (is_dir($this->configPath . $module)) { - $dir = $this->configPath . $module; - } - - if (isset($dir)) { - $files = scandir($dir); - foreach ($files as $file) { - if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { - $filename = $dir . DIRECTORY_SEPARATOR . $file; - $this->config->load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - // 加载行为扩展文件 if (is_file($path . 'tags.php')) { $this->hook->import(include $path . 'tags.php'); @@ -280,10 +260,31 @@ class App implements \ArrayAccess include $path . 'common.php'; } - // 注册服务和容器对象实例 + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 注册服务的容器对象实例 if (is_file($path . 'provider.php')) { $this->container->bind(include $path . 'provider.php'); } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config'; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $filename = $dir . DIRECTORY_SEPARATOR . $file; + $this->config->load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } } $this->request->filter($this->config('app.default_filter')); diff --git a/library/think/console/command/optimize/Config.php b/library/think/console/command/optimize/Config.php index 71ef9dd4..da45dc97 100644 --- a/library/think/console/command/optimize/Config.php +++ b/library/think/console/command/optimize/Config.php @@ -31,7 +31,7 @@ class Config extends Command protected function execute(Input $input, Output $output) { - if ($input->hasArgument('module')) { + if ($input->getArgument('module')) { $module = $input->getArgument('module') . DIRECTORY_SEPARATOR; } else { $module = ''; @@ -60,7 +60,8 @@ class Config extends Command $ext = App::getConfigExt(); $config = Container::get('config'); - $files = scandir($configPath); + $files = is_dir($configPath) ? scandir($configPath) : []; + foreach ($files as $file) { if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $ext) { $filename = $configPath . DIRECTORY_SEPARATOR . $file; @@ -81,6 +82,10 @@ class Config extends Command } } + if ('' == $module) { + $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; + } + if (is_file($path . 'provider.php')) { $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export(include $path . 'provider.php' ?: [], true) . ');' . PHP_EOL; } -- Gitee From 4a9ae56a5db7bcc56495dd26ea27ceccadb5ec52 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 10 Jan 2018 11:53:39 +0800 Subject: [PATCH 0799/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../think/console/command/optimize/Route.php | 19 +++++++++---------- .../think/console/command/optimize/Schema.php | 6 ++++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/library/think/console/command/optimize/Route.php b/library/think/console/command/optimize/Route.php index e3e9d240..0b188583 100644 --- a/library/think/console/command/optimize/Route.php +++ b/library/think/console/command/optimize/Route.php @@ -39,16 +39,15 @@ class Route extends Command // 路由检测 $path = Container::get('app')->getRoutePath(); - $files = scandir($path); - if (!empty($files)) { - foreach ($files as $file) { - if (strpos($file, '.php')) { - $filename = $path . DIRECTORY_SEPARATOR . $file; - // 导入路由配置 - $rules = include $filename; - if (is_array($rules)) { - Container::get('route')->import($rules); - } + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + Container::get('route')->import($rules); } } } diff --git a/library/think/console/command/optimize/Schema.php b/library/think/console/command/optimize/Schema.php index 4c2cff92..17997013 100644 --- a/library/think/console/command/optimize/Schema.php +++ b/library/think/console/command/optimize/Schema.php @@ -40,7 +40,8 @@ class Schema extends Command if ($input->hasOption('module')) { $module = $input->getOption('module'); // 读取模型 - $list = scandir(App::getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'); + $path = App::getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'; + $list = is_dir($path) ? scandir($path) : []; $namespace = App::getNamespace(); foreach ($list as $file) { @@ -65,7 +66,8 @@ class Schema extends Command $tables = Db::getConnection()->getTables($dbName); } elseif (!\think\facade\Config::get('app_multi_module')) { $namespace = App::getNamespace(); - $list = scandir(App::getAppPath() . 'model'); + $path = App::getAppPath() . 'model'; + $list = is_dir($path) ? scandir($path) : []; foreach ($list as $file) { if (0 === strpos($file, '.')) { -- Gitee From 03c9384ee8cac228790773c3af3d13c0342c74a3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 10 Jan 2018 12:27:16 +0800 Subject: [PATCH 0800/1384] =?UTF-8?q?Session=E7=B1=BBget=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E7=BA=A7=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/library/think/Session.php b/library/think/Session.php index 5987c6e5..247d272e 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -216,25 +216,21 @@ class Session $this->lock(); // lock必须先于 $this->boot() empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->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; + $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; + } } } -- Gitee From c2dfa75d7df9934b3bc03d75d905165ba2306b81 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 10 Jan 2018 22:44:36 +0800 Subject: [PATCH 0801/1384] =?UTF-8?q?Request=E7=B1=BBonly=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=94=AF=E6=8C=81=E6=8C=87=E5=AE=9A=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=20only(['name'=3D>'default=5Fvalue','type'])?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index c2802fe4..02192408 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1366,9 +1366,19 @@ class Request } $item = []; - foreach ($name as $key) { + foreach ($name as $key => $item) { + $default = null; + + if (is_int($key)) { + $key = $item; + } else { + $default = $item; + } + if (isset($param[$key])) { $item[$key] = $param[$key]; + } elseif (!is_null($default)) { + $item[$key] = $default; } } -- Gitee From 1e3af4fb9395c03227ec4f3438b8ea0c5a28224a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 12:01:51 +0800 Subject: [PATCH 0802/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 4dbd9a84..93b5bcd7 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -105,7 +105,7 @@ class RuleItem extends Rule public function check($request, $url, $depr = '/', $completeMatch = false) { if ($this->parent && $groupName = $this->parent->getName()) { - $this->name = $groupName . '/' . $this->name; + $this->name = $groupName . ($this->name ? '/' . $this->name : ''); } if ($dispatch = $this->checkCrossDomain($request)) { -- Gitee From 1d5fa474e265d59fcf769379c421885c499395fb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 13:12:14 +0800 Subject: [PATCH 0803/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E7=9B=AE=E5=BD=95=E6=96=9C=E6=9D=86=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=20=E5=B9=B6=E6=94=AF=E6=8C=81=20removeSlash=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=AE=BE=E7=BD=AE=E4=B8=8D=E5=8C=BA=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 13 ++++++++++++- library/think/route/RuleItem.php | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 16050411..3b93d255 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -81,7 +81,7 @@ abstract class Rule */ public function name($name) { - $this->name = '/' != $name ? trim($name, '/') : '/'; + $this->name = '/' != $name ? ltrim($name, '/') : '/'; return $this; } @@ -351,6 +351,17 @@ abstract class Rule return $this->option('complete_match', $match); } + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove + * @return $this + */ + public function removeSlash($remove = true) + { + return $this->option('remove_slash', $remove); + } + /** * 设置是否允许跨域 * @access public diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 93b5bcd7..3373da71 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -122,6 +122,11 @@ class RuleItem extends Rule $this->mergeGroupOptions(); $option = $this->option; + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->name) { + $this->name = rtrim($this->name, '/'); + } + // 检查前置行为 if (isset($option['before']) && false === $this->checkBefore($option['before'])) { return false; -- Gitee From f5c36a4ceb44dc14fb203c624c43e875396a3644 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 13:22:02 +0800 Subject: [PATCH 0804/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BURL=E5=AF=B9?= =?UTF-8?q?=E6=96=9C=E6=9D=86=E7=BB=93=E5=B0=BE=E7=9A=84=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=94=9F=E6=88=90=20=E4=B8=8D=E8=87=AA=E5=8A=A8=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0url=E5=90=8E=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/Url.php b/library/think/Url.php index 84c7b0e3..17b7b678 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -164,7 +164,11 @@ class Url $url = str_replace('/', $depr, $url); // URL后缀 - $suffix = in_array($url, ['/', '']) ? '' : $this->parseSuffix($suffix); + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } // 锚点 $anchor = !empty($anchor) ? '#' . $anchor : ''; -- Gitee From b2ab9646acc87b2e862086ff9dd329dd31d96b5b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 14:38:19 +0800 Subject: [PATCH 0805/1384] =?UTF-8?q?Route=E7=B1=BB=E5=A2=9E=E5=8A=A0view?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8F=AF=E4=BB=A5=E8=B7=AF=E7=94=B1=EF=BC=88?= =?UTF-8?q?=E4=BB=85=E6=94=AF=E6=8C=81get=E8=AF=B7=E6=B1=82=EF=BC=89?= =?UTF-8?q?=E5=88=B0=E6=A8=A1=E6=9D=BF=E6=96=87=E4=BB=B6=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 15 ++++++++++++++ library/think/route/Rule.php | 14 ++++++++++++++ library/think/route/dispatch/View.php | 28 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 library/think/route/dispatch/View.php diff --git a/library/think/Route.php b/library/think/Route.php index b71664dc..4865075a 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -720,6 +720,21 @@ class Route return $this; } + /** + * 注册视图路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return $this + */ + public function view($rule, $template = '', $vars = [], $option = [], $pattern = []) + { + return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); + } + /** * 注册别名路由 * @access public diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 3b93d255..2cbff9b3 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -20,6 +20,7 @@ use think\route\dispatch\Controller as ControllerDispatch; use think\route\dispatch\Module as ModuleDispatch; use think\route\dispatch\Redirect as RedirectDispatch; use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\View as ViewDispatch; abstract class Rule { @@ -340,6 +341,17 @@ abstract class Rule return $this->option('pjax', $pjax); } + /** + * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量 + * @access public + * @param bool|array $view + * @return $this + */ + public function view($view = true) + { + return $this->option('view', $view); + } + /** * 设置路由完整匹配 * @access public @@ -691,6 +703,8 @@ abstract class Rule $result = new CallbackDispatch($route); } elseif ($route instanceof Response) { $result = new ResponseDispatch($route); + } elseif (isset($option['view']) && false !== $option['view']) { + $result = new ViewDispatch($route, is_array($option['view']) ? $option['view'] : []); } elseif (0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301); diff --git a/library/think/route/dispatch/View.php b/library/think/route/dispatch/View.php new file mode 100644 index 00000000..85ef9d28 --- /dev/null +++ b/library/think/route/dispatch/View.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Container; +use think\route\Dispatch; + +class View extends Dispatch +{ + public function run() + { + // 渲染模板输出 + $vars = array_merge($this->app['request']->param(), $this->param); + + return Container::get('view') + ->init(Container::get('config')->pull('template')) + ->fetch($this->dispatch, $vars); + } +} -- Gitee From 5c12c40caab9a4f8536f595ac8dacc784b5d0a02 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 14:42:12 +0800 Subject: [PATCH 0806/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index 4865075a..140437ac 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -728,7 +728,7 @@ class Route * @param array $vars 模板变量 * @param array $option 路由参数 * @param array $pattern 变量规则 - * @return $this + * @return RuleItem */ public function view($rule, $template = '', $vars = [], $option = [], $pattern = []) { -- Gitee From 81cccaa4458505cea2e2105a84421ac01214ec7d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 14:59:00 +0800 Subject: [PATCH 0807/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0route::redirect?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=94=A8=E4=BA=8E=E9=87=8D=E5=AE=9A=E5=90=91?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 15 +++++++++++++++ library/think/route/Rule.php | 13 ++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index 140437ac..db9503f0 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -735,6 +735,21 @@ class Route return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); } + /** + * 注册重定向路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $status 状态码 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function redirect($rule, $route = '', $status = 301, $option = [], $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status); + } + /** * 注册别名路由 * @access public diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 2cbff9b3..fbff40a3 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -352,6 +352,17 @@ abstract class Rule return $this->option('view', $view); } + /** + * 当前路由为重定向 + * @access public + * @param bool $redirect 是否为重定向 + * @return $this + */ + public function redirect($redirect = true) + { + return $this->option('redirect', $redirect); + } + /** * 设置路由完整匹配 * @access public @@ -705,7 +716,7 @@ abstract class Rule $result = new ResponseDispatch($route); } elseif (isset($option['view']) && false !== $option['view']) { $result = new ViewDispatch($route, is_array($option['view']) ? $option['view'] : []); - } elseif (0 === strpos($route, '/') || strpos($route, '://')) { + } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301); } elseif (false !== strpos($route, '\\')) { -- Gitee From a9d9fd397eb55695fd24a4c67742fc202052e4ea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 15:34:24 +0800 Subject: [PATCH 0808/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=97=AD=E5=8C=85=E7=9A=84=E6=83=85=E5=86=B5=20=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=BC=93=E5=AD=98=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index b1dfa8fb..6437fb1e 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -2029,7 +2029,11 @@ abstract class Connection if (isset($data)) { return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; } else { - return md5(serialize($options) . serialize($bind)); + try { + return md5(serialize($options) . serialize($bind)); + } catch (\Exception $e) { + return; + } } } -- Gitee From 284f5c4cdaca8a3405f9a63d51dfd0cf7be1c198 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 16:19:40 +0800 Subject: [PATCH 0809/1384] =?UTF-8?q?=E8=A7=86=E5=9B=BE=E5=A2=9E=E5=8A=A0f?= =?UTF-8?q?ilter=E6=96=B9=E6=B3=95=E7=94=A8=E4=BA=8E=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E8=BE=93=E5=87=BA=E8=BF=87=E6=BB=A4=20view?= =?UTF-8?q?=E5=8A=A9=E6=89=8B=E5=87=BD=E6=95=B0=E5=A2=9E=E5=8A=A0filer?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 5 +++-- library/think/Controller.php | 13 +++++++++++++ library/think/View.php | 23 +++++++++++++++++++++-- library/think/facade/View.php | 2 +- library/think/response/View.php | 18 ++++++++++++++++-- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/helper.php b/helper.php index 1124845f..9c711c50 100644 --- a/helper.php +++ b/helper.php @@ -636,11 +636,12 @@ if (!function_exists('view')) { * @param string $template 模板文件 * @param array $vars 模板变量 * @param integer $code 状态码 + * @param callable $filer 内容过滤 * @return \think\response\View */ - function view($template = '', $vars = [], $code = 200) + function view($template = '', $vars = [], $code = 200, $filter = null) { - return Response::create($template, 'view', $code)->assign($vars); + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); } } diff --git a/library/think/Controller.php b/library/think/Controller.php index 718c2d0b..cc3953d2 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -150,6 +150,19 @@ class Controller return $this; } + /** + * 视图过滤 + * @access protected + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + protected function filter($filter) + { + $this->view->filter($filter); + + return $this; + } + /** * 初始化模板引擎 * @access protected diff --git a/library/think/View.php b/library/think/View.php index 82e90342..3a2d3cc3 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -25,6 +25,12 @@ class View */ protected $data = []; + /** + * 内容过滤 + * @var mixed + */ + protected $filer; + /** * 全局模板变量 * @var array @@ -132,6 +138,18 @@ class View return $this->engine->exists($name); } + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter($filter) + { + $this->filter = $filter; + return $this; + } + /** * 解析和获取模板内容 用于输出 * @access public @@ -163,8 +181,9 @@ class View // 获取并清空缓存 $content = ob_get_clean(); - // 内容过滤标签 - Container::get('hook')->listen('view_filter', $content); + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); + } return $content; } diff --git a/library/think/facade/View.php b/library/think/facade/View.php index 540c40c3..0309a760 100644 --- a/library/think/facade/View.php +++ b/library/think/facade/View.php @@ -21,7 +21,7 @@ use think\Facade; * @method \think\View assign(mixed $name, mixed $value = '') static 模板变量赋值 * @method \think\View config(mixed $name, mixed $value = '') static 配置模板引擎 * @method \think\View exists(mixed $name) static 检查模板是否存在 - * @method \think\View replace(mixed $content, string $replace = '') static 视图内容替换 + * @method \think\View filter(Callable $filter) static 视图内容过滤 * @method \think\View engine(mixed $engine = []) static 设置当前模板解析的引擎 * @method string fetch(string $template = '', array $vars = [], array $replace = [], array $config = [], bool $renderContent = false) static 解析和获取模板内容 * @method string display(string $content = '', array $vars = [], array $replace = [], array $config = []) static 渲染内容输出 diff --git a/library/think/response/View.php b/library/think/response/View.php index 3c8fb2db..2cd8dd4a 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -17,8 +17,9 @@ use think\Response; class View extends Response { // 输出参数 - protected $options = []; - protected $vars = []; + protected $options = []; + protected $vars = []; + protected $filter; protected $contentType = 'text/html'; /** @@ -33,6 +34,7 @@ class View extends Response $config = Container::get('config'); return Container::get('view') ->init($config->pull('template')) + ->filter($this->filter) ->fetch($data, $this->vars); } @@ -70,6 +72,18 @@ class View extends Response return $this; } + /** + * 视图内容过滤 + * @access public + * @param callable $filter + * @return $this + */ + public function filter($filter) + { + $this->filter = $filer; + return $this; + } + /** * 检查模板是否存在 * @access private -- Gitee From 4f94c108013c054c52220b5911c30e28856db3ad Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 16:28:51 +0800 Subject: [PATCH 0810/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/response/View.php b/library/think/response/View.php index 2cd8dd4a..fc32aa56 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -80,7 +80,7 @@ class View extends Response */ public function filter($filter) { - $this->filter = $filer; + $this->filter = $filter; return $this; } -- Gitee From 5d0a8c383431f5ad051680bae05c17066e03aa94 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 17:19:35 +0800 Subject: [PATCH 0811/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0route=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E5=87=BD=E6=95=B0=20=E5=8F=AF=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=96=87=E4=BB=B6=E5=AE=9A=E4=B9=89=20?= =?UTF-8?q?=E5=92=8C=20Route::any=E6=96=B9=E6=B3=95=E7=AD=89=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/helper.php b/helper.php index 9c711c50..461460ea 100644 --- a/helper.php +++ b/helper.php @@ -26,9 +26,11 @@ use think\facade\Hook; use think\facade\Lang; use think\facade\Log; use think\facade\Request; +use think\facade\Route; use think\facade\Session; use think\facade\Url; use think\Response; +use think\route\RuleItem; if (!function_exists('abort')) { /** @@ -522,6 +524,21 @@ if (!function_exists('response')) { } } +if (!function_exists('route')) { + /** + * 路由注册 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + function route($rule, $route, $option = [], $pattern = []) + { + return Route::rule($rule, $route, '*', $option, $pattern); + } +} + if (!function_exists('session')) { /** * Session管理 -- Gitee From 312389820367a2a2db1b6d493577cea4c0dec95a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 18:10:11 +0800 Subject: [PATCH 0812/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=86=E7=BB=84?= =?UTF-8?q?=E5=B5=8C=E5=A5=97=E8=B5=84=E6=BA=90=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index b9064739..3d3c3888 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -106,11 +106,9 @@ class Resource extends RuleGroup $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); } - $item = trim(substr(trim($rule . $val[1], '/'), strlen($this->name)), '/'); - $option['rest'] = $key; - $this->router->rule($item, $this->route . '/' . $val[2], $val[0], $option); + $this->router->rule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); } $this->router->setGroup($group); -- Gitee From b614adb4eccb8238a0eea5d6285e86c27f584b01 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 11 Jan 2018 19:25:25 +0800 Subject: [PATCH 0813/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Build.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/think/Build.php b/library/think/Build.php index 29849183..51a84193 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -273,8 +273,14 @@ class Build $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); - $key = array_search('', $comment); - $comment = implode(PHP_EOL . "\t", array_slice($comment, 0, $key)) . ';'; + + 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]; -- Gitee From af0f857a906a605b73f1f305b5376c3bb28d9c98 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 12 Jan 2018 11:47:44 +0800 Subject: [PATCH 0814/1384] =?UTF-8?q?Session=E9=A9=B1=E5=8A=A8=E6=94=B9?= =?UTF-8?q?=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/session/driver/Memcache.php | 4 ++-- library/think/session/driver/Memcached.php | 4 ++-- library/think/session/driver/Redis.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/think/session/driver/Memcache.php b/library/think/session/driver/Memcache.php index a25bf3f0..40d7bb82 100644 --- a/library/think/session/driver/Memcache.php +++ b/library/think/session/driver/Memcache.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Memcache extends SessionHandler +class Memcache implements SessionHandlerInterface { protected $handler = null; protected $config = [ diff --git a/library/think/session/driver/Memcached.php b/library/think/session/driver/Memcached.php index aa3d6adc..074b2ff7 100644 --- a/library/think/session/driver/Memcached.php +++ b/library/think/session/driver/Memcached.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Memcached extends SessionHandler +class Memcached implements SessionHandlerInterface { protected $handler = null; protected $config = [ diff --git a/library/think/session/driver/Redis.php b/library/think/session/driver/Redis.php index 083943e0..e09988ee 100644 --- a/library/think/session/driver/Redis.php +++ b/library/think/session/driver/Redis.php @@ -11,10 +11,10 @@ namespace think\session\driver; -use SessionHandler; +use SessionHandlerInterface; use think\Exception; -class Redis extends SessionHandler +class Redis implements SessionHandlerInterface { /** @var \Redis */ protected $handler = null; -- Gitee From 794dfe808d221af645a0ce03756803e2066348f0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 12 Jan 2018 11:57:37 +0800 Subject: [PATCH 0815/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 19a7772b..8cc7c9fa 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.2'; + const VERSION = '5.1.3'; /** * 当前模块路径 -- Gitee From a3dbddd7e5941d7b7f589e4799381f71c83cfa36 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 12 Jan 2018 12:27:43 +0800 Subject: [PATCH 0816/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/View.php b/library/think/View.php index 3a2d3cc3..6127235d 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -29,7 +29,7 @@ class View * 内容过滤 * @var mixed */ - protected $filer; + protected $filter; /** * 全局模板变量 -- Gitee From 5957834bdd0a329429f0b665c771bd80095c40aa Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 12 Jan 2018 16:06:24 +0800 Subject: [PATCH 0817/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bupdate=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=94=9F=E6=88=90=E7=9A=84sql=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 8 ++++---- library/think/route/Rule.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index b7be1a36..8ea36db2 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -134,10 +134,10 @@ abstract class Builder $result[$item] = $val[1]; break; case 'inc': - $result[$item] = $this->parseKey($query, $val[1]) . '+' . floatval($val[2]); + $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]); break; case 'dec': - $result[$item] = $this->parseKey($query, $val[1]) . '-' . floatval($val[2]); + $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]); break; } } elseif (is_scalar($val)) { @@ -1085,14 +1085,14 @@ abstract class Builder } foreach ($data as $key => $val) { - $set[] = $key . '=' . $val; + $set[] = $key . ' = ' . $val; } return str_replace( ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ $this->parseTable($query, $options['table']), - implode(',', $set), + implode(' , ', $set), $this->parseJoin($query, $options['join']), $this->parseWhere($query, $options['where']), $this->parseOrder($query, $options['order']), diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index fbff40a3..30b58627 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -438,7 +438,7 @@ abstract class Rule * @access public * @return $this */ - public function crossDomain() + public function crossDomainRule() { if ($this instanceof RuleGroup) { $method = '*'; -- Gitee From 8e41f4e2599405b6b722de32ce75b6c473064d24 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 12 Jan 2018 17:31:52 +0800 Subject: [PATCH 0818/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3request=E7=B1=BBonl?= =?UTF-8?q?y=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 02192408..bd2f69bf 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1366,18 +1366,18 @@ class Request } $item = []; - foreach ($name as $key => $item) { - $default = null; + foreach ($name as $key => $val) { if (is_int($key)) { - $key = $item; + $default = null; + $key = $val; } else { - $default = $item; + $default = $val; } if (isset($param[$key])) { $item[$key] = $param[$key]; - } elseif (!is_null($default)) { + } elseif (isset($default)) { $item[$key] = $default; } } -- Gitee From 13e4fd065d0c836c36ea45dfd920b878828ecd7b Mon Sep 17 00:00:00 2001 From: slince Date: Fri, 12 Jan 2018 14:23:51 +0800 Subject: [PATCH 0819/1384] add middleware --- base.php | 2 +- library/think/http/middleware/Dispatcher.php | 70 +++++++++++++++++++ .../http/middleware/DispatcherInterface.php | 39 +++++++++++ .../middleware/MissingResponseException.php | 17 +++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 library/think/http/middleware/Dispatcher.php create mode 100644 library/think/http/middleware/DispatcherInterface.php create mode 100644 library/think/http/middleware/MissingResponseException.php diff --git a/base.php b/base.php index c20b5bd5..fc8b7b13 100644 --- a/base.php +++ b/base.php @@ -47,7 +47,7 @@ Container::getInstance()->bind([ 'url' => Url::class, 'validate' => Validate::class, 'view' => View::class, - + 'middlewareDispatcher' => http\middleware\Dispatcher::class, // 接口依赖注入 'think\LoggerInterface' => Log::class, ]); diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php new file mode 100644 index 00000000..91fdaf40 --- /dev/null +++ b/library/think/http/middleware/Dispatcher.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- + +namespace think\http\middleware; + +use think\Request; +use think\Response; + +class Dispatcher implements DispatcherInterface +{ + protected $queue; + + /** + * {@inheritdoc} + */ + public function add($middleware) + { + $this->assertValid($middleware); + $this->queue[] = $middleware; + } + + /** + * {@inheritdoc} + */ + public function insert($middleware) + { + $this->assertValid($middleware); + array_unshift($this->queue, $middleware); + } + + /** + * {@inheritdoc} + */ + public function dispatch(Request $request) + { + $requestHandler = $this->resolve(); + return call_user_func($requestHandler, $request); + } + + protected function resolve() + { + return function(Request $request){ + $middleware = array_shift($this->queue); + if ($middleware !== null) { + $response = call_user_func($middleware, $request, $this->resolve()); + if ($response instanceof Response) { + throw new \LogicException('The middleware must return Response instance'); + } + return $response; + } else { + throw new MissingResponseException('The queue was exhausted, with no response returned'); + } + }; + } + + protected function assertValid($middleware) + { + if (!is_callable($middleware)) { + throw new \InvalidArgumentException('The middleware is invalid'); + } + } +} \ No newline at end of file diff --git a/library/think/http/middleware/DispatcherInterface.php b/library/think/http/middleware/DispatcherInterface.php new file mode 100644 index 00000000..47e883a2 --- /dev/null +++ b/library/think/http/middleware/DispatcherInterface.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\http\middleware; + +use think\Request; +use think\Response; + +interface DispatcherInterface +{ + /** + * 在队尾添加 middleware + * @param callable $middleware + * @return DispatcherInterface + */ + public function add($middleware); + + /** + * 在队前插入 middleware + * @param callable $middleware + * @return DispatcherInterface + */ + public function insert($middleware); + + /** + * 处理 request 并返回 response + * @param Request $request + * @return Response + */ + public function dispatch(Request $request); +} \ No newline at end of file diff --git a/library/think/http/middleware/MissingResponseException.php b/library/think/http/middleware/MissingResponseException.php new file mode 100644 index 00000000..fef97693 --- /dev/null +++ b/library/think/http/middleware/MissingResponseException.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- + +namespace think\http\middleware; + +class MissingResponseException extends \InvalidArgumentException +{ + +} \ No newline at end of file -- Gitee From 1c90a48618caffcafccf5b986dd38261f3545a34 Mon Sep 17 00:00:00 2001 From: slince Date: Fri, 12 Jan 2018 16:36:50 +0800 Subject: [PATCH 0820/1384] add support for middleware --- composer.json | 2 +- library/think/App.php | 28 ++++---- library/think/http/middleware/Dispatcher.php | 15 +++- .../http/middleware/DispatcherInterface.php | 6 ++ .../http/tests/middleware/DispatcherTest.php | 69 +++++++++++++++++++ phpunit.xml.dist | 41 +++++++++++ 6 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 library/think/http/tests/middleware/DispatcherTest.php create mode 100644 phpunit.xml.dist diff --git a/composer.json b/composer.json index 245b2699..3e504960 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "topthink/think-installer": "~1.0" }, "require-dev": { - "phpunit/phpunit": "4.8.*", + "phpunit/phpunit": "^5.0|^6.0", "johnkary/phpunit-speedtrap": "^1.0", "mikey179/vfsStream": "~1.6", "phploc/phploc": "2.*", diff --git a/library/think/App.php b/library/think/App.php index 8cc7c9fa..90b5bb41 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -365,18 +365,22 @@ class App implements \ArrayAccess $data = $exception->getResponse(); } - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $isAjax = $this->request->isAjax(); - $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); - - $response = Response::create($data, $type); - } else { - $response = Response::create(); - } + $this->middlewareDispatcher->add(function(Request $request, $next) use ($data){ + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $request->isAjax(); + $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); + + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + return $response; + }); + $response = $this->middlewareDispatcher->dispatch($this->request); // 监听app_end $this->hook->listen('app_end', $response); diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php index 91fdaf40..78458056 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/http/middleware/Dispatcher.php @@ -18,6 +18,11 @@ class Dispatcher implements DispatcherInterface { protected $queue; + public function __construct($middlewares = []) + { + $this->queue = (array)$middlewares; + } + /** * {@inheritdoc} */ @@ -36,6 +41,14 @@ class Dispatcher implements DispatcherInterface array_unshift($this->queue, $middleware); } + /** + * {@inheritdoc} + */ + public function all() + { + return $this->queue; + } + /** * {@inheritdoc} */ @@ -51,7 +64,7 @@ class Dispatcher implements DispatcherInterface $middleware = array_shift($this->queue); if ($middleware !== null) { $response = call_user_func($middleware, $request, $this->resolve()); - if ($response instanceof Response) { + if (!$response instanceof Response) { throw new \LogicException('The middleware must return Response instance'); } return $response; diff --git a/library/think/http/middleware/DispatcherInterface.php b/library/think/http/middleware/DispatcherInterface.php index 47e883a2..7e2a0242 100644 --- a/library/think/http/middleware/DispatcherInterface.php +++ b/library/think/http/middleware/DispatcherInterface.php @@ -30,6 +30,12 @@ interface DispatcherInterface */ public function insert($middleware); + /** + * 获取所有的middleware + * @return array + */ + public function all(); + /** * 处理 request 并返回 response * @param Request $request diff --git a/library/think/http/tests/middleware/DispatcherTest.php b/library/think/http/tests/middleware/DispatcherTest.php new file mode 100644 index 00000000..04c99ba4 --- /dev/null +++ b/library/think/http/tests/middleware/DispatcherTest.php @@ -0,0 +1,69 @@ +add(function(){ + }); + $this->assertCount(1, $dispatcher->all()); + $this->expectException(\InvalidArgumentException::class); + $dispatcher->add('foo middleware'); + } + + public function testAddAndInsert() + { + $middleware1 = function(){}; + $middleware2 = function(){}; + $dispatcher = new Dispatcher(); + $dispatcher->add($middleware1); + $dispatcher->insert($middleware2); + $this->assertSame([$middleware2, $middleware1], $dispatcher->all()); + } + + public function testDispatch() + { + $middleware1 = function($request, $next){ + return $next($request); + }; + $middleware2 = function($request){ + return Response::create('hello world'); + }; + $dispatcher = new Dispatcher([$middleware1, $middleware2]); + $response = $dispatcher->dispatch(new Request()); + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('hello world', $response->getContent()); + } + + public function testDispatchWithoutResponse() + { + $middleware1 = function($request, $next){ + return $next($request); + }; + $middleware2 = function($request, $next){ + return $next($request); + }; + $dispatcher = new Dispatcher([$middleware1, $middleware2]); + $this->expectException(MissingResponseException::class); + $dispatcher->dispatch(new Request()); + } + + public function testDispatchWithBadResponse() + { + $middleware = function($request, $next){ + return 'invalid response'; + }; + $dispatcher = new Dispatcher($middleware); + $this->expectException(\LogicException::class); + $dispatcher->dispatch(new Request()); + } +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..37c3d2b5 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + ./library/think/*/tests/ + + + + + + ./library/ + + ./library/think/*/tests + ./library/think/*/assets + ./library/think/*/resources + ./library/think/*/vendor + + + + \ No newline at end of file -- Gitee From 376dc7e4d6dbbf9a26974c2173be22b738c95841 Mon Sep 17 00:00:00 2001 From: slince Date: Fri, 12 Jan 2018 16:45:29 +0800 Subject: [PATCH 0821/1384] add support for middleware --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57eeb304..5c340b9e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ -ThinkPHP 5.1 -=============== +

+ + ThinkPHP ThinkPHP + +

ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: -- Gitee From 997afed45d80f3d7511666798d2384cabeb6e0a6 Mon Sep 17 00:00:00 2001 From: slince Date: Fri, 12 Jan 2018 16:46:46 +0800 Subject: [PATCH 0822/1384] add support for middleware --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c340b9e..57eeb304 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ -

- - ThinkPHP ThinkPHP - -

+ThinkPHP 5.1 +=============== ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: -- Gitee From 371933ce6964c85851972ad88807c08db9700ffc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 13 Jan 2018 20:45:28 +0800 Subject: [PATCH 0823/1384] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 3 ++- library/think/http/middleware/Dispatcher.php | 11 +++++++---- library/think/http/middleware/DispatcherInterface.php | 2 +- .../http/middleware/MissingResponseException.php | 4 +--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 90b5bb41..689e282e 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -365,7 +365,7 @@ class App implements \ArrayAccess $data = $exception->getResponse(); } - $this->middlewareDispatcher->add(function(Request $request, $next) use ($data){ + $this->middlewareDispatcher->add(function (Request $request, $next) use ($data) { // 输出数据到客户端 if ($data instanceof Response) { $response = $data; @@ -380,6 +380,7 @@ class App implements \ArrayAccess } return $response; }); + $response = $this->middlewareDispatcher->dispatch($this->request); // 监听app_end diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php index 78458056..4218915d 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/http/middleware/Dispatcher.php @@ -20,7 +20,7 @@ class Dispatcher implements DispatcherInterface public function __construct($middlewares = []) { - $this->queue = (array)$middlewares; + $this->queue = (array) $middlewares; } /** @@ -60,13 +60,16 @@ class Dispatcher implements DispatcherInterface protected function resolve() { - return function(Request $request){ + return function (Request $request) { $middleware = array_shift($this->queue); - if ($middleware !== null) { + + if (null !== $middleware) { $response = call_user_func($middleware, $request, $this->resolve()); + if (!$response instanceof Response) { throw new \LogicException('The middleware must return Response instance'); } + return $response; } else { throw new MissingResponseException('The queue was exhausted, with no response returned'); @@ -80,4 +83,4 @@ class Dispatcher implements DispatcherInterface throw new \InvalidArgumentException('The middleware is invalid'); } } -} \ No newline at end of file +} diff --git a/library/think/http/middleware/DispatcherInterface.php b/library/think/http/middleware/DispatcherInterface.php index 7e2a0242..1693f609 100644 --- a/library/think/http/middleware/DispatcherInterface.php +++ b/library/think/http/middleware/DispatcherInterface.php @@ -42,4 +42,4 @@ interface DispatcherInterface * @return Response */ public function dispatch(Request $request); -} \ No newline at end of file +} diff --git a/library/think/http/middleware/MissingResponseException.php b/library/think/http/middleware/MissingResponseException.php index fef97693..7cdcb33d 100644 --- a/library/think/http/middleware/MissingResponseException.php +++ b/library/think/http/middleware/MissingResponseException.php @@ -12,6 +12,4 @@ namespace think\http\middleware; class MissingResponseException extends \InvalidArgumentException -{ - -} \ No newline at end of file +{} -- Gitee From beabeaa5e9edc642e881613d51f9721e1b1375c0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 15 Jan 2018 15:04:05 +0800 Subject: [PATCH 0824/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3rule=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=9A=84method=E5=8F=82=E6=95=B0=E4=BD=BF=E7=94=A8=20?= =?UTF-8?q?get|post=20=E6=96=B9=E5=BC=8F=E6=B3=A8=E5=86=8C=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 55c229cd..2edac770 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -199,6 +199,11 @@ class RuleGroup extends Rule $rule->name($this->name . '/' . $name); } + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + $this->rules[$method][] = $rule; if ($rule instanceof RuleItem) { -- Gitee From 7d244c44c1ea8b130e7f0ab61e957937de73a120 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 15 Jan 2018 15:29:41 +0800 Subject: [PATCH 0825/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 689e282e..f324394b 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -343,7 +343,7 @@ class App implements \ArrayAccess // 记录路由和请求信息 if ($this->debug) { - $this->log('[ ROUTE ] ' . var_export($this->request->routeinfo(), true)); + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); } -- Gitee From b7b8ec8e69952d3f18a3e58e95898641ee6bb1c1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 15 Jan 2018 16:03:04 +0800 Subject: [PATCH 0826/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=20deleteTime=E5=B1=9E=E6=80=A7=E4=B8=BAfalse=20=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E8=BD=AF=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/SoftDelete.php | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index c1f8011b..16b75b72 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -19,7 +19,7 @@ trait SoftDelete { $field = $this->getDeleteTimeField(); - if (!empty($this->getOrigin($field))) { + if ($field && !empty($this->getOrigin($field))) { return true; } @@ -48,9 +48,13 @@ trait SoftDelete $model = new static(); $field = $model->getDeleteTimeField(true); - return $model - ->db(false) - ->useSoftDelete($field, ['not null', '']); + if ($field) { + return $model + ->db(false) + ->useSoftDelete($field, ['not null', '']); + } else { + return $model->db(false); + } } /** @@ -67,7 +71,7 @@ trait SoftDelete $name = $this->getDeleteTimeField(); - if (!$force) { + if ($name && !$force) { // 软删除 $this->data($name, $this->autoWriteTimestamp($name)); @@ -144,23 +148,31 @@ trait SoftDelete $where[$pk] = [$pk, '=', $this->getData($pk)]; } - // 恢复删除 - return $this->db(false) - ->where($where) - ->useSoftDelete($name, ['not null', '']) - ->update([$name => null]); + if ($name) { + // 恢复删除 + return $this->db(false) + ->where($where) + ->useSoftDelete($name, ['not null', '']) + ->update([$name => null]); + } else { + return 0; + } } /** * 获取软删除字段 * @access protected * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 - * @return string + * @return string|false */ protected function getDeleteTimeField($read = false) { $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + if (false === $field) { + return false; + } + if (!strpos($field, '.')) { $field = '__TABLE__.' . $field; } -- Gitee From cc39b1ab989cdd6fa98c9d922671f419b1ee78c4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 15 Jan 2018 16:08:48 +0800 Subject: [PATCH 0827/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 4ffa0633..e4a7e79f 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -241,7 +241,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 软删除 if (method_exists($this, 'getDeleteTimeField')) { $field = $this->getDeleteTimeField(true); - $query->useSoftDelete($field); + if ($field) { + $query->useSoftDelete($field); + } } // 全局作用域 -- Gitee From b45ee7355d8ae2a221fa0054a8da89f5ef3b060c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 15 Jan 2018 17:35:38 +0800 Subject: [PATCH 0828/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bunion=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 8ea36db2..46f5f964 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -36,7 +36,7 @@ abstract class Builder ]; // SQL表达式 - protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%UNION%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; -- Gitee From ff7b885037edf84ff7b48e843a4848848419f6eb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 15 Jan 2018 19:13:04 +0800 Subject: [PATCH 0829/1384] =?UTF-8?q?=E5=85=B3=E8=81=94=E9=A2=84=E8=BD=BD?= =?UTF-8?q?=E5=85=A5with=E6=96=B9=E6=B3=95=E5=A2=9E=E5=8A=A0=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 9 ++++++--- library/think/model/concern/RelationShip.php | 16 ++++++++++------ library/think/model/relation/BelongsTo.php | 10 ++++++---- library/think/model/relation/BelongsToMany.php | 14 +++++++++----- library/think/model/relation/HasMany.php | 15 +++++++++------ library/think/model/relation/HasOne.php | 10 ++++++---- library/think/model/relation/MorphMany.php | 15 +++++++++------ library/think/model/relation/MorphOne.php | 15 +++++++++------ library/think/model/relation/MorphTo.php | 13 ++++++++----- library/think/model/relation/OneToOne.php | 15 +++++++++------ 10 files changed, 81 insertions(+), 51 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index af5fc616..1c1bf6df 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2076,9 +2076,10 @@ class Query * 设置关联查询JOIN预查询 * @access public * @param string|array $with 关联方法名称 + * @param bool|array $cache 设置关联缓存 * @return $this */ - public function with($with) + public function with($with, $cache = false) { if (empty($with)) { return $this; @@ -2129,6 +2130,8 @@ class Query $this->options['with'] = $with; } + $this->options['relation_cache'] = $cache; + return $this; } @@ -2488,7 +2491,7 @@ class Query if (!empty($this->options['with'])) { // 预载入 - $result->eagerlyResultSet($resultSet, $this->options['with']); + $result->eagerlyResultSet($resultSet, $this->options['with'], $this->options['relation_cache']); } // 模型数据集转换 @@ -2574,7 +2577,7 @@ class Query // 预载入查询 if (!$resultSet && !empty($options['with'])) { - $result->eagerlyResult($result, $options['with']); + $result->eagerlyResult($result, $options['with'], $options['relation_cache']); } // 关联统计 diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index 51611958..ad675a4b 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -210,9 +210,10 @@ trait RelationShip * @access public * @param array $resultSet 数据集 * @param string $relation 关联名 + * @param mixed $cache 关联缓存 * @return array */ - public function eagerlyResultSet(&$resultSet, $relation) + public function eagerlyResultSet(&$resultSet, $relation, $cache = false) { $relations = is_string($relation) ? explode(',', $relation) : $relation; @@ -232,9 +233,10 @@ trait RelationShip list($relation, $subRelation) = explode('.', $relation, 2); } - $relation = Loader::parseName($relation, 1, false); + $relationCache = isset($cache[$relation]) ? $cache[$relation] : $cache; + $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $relationCache); } } @@ -243,9 +245,10 @@ trait RelationShip * @access public * @param Model $result 数据对象 * @param string $relation 关联名 + * @param mixed $cache 关联缓存 * @return Model */ - public function eagerlyResult(&$result, $relation) + public function eagerlyResult(&$result, $relation, $cache = false) { $relations = is_string($relation) ? explode(',', $relation) : $relation; @@ -265,9 +268,10 @@ trait RelationShip list($relation, $subRelation) = explode('.', $relation, 2); } - $relation = Loader::parseName($relation, 1, false); + $relationCache = isset($cache[$relation]) ? $cache[$relation] : $cache; + $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure, $relationCache); } } diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index bcbab2ac..f8073889 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -109,9 +109,10 @@ class BelongsTo extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -127,7 +128,7 @@ class BelongsTo extends OneToOne if (!empty($range)) { $data = $this->eagerlyWhere([ [$localKey, 'in', $range], - ], $localKey, $relation, $subRelation, $closure); + ], $localKey, $relation, $subRelation, $closure, $cache); // 关联属性名 $attr = Loader::parseName($relation); @@ -161,16 +162,17 @@ class BelongsTo extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + protected function eagerlyOne(&$result, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; $data = $this->eagerlyWhere([ [$localKey, '=', $result->$foreignKey], - ], $localKey, $relation, $subRelation, $closure); + ], $localKey, $relation, $subRelation, $closure, $cache); // 关联模型 if (!isset($data[$result->$foreignKey])) { diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index 203c198b..c9fd159e 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -263,9 +263,10 @@ class BelongsToMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -283,7 +284,7 @@ class BelongsToMany extends Relation // 查询关联数据 $data = $this->eagerlyManyToMany([ ['pivot.' . $localKey, 'in', $range], - ], $relation, $subRelation); + ], $relation, $subRelation, $cache); // 关联属性名 $attr = Loader::parseName($relation); @@ -306,9 +307,10 @@ class BelongsToMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) { $pk = $result->getPk(); @@ -317,7 +319,7 @@ class BelongsToMany extends Relation // 查询管理数据 $data = $this->eagerlyManyToMany([ ['pivot.' . $this->localKey, '=', $pk], - ], $relation, $subRelation); + ], $relation, $subRelation, $cache); // 关联数据封装 if (!isset($data[$pk])) { @@ -375,13 +377,15 @@ class BelongsToMany extends Relation * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 + * @param mixed $cache 缓存 * @return array */ - protected function eagerlyManyToMany($where, $relation, $subRelation = '') + protected function eagerlyManyToMany($where, $relation, $subRelation = '', $cache = false) { // 预载入关联查询 支持嵌套预载入 $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) ->with($subRelation) + ->cache($cache) ->select(); // 组装模型数据 diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 3a882298..89622a58 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -69,9 +69,10 @@ class HasMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; $range = []; @@ -87,7 +88,7 @@ class HasMany extends Relation $where = [ [$this->foreignKey, 'in', $range], ]; - $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure, $cache); // 关联属性名 $attr = Loader::parseName($relation); @@ -115,9 +116,10 @@ class HasMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; @@ -126,7 +128,7 @@ class HasMany extends Relation $where = [ [$this->foreignKey, '=', $pk], ]; - $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure, $cache); // 关联数据封装 if (!isset($data[$pk])) { @@ -193,9 +195,10 @@ class HasMany extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool $closure + * @param mixed $cache 缓存 * @return array */ - protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = false, $cache = false) { $foreignKey = $this->foreignKey; @@ -206,7 +209,7 @@ class HasMany extends Relation $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->select(); + $list = $this->query->where($where)->with($subRelation)->cache($cache)->select(); // 组装模型数据 $data = []; diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 915dea47..0f01fd21 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -118,9 +118,10 @@ class HasOne extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -136,7 +137,7 @@ class HasOne extends OneToOne if (!empty($range)) { $data = $this->eagerlyWhere([ [$foreignKey, 'in', $range], - ], $foreignKey, $relation, $subRelation, $closure); + ], $foreignKey, $relation, $subRelation, $closure, $cache); // 关联属性名 $attr = Loader::parseName($relation); @@ -170,15 +171,16 @@ class HasOne extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + protected function eagerlyOne(&$result, $relation, $subRelation, $closure, $cache = false) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; $data = $this->eagerlyWhere([ [$foreignKey, '=', $result->$localKey], - ], $foreignKey, $relation, $subRelation, $closure); + ], $foreignKey, $relation, $subRelation, $closure, $cache); // 关联模型 if (!isset($data[$result->$localKey])) { diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index b3a4f39f..a0361680 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -102,9 +102,10 @@ class MorphMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $morphType = $this->morphType; $morphKey = $this->morphKey; @@ -124,7 +125,7 @@ class MorphMany extends Relation [$morphKey, 'in', $range], [$morphType, '=', $type], ]; - $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure, $cache); // 关联属性名 $attr = Loader::parseName($relation); @@ -152,9 +153,10 @@ class MorphMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) { $pk = $result->getPk(); @@ -164,7 +166,7 @@ class MorphMany extends Relation [$this->morphKey, '=', $key], [$this->morphType, '=', $this->type], ]; - $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure, $cache); if (!isset($data[$key])) { $data[$key] = []; @@ -239,9 +241,10 @@ class MorphMany extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool|\Closure $closure 闭包 + * @param mixed $cache 缓存 * @return array */ - protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false, $cache = false) { // 预载入关联查询 支持嵌套预载入 $this->query->removeOptions('where'); @@ -250,7 +253,7 @@ class MorphMany extends Relation $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->select(); + $list = $this->query->where($where)->with($subRelation)->cache($cache)->select(); $morphKey = $this->morphKey; // 组装模型数据 diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index 09444008..8238d874 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -101,9 +101,10 @@ class MorphOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $morphType = $this->morphType; $morphKey = $this->morphKey; @@ -122,7 +123,7 @@ class MorphOne extends Relation $data = $this->eagerlyMorphToOne([ [$morphKey, 'in', $range], [$morphType, '=', $type], - ], $relation, $subRelation, $closure); + ], $relation, $subRelation, $closure, $cache); // 关联属性名 $attr = Loader::parseName($relation); @@ -149,9 +150,10 @@ class MorphOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) { $pk = $result->getPk(); @@ -160,7 +162,7 @@ class MorphOne extends Relation $data = $this->eagerlyMorphToOne([ [$this->morphKey, '=', $pk], [$this->morphType, '=', $this->type], - ], $relation, $subRelation, $closure); + ], $relation, $subRelation, $closure, $cache); if (isset($data[$pk])) { $relationModel = $data[$pk]; @@ -181,16 +183,17 @@ class MorphOne extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool|\Closure $closure 闭包 + * @param mixed $cache 缓存 * @return array */ - protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false, $cache = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->find(); + $list = $this->query->where($where)->with($subRelation)->cache($cache)->find(); $morphKey = $this->morphKey; // 组装模型数据 diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 9eafebd7..280d815b 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -149,10 +149,11 @@ class MorphTo extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void * @throws Exception */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { $morphKey = $this->morphKey; $morphType = $this->morphType; @@ -206,16 +207,17 @@ class MorphTo extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) { $morphKey = $this->morphKey; $morphType = $this->morphType; // 多态类型映射 $model = $this->parseModel($result->{$this->morphType}); - $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache); } /** @@ -237,13 +239,14 @@ class MorphTo extends Relation * @param string $relation 关联名 * @param Model $result * @param string $subRelation 子关联 + * @param mixed $cache 缓存 * @return void */ - protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '', $cache = false) { // 预载入关联查询 支持嵌套预载入 $pk = $this->parent->{$this->morphKey}; - $data = (new $model)->with($subRelation)->find($pk); + $data = (new $model)->with($subRelation)->cache($cache)->find($pk); if ($data) { $data->setParent(clone $result); diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 7051188c..8eeefd6f 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -133,13 +133,14 @@ abstract class OneToOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) { if (1 == $this->eagerlyType) { // IN查询 - $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache); } else { // 模型关联组装 foreach ($resultSet as $result) { @@ -155,13 +156,14 @@ abstract class OneToOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 + * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) { if (1 == $this->eagerlyType) { // IN查询 - $this->eagerlyOne($result, $relation, $subRelation, $closure); + $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache); } else { // 模型关联组装 $this->match($this->model, $relation, $result); @@ -310,9 +312,10 @@ abstract class OneToOne extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool|\Closure $closure + * @param mixed $cache 缓存 * @return array */ - protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false) + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false, $cache = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { @@ -323,7 +326,7 @@ abstract class OneToOne extends Relation } } - $list = $this->query->where($where)->with($subRelation)->select(); + $list = $this->query->where($where)->with($subRelation)->cache($cache)->select(); // 组装模型数据 $data = []; -- Gitee From e242c4d4bdf346ec2606d5303582801b6e03a3ec Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 16 Jan 2018 11:17:36 +0800 Subject: [PATCH 0830/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=B1=BB=E7=9A=84get=E5=92=8Call=E6=96=B9=E6=B3=95=E7=9A=84?= =?UTF-8?q?=E7=BC=93=E5=AD=98=20=E6=94=AF=E6=8C=81=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index e4a7e79f..52752132 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -816,7 +816,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected static function parseQuery(&$data, $with, $cache) { - $result = self::with($with)->cache($cache); + $result = self::with($with, true === $cache ? true : false)->cache($cache); if (is_array($data) && key($data) !== 0) { $result = $result->where($data); -- Gitee From 8ec46bfcf0f3301b4fdb95c60cfd78606c632801 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 16 Jan 2018 13:48:19 +0800 Subject: [PATCH 0831/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81order=20by=20field?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 18 +++++++++++++++++- library/think/db/Query.php | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 46f5f964..7fada041 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -801,7 +801,23 @@ abstract class Builder $array = []; foreach ($order as $key => $val) { - if (is_numeric($key)) { + if (is_array($val)) { + if (isset($val['sort'])) { + $sort = ' ' . $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); + } + + $array[] = 'field(' . $this->parseKey($query, $key) . ',' . implode(',', $val) . ')' . $sort; + } elseif (is_numeric($key)) { if ('[rand]' == $val) { $array[] = $this->parseRand($query); } elseif (false === strpos($val, '(')) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1c1bf6df..a4a153a3 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1671,6 +1671,33 @@ class Query return $this; } + /** + * 指定Field排序 order('id',[1,2,3],'desc') + * @access public + * @param string|array $field 排序字段 + * @param string $values 排序值 + * @param string $order + * @return $this + */ + public function orderField($field, array $values = [], $order = '') + { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + /** * 查询缓存 * @access public -- Gitee From d79f89a9272efb59f42d93a84a6dbc559d14c067 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 16 Jan 2018 15:02:46 +0800 Subject: [PATCH 0832/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index db9503f0..fd93042b 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -527,7 +527,7 @@ class Route /** * 批量注册路由规则 * @access public - * @param string $rules 路由规则 + * @param array $rules 路由规则 * @param string $method 请求类型 * @param array $option 路由参数 * @param array $pattern 变量规则 -- Gitee From 7dca3eeeb724066a0eb873ca99143e52908618e0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 16 Jan 2018 18:58:46 +0800 Subject: [PATCH 0833/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BinsertAll=E5=88=86?= =?UTF-8?q?=E6=89=B9=E5=86=99=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 41 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 6437fb1e..323360c6 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -986,6 +986,8 @@ abstract class Connection * @param bool $replace 是否replace * @param integer $limit 每次写入数据限制 * @return integer|string + * @throws \Exception + * @throws \Throwable */ public function insertAll(Query $query, $dataSet = [], $replace = false, $limit = null) { @@ -995,24 +997,43 @@ abstract class Connection $options = $query->getOptions(); - // 生成SQL语句 - if (is_null($limit)) { - $sql = $this->builder->insertAll($query, $dataSet, $replace); - } else { - $array = array_chunk($dataSet, $limit, true); - foreach ($array as $item) { - $sql[] = $this->builder->insertAll($query, $item, $replace); + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $bind = $query->getBind(); + if (!empty($options['fetch_sql'])) { + $fetchSql[] = $this->getRealSql($sql, $bind); + } else { + $count += $this->execute($sql, $bind); + } + } + + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; } + + return isset($fetchSql) ? implode(';', $fetchSql) : $count; } + $sql = $this->builder->insertAll($query, $dataSet, $replace); $bind = $query->getBind(); if (!empty($options['fetch_sql'])) { // 获取实际执行的SQL语句 return $this->getRealSql($sql, $bind); - } elseif (is_array($sql)) { - // 执行操作 - return $this->batchQuery($sql, $bind); } else { // 执行操作 return $this->execute($sql, $bind); -- Gitee From afcab45fe9bf15cc9aeed09fd8b3d58a0ecdbfd8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 16 Jan 2018 19:03:31 +0800 Subject: [PATCH 0834/1384] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Debug.php | 2 +- .../http/tests/middleware/DispatcherTest.php | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/library/think/Debug.php b/library/think/Debug.php index 5a6ae10e..8a384e14 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -221,7 +221,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); + echo($output); return; } else { return $output; diff --git a/library/think/http/tests/middleware/DispatcherTest.php b/library/think/http/tests/middleware/DispatcherTest.php index 04c99ba4..298cbccc 100644 --- a/library/think/http/tests/middleware/DispatcherTest.php +++ b/library/think/http/tests/middleware/DispatcherTest.php @@ -13,7 +13,7 @@ class DispatcherTest extends TestCase public function testValidMiddleware() { $dispatcher = new Dispatcher(); - $dispatcher->add(function(){ + $dispatcher->add(function () { }); $this->assertCount(1, $dispatcher->all()); $this->expectException(\InvalidArgumentException::class); @@ -22,8 +22,8 @@ class DispatcherTest extends TestCase public function testAddAndInsert() { - $middleware1 = function(){}; - $middleware2 = function(){}; + $middleware1 = function () {}; + $middleware2 = function () {}; $dispatcher = new Dispatcher(); $dispatcher->add($middleware1); $dispatcher->insert($middleware2); @@ -32,24 +32,24 @@ class DispatcherTest extends TestCase public function testDispatch() { - $middleware1 = function($request, $next){ + $middleware1 = function ($request, $next) { return $next($request); }; - $middleware2 = function($request){ + $middleware2 = function ($request) { return Response::create('hello world'); }; $dispatcher = new Dispatcher([$middleware1, $middleware2]); - $response = $dispatcher->dispatch(new Request()); + $response = $dispatcher->dispatch(new Request()); $this->assertInstanceOf(Response::class, $response); $this->assertEquals('hello world', $response->getContent()); } public function testDispatchWithoutResponse() { - $middleware1 = function($request, $next){ + $middleware1 = function ($request, $next) { return $next($request); }; - $middleware2 = function($request, $next){ + $middleware2 = function ($request, $next) { return $next($request); }; $dispatcher = new Dispatcher([$middleware1, $middleware2]); @@ -59,11 +59,11 @@ class DispatcherTest extends TestCase public function testDispatchWithBadResponse() { - $middleware = function($request, $next){ + $middleware = function ($request, $next) { return 'invalid response'; }; $dispatcher = new Dispatcher($middleware); $this->expectException(\LogicException::class); $dispatcher->dispatch(new Request()); } -} \ No newline at end of file +} -- Gitee From dc0a487fe779a7b9b93c1d67249359a65267343a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 17 Jan 2018 13:08:48 +0800 Subject: [PATCH 0835/1384] =?UTF-8?q?json=E5=AD=97=E6=AE=B5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Builder.php | 10 +++++-- library/think/db/Query.php | 34 +++++++++++++++++++++++ library/think/model/concern/Attribute.php | 6 ++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 52752132..bfd140a4 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -195,7 +195,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { // 设置当前模型 确保查询返回模型对象 $class = $this->query; - $query = (new $class())->connect($this->connection)->model($this); + $query = (new $class())->connect($this->connection)->model($this)->json($this->json); // 设置当前数据表和模型名 if (!empty($this->table)) { diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 7fada041..2e227b17 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -118,11 +118,15 @@ abstract class Builder if (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 $val = $val->__toString(); - } elseif (is_array($val) && 'json' == $this->connection->getFieldsType($options['table'], $key)) { + } elseif (!is_scalar($val) && 'json' == $this->connection->getFieldsType($options['table'], $key)) { $val = json_encode($val); } - if (false === strpos($key, '.') && !in_array($key, $fields, true)) { + if (false !== strpos($key, '->')) { + list($key, $name) = explode('->', $key); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind, $suffix) . ')'; + } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } @@ -165,7 +169,7 @@ abstract class Builder if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) { return $data; } else { - $key = str_replace('.', '_', $key); + $key = str_replace(['.', '->'], '_', $key); $name = 'data__' . $key . $suffix; $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); return ':' . $name; diff --git a/library/think/db/Query.php b/library/think/db/Query.php index a4a153a3..5b385ae2 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1899,6 +1899,18 @@ class Query return $this; } + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @return $this + */ + public function json($json = []) + { + $this->options['json'] = $json; + return $this; + } + /** * 添加查询范围 * @access public @@ -2583,6 +2595,24 @@ class Query return $result; } + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @return void + */ + protected function jsonResult(&$result, $json = []) + { + foreach ($json as $key => $val) { + if (is_int($key)) { + $result[$val] = json_decode($result[$val]); + } else { + $result[$key] = new $val($result[$key]); + } + } + } + /** * 查询数据转换为模型对象 * @access protected @@ -2594,6 +2624,10 @@ class Query protected function resultToModel(&$result, $options = [], $resultSet = false) { + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json']); + } + $condition = (!$resultSet && isset($options['where']['AND'])) ? $options['where']['AND'] : null; $result = $this->model->newInstance($result, $condition); diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index ac549d0a..dc104481 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -30,6 +30,12 @@ trait Attribute */ protected $field = []; + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + /** * 数据表废弃字段 * @var array -- Gitee From 3191d94ccc8913e1ed04d088470136b49b33fccd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 17 Jan 2018 17:04:19 +0800 Subject: [PATCH 0836/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bjson=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 6 +++--- library/think/db/Query.php | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 2e227b17..83128a4d 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -115,11 +115,11 @@ abstract class Builder foreach ($data as $key => $val) { $item = $this->parseKey($query, $key); - if (is_object($val) && method_exists($val, '__toString')) { + if (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { + $val = json_encode($val); + } elseif (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 $val = $val->__toString(); - } elseif (!is_scalar($val) && 'json' == $this->connection->getFieldsType($options['table'], $key)) { - $val = json_encode($val); } if (false !== strpos($key, '->')) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 5b385ae2..12918353 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1905,7 +1905,7 @@ class Query * @param array $json JSON字段 * @return $this */ - public function json($json = []) + public function json(array $json = []) { $this->options['json'] = $json; return $this; @@ -2604,11 +2604,9 @@ class Query */ protected function jsonResult(&$result, $json = []) { - foreach ($json as $key => $val) { - if (is_int($key)) { - $result[$val] = json_decode($result[$val]); - } else { - $result[$key] = new $val($result[$key]); + foreach ($json as $name) { + if (isset($result[$name])) { + $result[$name] = json_decode($result[$name]); } } } @@ -2623,7 +2621,6 @@ class Query */ protected function resultToModel(&$result, $options = [], $resultSet = false) { - if (!empty($options['json'])) { $this->jsonResult($result, $options['json']); } -- Gitee From fc106afe95457e72429de1fa499a507a0cdaa763 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 17 Jan 2018 17:23:14 +0800 Subject: [PATCH 0837/1384] =?UTF-8?q?Db=E7=B1=BB=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=94=AF=E6=8C=81json=E6=96=B9=E6=B3=95=E6=8C=87=E5=AE=9AJSON?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 12918353..3bdaf7cc 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2541,6 +2541,10 @@ class Query } elseif ('collection' == $this->connection->getConfig('resultset_type')) { // 返回Collection对象 $resultSet = new Collection($resultSet); + } elseif (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } } // 返回结果处理 @@ -2587,6 +2591,8 @@ class Query if (!empty($this->model)) { // 返回模型对象 $this->resultToModel($result, $this->options); + } elseif (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); } } elseif (!empty($this->options['fail'])) { $this->throwNotFound($this->options); @@ -2600,13 +2606,14 @@ class Query * @access protected * @param array $result 查询数据 * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 * @return void */ - protected function jsonResult(&$result, $json = []) + protected function jsonResult(&$result, $json = [], $assoc = false) { foreach ($json as $name) { if (isset($result[$name])) { - $result[$name] = json_decode($result[$name]); + $result[$name] = json_decode($result[$name], $assoc); } } } -- Gitee From a81bc1b2cc4cb19d77b4333e1e41380f5326f72a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 18 Jan 2018 23:13:59 +0800 Subject: [PATCH 0838/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1e?= =?UTF-8?q?xt=E5=8F=82=E6=95=B0=E6=A3=80=E6=B5=8B=20=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E4=B8=8D=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 30b58627..0bb6f4f9 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -800,8 +800,8 @@ abstract class Rule } // 伪静态后缀检测 - if ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) - || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|'))) { + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { return false; } -- Gitee From aaae036fd0eb5c2a847901eedbf0251108aaa188 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 18 Jan 2018 23:26:52 +0800 Subject: [PATCH 0839/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=A2=9E=E5=8A=A0g?= =?UTF-8?q?etError=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/library/think/Model.php b/library/think/Model.php index bfd140a4..e09189b6 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -98,6 +98,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected $queryInstance; + /** + * 错误信息 + * @var mixed + */ + protected $error; + /** * 架构函数 * @access public @@ -867,6 +873,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $count; } + /** + * 获取错误信息 + * @access public + * @return mixed + */ + public function getError() + { + return $this->error; + } + /** * 解序列化后处理 */ -- Gitee From 2912353085c850d45acf301f9797d8a41b93be11 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 19 Jan 2018 08:57:57 +0800 Subject: [PATCH 0840/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BB?= =?UTF-8?q?=E7=9A=84getTableFields=E5=92=8CgetFieldsType=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81=E8=A1=A8=E5=90=8D=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3bdaf7cc..2fd108f6 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -437,8 +437,12 @@ class Query * @param string $tableName 数据表名 * @return array */ - public function getTableFields($tableName) + public function getTableFields($tableName = '') { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + return $this->connection->getTableFields($tableName); } @@ -449,8 +453,12 @@ class Query * @param string $field 字段名 * @return array|string */ - public function getFieldsType($tableName, $field = null) + public function getFieldsType($tableName = '', $field = null) { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + return $this->connection->getFieldsType($tableName, $field); } @@ -911,11 +919,11 @@ class Query if (true === $field) { // 获取全部字段 - $fields = $this->getTableFields($tableName ?: (isset($this->options['table']) ? $this->options['table'] : $this->getTable())); + $fields = $this->getTableFields($tableName); $field = $fields ?: ['*']; } elseif ($except) { // 字段排除 - $fields = $this->getTableFields($tableName ?: (isset($this->options['table']) ? $this->options['table'] : $this->getTable())); + $fields = $this->getTableFields($tableName); $field = $fields ? array_diff($fields, $field) : $field; } -- Gitee From c3ba1e62c725a2efe25fb3c5fecbc83c9a16dbea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 19 Jan 2018 09:06:10 +0800 Subject: [PATCH 0841/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=B1=BBtoCollecti?= =?UTF-8?q?on=E6=96=B9=E6=B3=95=E5=A2=9E=E5=8A=A0=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=95=B0=E6=8D=AE=E9=9B=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/Conversion.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/think/model/concern/Conversion.php b/library/think/model/concern/Conversion.php index b6c7cd64..697e495b 100644 --- a/library/think/model/concern/Conversion.php +++ b/library/think/model/concern/Conversion.php @@ -223,13 +223,15 @@ trait Conversion * 转换数据集为数据集对象 * @access public * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 * @return Collection */ - public function toCollection($collection) + public function toCollection($collection, $resultSetType = null) { - if ($this->resultSetType && false !== strpos($this->resultSetType, '\\')) { - $class = $this->resultSetType; - $collection = new $class($collection); + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); } else { $collection = new ModelCollection($collection); } -- Gitee From 57026de4c60ee300896124f32554de8ebb3cc921 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 19 Jan 2018 16:06:41 +0800 Subject: [PATCH 0842/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index f324394b..ceff17d7 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.3'; + const VERSION = '5.1.4'; /** * 当前模块路径 -- Gitee From 71d15f4b7970126d29b9f8d63a6115ea8a71824c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 20 Jan 2018 13:31:35 +0800 Subject: [PATCH 0843/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/facade/Log.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/facade/Log.php b/library/think/facade/Log.php index 8cba4a8c..3a1fbf88 100644 --- a/library/think/facade/Log.php +++ b/library/think/facade/Log.php @@ -24,7 +24,7 @@ use think\Facade; * @method bool check(array $config) static 检查日志写入权限 * @method bool save() static 保存调试信息 * @method void write(mixed $msg, string $type = 'info', bool $force = false) static 实时写入日志信息 - * @method void log(mixed $message, array $context = []) static 记录日志信息 + * @method void log(string $level,mixed $message, array $context = []) static 记录日志信息 * @method void emergency(mixed $message, array $context = []) static 记录emergency信息 * @method void alert(mixed $message, array $context = []) static 记录alert信息 * @method void critical(mixed $message, array $context = []) static 记录critical信息 -- Gitee From 7f2e76cf51a33d98162a74ab3c2f5c46813b13ad Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 20 Jan 2018 13:42:19 +0800 Subject: [PATCH 0844/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E9=9B=86=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 2fd108f6..c38eec38 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2546,12 +2546,16 @@ class Query } else { $resultSet = $this->model->toCollection($resultSet); } - } elseif ('collection' == $this->connection->getConfig('resultset_type')) { - // 返回Collection对象 - $resultSet = new Collection($resultSet); - } elseif (!empty($this->options['json'])) { - foreach ($resultSet as &$result) { - $this->jsonResult($result, $this->options['json'], true); + } else { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); } } -- Gitee From e19bacfc8e51f78a54fff07d3727d09b7c81b2e0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 20 Jan 2018 17:09:11 +0800 Subject: [PATCH 0845/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 1 + library/think/db/builder/Pgsql.php | 1 + library/think/db/builder/Sqlite.php | 1 + library/think/db/builder/Sqlsrv.php | 1 + 4 files changed, 4 insertions(+) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 35d76d6b..4ba53144 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -122,6 +122,7 @@ class Mysql extends Builder if ('__TABLE__' == $table) { $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } if (isset($alias[$table])) { diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index d638835c..cfcc8f0a 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -68,6 +68,7 @@ class Pgsql extends Builder if ('__TABLE__' == $table) { $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } if (isset($alias[$table])) { diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 18ece89f..56d64a23 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -71,6 +71,7 @@ class Sqlite extends Builder if ('__TABLE__' == $table) { $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } if (isset($alias[$table])) { diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index deb0d3ce..f1cd0c98 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -88,6 +88,7 @@ class Sqlsrv extends Builder if ('__TABLE__' == $table) { $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; } if (isset($alias[$table])) { -- Gitee From 6ab3df1ae372b7f8abd3e0a5c0bb3c3087068f97 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 20 Jan 2018 17:38:14 +0800 Subject: [PATCH 0846/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BOneToOne=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=9A=84relationCount=20=E6=8A=9B=E5=87=BA=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/OneToOne.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 8eeefd6f..9bfd57af 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -243,10 +243,14 @@ abstract class OneToOne extends Relation * @access public * @param Model $result 数据对象 * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 * @return integer */ - public function relationCount($result, $closure) - {} + public function relationCount($result, $closure, $aggregate = 'count', $field = '*') + { + throw new Exception('relation not support: ' . $aggregate); + } /** * 一对一 关联模型预查询拼装 -- Gitee From 4d3e5f02f1478e1a460c8c1a511afb2f0431475d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 20 Jan 2018 18:06:51 +0800 Subject: [PATCH 0847/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 323360c6..94134aa6 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -841,7 +841,7 @@ abstract class Connection $result = isset($resultSet[0]) ? $resultSet[0] : null; } - if (isset($cache) && false !== $result) { + if (isset($cache) && $result) { // 缓存数据 $this->cacheData($key, $result, $cache); } @@ -917,7 +917,7 @@ abstract class Connection } } - if (isset($cache) && false !== $resultSet) { + if (isset($cache) && $resultSet) { // 缓存数据集 $this->cacheData($key, $resultSet, $cache); } -- Gitee From 4bf1282fdeeef0c5b5a03f5ad804b6e0a11871eb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 20 Jan 2018 18:33:45 +0800 Subject: [PATCH 0848/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 94134aa6..408c69a8 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -917,7 +917,7 @@ abstract class Connection } } - if (isset($cache) && $resultSet) { + if (isset($cache) && false !== $resultSet) { // 缓存数据集 $this->cacheData($key, $resultSet, $cache); } -- Gitee From f2b79a7966657ec1c323f0016433ed7fb50559ae Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 21 Jan 2018 10:20:18 +0800 Subject: [PATCH 0849/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Session=E7=B1=BBflu?= =?UTF-8?q?sh=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/library/think/Session.php b/library/think/Session.php index 247d272e..bdd74d63 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -344,17 +344,15 @@ class Session */ public function flush() { - if ($this->init) { - $item = $this->get('__flash__'); + $item = $this->get('__flash__'); - if (!empty($item)) { - $time = $item['__time__']; + if (!empty($item)) { + $time = $item['__time__']; - if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { - unset($item['__time__']); - $this->delete($item); - $this->set('__flash__', []); - } + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + $this->delete($item); + $this->set('__flash__', []); } } } -- Gitee From 980e4a638452b2718babb69b0188f4f6e40cd6f2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 21 Jan 2018 16:32:35 +0800 Subject: [PATCH 0850/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0run=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=20request=E7=B1=BBpathinfo=E6=94=B9=E8=BF=9B=E6=94=AF?= =?UTF-8?q?=E6=8C=81cli-server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Console.php | 1 + library/think/Request.php | 2 + library/think/console/command/RunServer.php | 54 +++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 library/think/console/command/RunServer.php diff --git a/library/think/Console.php b/library/think/Console.php index 70e034d7..5cb1ccde 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -45,6 +45,7 @@ class Console "think\\console\\command\\optimize\\Config", "think\\console\\command\\optimize\\Schema", "think\\console\\command\\optimize\\Route", + "think\\console\\command\\RunServer", ]; public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') diff --git a/library/think/Request.php b/library/think/Request.php index bd2f69bf..bd1d2b62 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -546,6 +546,8 @@ class Request } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ('cli-server' == PHP_SAPI) { + $_SERVER['PATH_INFO'] = strpos($_SERVER['REQUEST_URI'], '?') ? strstr($_SERVER['REQUEST_URI'], '?', true) : $_SERVER['REQUEST_URI']; } // 分析PATHINFO信息 diff --git a/library/think/console/command/RunServer.php b/library/think/console/command/RunServer.php new file mode 100644 index 00000000..60edfd98 --- /dev/null +++ b/library/think/console/command/RunServer.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; + +class RunServer extends Command +{ + + public function configure() + { + $this->setName('run') + ->addOption('host', 'H', Option::VALUE_OPTIONAL, + 'The host to server the application on', '127.0.0.1') + ->addOption('port', 'p', Option::VALUE_OPTIONAL, + 'The port to server the application on', 8000) + ->addOption('root', 'r', Option::VALUE_OPTIONAL, + 'The document root of the application', App::getRootPath() . 'public') + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . '/router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} -- Gitee From 697425302a03b64f9ec1c61e072f1ba2c7b43237 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 22 Jan 2018 13:52:00 +0800 Subject: [PATCH 0851/1384] =?UTF-8?q?session=E7=B1=BB=E5=A2=9E=E5=8A=A0use?= =?UTF-8?q?=5Flock=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=90=AF=E7=94=A8=E9=94=81=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=20=E9=BB=98=E8=AE=A4=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/library/think/Session.php b/library/think/Session.php index bdd74d63..f52bbc62 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -45,6 +45,12 @@ class Session */ protected $lockTimeout = 3; + /** + * 是否启用锁机制 + * @var bool + */ + protected $lock = false; + /** * 设置或者获取session作用域(前缀) * @access public @@ -92,6 +98,10 @@ class Session $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'])) { @@ -180,7 +190,7 @@ class Session */ public function set($name, $value, $prefix = null) { - $this->lock(); // lock必须先于 $this->boot() + $this->lock(); empty($this->init) && $this->boot(); @@ -200,7 +210,6 @@ class Session $_SESSION[$name] = $value; } - $this->pause(); $this->unlock(); } @@ -213,7 +222,7 @@ class Session */ public function get($name = '', $prefix = null) { - $this->lock(); // lock必须先于 $this->boot() + $this->lock(); empty($this->init) && $this->boot(); @@ -234,7 +243,6 @@ class Session } } - $this->pause(); $this->unlock(); return $value; @@ -247,6 +255,7 @@ class Session { // 不在 init 方法中实例化lockDriver,是因为 init 方法不一定先于 set 或 get 方法调用 $config = Container::get('config')->pull('session'); + 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']); @@ -256,10 +265,12 @@ class Session $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']; } @@ -272,12 +283,17 @@ class Session */ 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(); @@ -293,6 +309,12 @@ class Session */ 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); @@ -344,6 +366,10 @@ class Session */ public function flush() { + if (!$this->init) { + return; + } + $item = $this->get('__flash__'); if (!empty($item)) { @@ -367,6 +393,7 @@ class Session public function delete($name, $prefix = null) { empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; if (is_array($name)) { @@ -499,7 +526,6 @@ class Session { // 暂停session session_write_close(); - $this->init = false; } } -- Gitee From f72355428e669f49e291103ddcb5bf103309e78d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 22 Jan 2018 17:16:57 +0800 Subject: [PATCH 0852/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96File=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E7=A9=BA=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 9b04dc04..d4c52033 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -72,9 +72,10 @@ class File extends Driver * 取得变量的存储文件名 * @access protected * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 * @return string */ - protected function getCacheKey($name) + protected function getCacheKey($name, $auto = false) { $name = hash($this->options['hash_type'], $name); @@ -90,7 +91,7 @@ class File extends Driver $filename = $this->options['path'] . $name . '.php'; $dir = dirname($filename); - if (!is_dir($dir)) { + if ($auto && !is_dir($dir)) { mkdir($dir, 0755, true); } @@ -166,7 +167,7 @@ class File extends Driver } $expire = $this->getExpireTime($expire); - $filename = $this->getCacheKey($name); + $filename = $this->getCacheKey($name, true); if ($this->tag && !is_file($filename)) { $first = true; -- Gitee From 105e72e47d450f7463d00fed65ef8001c1fb2af4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 12:15:08 +0800 Subject: [PATCH 0853/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E7=BB=91=E5=AE=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8param=E6=96=B9=E6=B3=95=E8=8E=B7=E5=8F=96=20R?= =?UTF-8?q?oute::domain('user','user=3Fstatus=3D1');=20=E6=88=96=E8=80=85?= =?UTF-8?q?=20Route::domain('user','user')->append(['status'=3D>1]);=20?= =?UTF-8?q?=E5=88=86=E7=BB=84=E8=B7=AF=E7=94=B1=E4=BA=A6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=AF=A5=E6=96=B9=E5=BC=8F=E4=BC=A0=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 65 +++++++++++++++++++------------ library/think/route/Domain.php | 10 ++--- library/think/route/Rule.php | 17 ++++++++ library/think/route/RuleGroup.php | 12 +++--- library/think/route/RuleItem.php | 4 ++ 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index fd93042b..af4a795a 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -232,31 +232,25 @@ class Route public function domain($name, $rule = '', $option = [], $pattern = []) { // 支持多个域名使用相同路由规则 - $domain = is_array($name) ? array_shift($name) : $name; + $domainName = is_array($name) ? array_shift($name) : $name; - if ('*' != $domain && !strpos($domain, '.')) { - $domain .= '.' . $this->getRootDomain(); + if ('*' != $domainName && !strpos($domainName, '.')) { + $domainName .= '.' . $this->getRootDomain(); } $route = $this->config->get('url_lazy_route') ? $rule : null; - $this->domains[$domain] = new Domain($this, $domain, $route, $option, $pattern); + $domain = new Domain($this, $domainName, $route, $option, $pattern); if (is_null($route)) { // 获取原始分组 $originGroup = $this->group; // 设置当前域名 - $this->domain = $domain; - $this->group = $this->createTopGroup($this->domains[$domain]); - - // 执行域名路由 - if ($rule instanceof \Closure) { - Container::getInstance()->invokeFunction($rule); - } elseif (is_array($rule)) { - $this->rules($rule); - } elseif ($rule) { - $this->bind($rule); - } + $this->domain = $domainName; + $this->group = $this->createTopGroup($domain); + + // 解析域名路由规则 + $this->parseGroupRule($domain, $rule); // 还原默认域名 $this->domain = $this->host; @@ -264,6 +258,8 @@ class Route $this->group = $originGroup; } + $this->domains[$domainName] = $domain; + if (is_array($name) && !empty($name)) { $root = $this->getRootDomain(); foreach ($name as $item) { @@ -271,12 +267,38 @@ class Route $item .= '.' . $root; } - $this->domains[$item] = $domain; + $this->domains[$item] = $domainName; } } // 返回域名对象 - return $this->domains[$domain]; + return $domain; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param RuleGroup $group 分组路由对象 + * @param mixed $rule 路由规则 + * @return void + */ + public function parseGroupRule($group, $rule) + { + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif ($rule instanceof Response) { + $group->setRule($rule); + } elseif (is_array($rule)) { + $this->rules($rule); + } elseif ($rule) { + if (false !== strpos($rule, '?')) { + list($rule, $query) = explode('?', $rule); + parse_str($query, $vars); + $group->append($vars); + } + + $this->bind($rule); + } } /** @@ -578,13 +600,8 @@ class Route $this->group = $group; - if ($route instanceof \Closure) { - Container::getInstance()->invokeFunction($route); - } elseif ($route instanceof Response) { - $group->setRule($route); - } else { - $this->rules($route); - } + // 解析分组路由规则 + $this->parseGroupRule($group, $route); $this->group = $parent; } diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index fee9c4b3..d8f3e4f2 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -58,16 +58,12 @@ class Domain extends RuleGroup } $group = new RuleGroup($this->router); + $this->addRule($group); + $this->router->setGroup($group); - if ($this->rule instanceof \Closure) { - Container::getInstance()->invokeFunction($this->rule); - } elseif (is_array($this->rule)) { - $this->router->rules($this->rule); - } else { - $this->router->bind($this->rule); - } + $this->router->parseGroupRule($this, $this->rule); $this->rule = null; } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 0bb6f4f9..107a863a 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -127,6 +127,23 @@ abstract class Rule return isset($this->option[$name]) ? $this->option[$name] : null; } + /** + * 附加路由隐式参数 + * @access public + * @param array $append + * @return $this + */ + public function append(array $append = []) + { + if (isset($this->option['append'])) { + $this->option['append'] = array_merge($this->option['append'], $append); + } else { + $this->option['append'] = $append; + } + + return $this; + } + /** * 设置路由请求类型 * @access public diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 2edac770..c8e464cc 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -117,13 +117,10 @@ class RuleGroup extends Rule $this->router->setGroup($this); - if ($this->rule instanceof \Closure) { - Container::getInstance()->invokeFunction($this->rule); - } else { - $this->router->rules($this->rule); - } + $this->router->parseGroupRule($this, $this->rule); $this->router->setGroup($group); + $this->rule = null; } @@ -152,6 +149,10 @@ class RuleGroup extends Rule $completeMatch = $this->option['complete_match']; } + if (!empty($this->option['append'])) { + $request->route($this->option['append']); + } + if (isset($rules[$url])) { // 快速定位 $item = $rules[$url]; @@ -246,4 +247,5 @@ class RuleGroup extends Rule return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : []; } } + } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 3373da71..ed688d71 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -122,6 +122,10 @@ class RuleItem extends Rule $this->mergeGroupOptions(); $option = $this->option; + if (!empty($option['append'])) { + $request->route($option['append']); + } + // 是否区分 / 地址访问 if (!empty($option['remove_slash']) && '/' != $this->name) { $this->name = rtrim($this->name, '/'); -- Gitee From 5c10e2942775eb95c3f8b7a87c4d0123851a4af4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 14:43:28 +0800 Subject: [PATCH 0854/1384] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index bd1d2b62..749ae276 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1777,14 +1777,15 @@ class Request return; } + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + if ($key instanceof \Closure) { $key = call_user_func_array($key, [$this]); } elseif (true === $key) { - foreach ($except as $rule) { - if (0 === stripos($this->url(), $rule)) { - return; - } - } // 自动缓存功能 $key = '__URL__'; } elseif (strpos($key, '|')) { @@ -1793,7 +1794,7 @@ class Request // 特殊规则替换 if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); } if (false !== strpos($key, ':')) { -- Gitee From 9016737c6a58eb58b0c5c2feff06c0a0237735c8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 15:12:33 +0800 Subject: [PATCH 0855/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E7=9A=84=E5=B9=B6=E5=8F=91=E5=86=99=E5=85=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index cc3a834c..deb2ef7c 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -101,7 +101,11 @@ class File { // 检测日志文件大小,超过配置大小则备份日志文件重新生成 if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { - rename($destination, dirname($destination) . '/' . time() . '-' . basename($destination)); + try { + rename($destination, dirname($destination) . '/' . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + $this->writed[$destination] = false; } -- Gitee From d69b22b03d616db3487e36f35c8c5ca3cc50dc3d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 15:20:09 +0800 Subject: [PATCH 0856/1384] =?UTF-8?q?=E4=B8=80=E5=A4=84=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c38eec38..7ad2c4b7 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1683,7 +1683,7 @@ class Query * 指定Field排序 order('id',[1,2,3],'desc') * @access public * @param string|array $field 排序字段 - * @param string $values 排序值 + * @param array $values 排序值 * @param string $order * @return $this */ -- Gitee From c3ee4670747cad2de29d3543c575b1f5dd9e5aba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 16:46:54 +0800 Subject: [PATCH 0857/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 84 +++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 7ad2c4b7..2c49a474 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1107,9 +1107,7 @@ class Query { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('AND', $field, $op, $condition, $param); - - return $this; + return $this->parseWhereExp('AND', $field, $op, $condition, $param); } /** @@ -1124,9 +1122,7 @@ class Query { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('OR', $field, $op, $condition, $param); - - return $this; + return $this->parseWhereExp('OR', $field, $op, $condition, $param); } /** @@ -1141,9 +1137,7 @@ class Query { $param = func_get_args(); array_shift($param); - $this->parseWhereExp('XOR', $field, $op, $condition, $param); - - return $this; + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); } /** @@ -1155,8 +1149,7 @@ class Query */ public function whereNull($field, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'null', null); - return $this; + return $this->parseWhereExp($logic, $field, 'null', null); } /** @@ -1168,8 +1161,7 @@ class Query */ public function whereNotNull($field, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'notnull', null); - return $this; + return $this->parseWhereExp($logic, $field, 'notnull', null); } /** @@ -1208,8 +1200,7 @@ class Query */ public function whereIn($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'in', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'in', $condition); } /** @@ -1222,8 +1213,7 @@ class Query */ public function whereNotIn($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'not in', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'not in', $condition); } /** @@ -1236,8 +1226,7 @@ class Query */ public function whereLike($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'like', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'like', $condition); } /** @@ -1250,8 +1239,7 @@ class Query */ public function whereNotLike($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'not like', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'not like', $condition); } /** @@ -1264,8 +1252,7 @@ class Query */ public function whereBetween($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'between', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'between', $condition); } /** @@ -1278,8 +1265,7 @@ class Query */ public function whereNotBetween($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'not between', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'not between', $condition); } /** @@ -1298,9 +1284,7 @@ class Query $operator = '='; } - $this->whereExp($field1, $operator . ' ' . $field2, $logic); - - return $this; + return $this->whereExp($field1, $operator . ' ' . $field2, $logic); } /** @@ -1329,22 +1313,26 @@ class Query */ public function whereExp($field, $condition, $logic = 'AND') { - $this->parseWhereExp($logic, $field, 'exp', $condition); - return $this; + return $this->parseWhereExp($logic, $field, 'exp', $condition); } /** * 分析查询表达式 * @access public - * @param string $logic 查询逻辑 and or xor - * @param string|array|\Closure $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @param array $param 查询参数 - * @return void + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return $this */ protected function parseWhereExp($logic, $field, $op, $condition, $param = []) { + if ($field instanceof $this) { + $this->options['where'] = $field->getOptions('where'); + return $this; + } + $logic = strtoupper($logic); if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { @@ -1380,7 +1368,7 @@ class Query $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; } - return; + return $this; } elseif ($field && is_string($field)) { // 字符串查询 $where = [$field, 'null', '']; @@ -1403,15 +1391,15 @@ class Query } } - if (empty($where)) { - return; + if (!empty($where)) { + if (isset($this->options['where'][$logic][$field])) { + $this->options['where'][$logic][] = $where; + } else { + $this->options['where'][$logic][$field] = $where; + } } - if (isset($this->options['where'][$logic][$field])) { - $this->options['where'][$logic][] = $where; - } else { - $this->options['where'][$logic][$field] = $where; - } + return $this; } /** @@ -2504,7 +2492,9 @@ class Query */ public function select($data = null) { - if ($data instanceof \Closure) { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { $data($this); $data = null; } @@ -2578,7 +2568,9 @@ class Query */ public function find($data = null) { - if ($data instanceof \Closure) { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { $data($this); $data = null; } -- Gitee From 4bbaa692b2e8eacd12a1a0d6442a169c8b5751b2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 16:59:21 +0800 Subject: [PATCH 0858/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8D=95=E4=B8=AA=E6=97=A5=E5=BF=97=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=EF=BC=8C=E9=85=8D=E7=BD=AE=20'singl?= =?UTF-8?q?e'=3D>'=E6=96=87=E4=BB=B6=E5=90=8D'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index deb2ef7c..3ec4cd1c 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -49,7 +49,8 @@ class File public function save(array $log = []) { if ($this->config['single']) { - $destination = $this->config['path'] . 'single.log'; + $name = is_string($single) ? $single : 'single'; + $destination = $this->config['path'] . $name . '.log'; } else { $cli = PHP_SAPI == 'cli' ? '_cli' : ''; $destination = $this->config['path'] . date('Ym') . '/' . date('d') . $cli . '.log'; @@ -71,7 +72,7 @@ class File if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 if ($this->config['single']) { - $filename = $path . '/' . $type . '.log'; + $filename = $path . '/' . $name . '_' . $type . '.log'; } else { $filename = $path . '/' . date('d') . '_' . $type . $cli . '.log'; } -- Gitee From d7927f01e17d75d94d44c5515e7f501371694a98 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 23 Jan 2018 19:11:56 +0800 Subject: [PATCH 0859/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E8=A7=84=E5=88=99=E7=9A=84=E5=9F=9F=E5=90=8D=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E7=BA=A6=E6=9D=9F=20=20Request=E7=B1=BB=E5=A2=9E=E5=8A=A0subDo?= =?UTF-8?q?main=E6=96=B9=E6=B3=95=E7=94=A8=E4=BA=8E=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E5=AD=90=E5=9F=9F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 31 +++++++++++++++++++++++++++++++ library/think/Route.php | 31 +++++-------------------------- library/think/route/Rule.php | 2 +- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 749ae276..1868c5f4 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -39,6 +39,12 @@ class Request */ protected $domain; + /** + * 子域名 + * @var string + */ + protected $subDomain; + /** * 泛域名 * @var string @@ -397,9 +403,34 @@ class Request return $this->domain; } + /** + * 获取当前子域名 + * @access public + * @return string|$this + */ + public function subDomain() + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->config->get('app.url_domain_root'); + + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($this->host(), $rootDomain, true), '.')); + } else { + $domain = explode('.', $this->host(), -2); + } + + $this->subDomain = implode('.', $domain); + } + + return $this->subDomain; + } + /** * 设置或获取当前泛域名的值 * @access public + * @param string $domain 域名 * @return string|$this */ public function panDomain($domain = null) diff --git a/library/think/Route.php b/library/think/Route.php index af4a795a..04c9cd3e 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -336,7 +336,7 @@ class Route $domain = $this->domain; } - $subDomain = implode('.', $this->getSubDomain()); + $subDomain = $this->request->subDomain(); if (strpos($subDomain, '.')) { $name = '*' . strstr($subDomain, '.'); @@ -925,26 +925,6 @@ class Route } } - /** - * 获取当前子域名 - * @access protected - * @return array - */ - protected function getSubDomain() - { - // 获取当前主域名 - $rootDomain = $this->config->get('app.url_domain_root'); - - if ($rootDomain) { - // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($this->host, $rootDomain, true), '.')); - } else { - $domain = explode('.', $this->host, -2); - } - - return $domain; - } - /** * 检测域名的路由规则 * @access protected @@ -954,14 +934,13 @@ class Route protected function checkDomain() { // 获取当前子域名 - $domain = $this->getSubDomain(); + $subDomain = $this->request->subDomain(); $item = false; - if (!empty($domain) && count($this->domains) > 1) { - // 当前子域名 - $subDomain = implode('.', $domain); - $domain2 = array_pop($domain); + if ($subDomain && count($this->domains) > 1) { + $domain = explode('.', $subDomain); + $domain2 = array_pop($domain); if ($domain) { // 存在三级域名 diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 107a863a..7fe8d3e8 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -823,7 +823,7 @@ abstract class Rule } // 域名检查 - if ((isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], $this->subDomain]))) { + if ((isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], $request->subDomain()]))) { return false; } -- Gitee From aefc5590aeb55d564e29551a77899b9d3758bba7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 24 Jan 2018 16:13:32 +0800 Subject: [PATCH 0860/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BResponse=E7=B1=BB?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0allowCache=E6=96=B9=E6=B3=95=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E6=98=AF=E5=90=A6=E5=85=81=E8=AE=B8=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=BC=93=E5=AD=98=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89sendData=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Response.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/library/think/Response.php b/library/think/Response.php index a92b113a..97bf61dd 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -39,6 +39,12 @@ class Response */ protected $code = 200; + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + /** * 输出参数 * @var array @@ -121,7 +127,7 @@ class Response Container::get('debug')->inject($this, $data); } - if (200 == $this->code) { + if (200 == $this->code && $this->allowCache) { $cache = Container::get('request')->getCache(); if ($cache) { $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; @@ -141,7 +147,7 @@ class Response } } - echo $data; + $this->sendData($data); if (function_exists('fastcgi_finish_request')) { // 提高页面响应 @@ -168,6 +174,17 @@ class Response return $data; } + /** + * 输出数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return void + */ + protected function sendData($data) + { + echo $data; + } + /** * 输出的参数 * @access public @@ -194,6 +211,19 @@ class Response return $this; } + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache($cache) + { + $this->allowCache = $cache; + + return $this; + } + /** * 设置响应头 * @access public -- Gitee From 86b0b4512496218a7448d174ed05a64aceeece86 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 24 Jan 2018 16:26:21 +0800 Subject: [PATCH 0861/1384] =?UTF-8?q?=E7=AE=80=E5=8C=96Env=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Env.php | 36 +++--------------------------------- library/think/Response.php | 2 +- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/library/think/Env.php b/library/think/Env.php index 811fd1ab..1416a8b1 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -55,23 +55,7 @@ class Env return $this->data[$name]; } - $result = getenv('PHP_' . $name); - - if (false !== $result) { - if ('false' === $result) { - $result = false; - } elseif ('true' === $result) { - $result = true; - } - - if (!isset($this->data[$name])) { - $this->data[$name] = $result; - } - - return $result; - } else { - return $default; - } + return $default; } /** @@ -87,23 +71,9 @@ class Env $env = array_change_key_case($env, CASE_UPPER); $this->data = array_merge($this->data, $env); - - foreach ($env as $key => $val) { - $name = 'PHP_' . $key; - if (is_array($val)) { - foreach ($val as $k => $v) { - $item = $name . '_' . strtoupper($k); - putenv("$item=$v"); - } - } else { - putenv("$name=$val"); - } - } } else { - $key = strtoupper($env); - $name = 'PHP_' . $key; - putenv("$name=$value"); - $this->data[$key] = $value; + $name = strtoupper(str_replace('.', '_', $env)); + $this->data[$name] = $value; } } } diff --git a/library/think/Response.php b/library/think/Response.php index 97bf61dd..e554ab31 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -177,7 +177,7 @@ class Response /** * 输出数据 * @access protected - * @param mixed $data 要处理的数据 + * @param string $data 要处理的数据 * @return void */ protected function sendData($data) -- Gitee From 346214f4796b2da14587e4bb87dbd6a45dda635b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 24 Jan 2018 18:49:42 +0800 Subject: [PATCH 0862/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0trace=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/debug/Console.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/debug/Console.php b/library/think/debug/Console.php index eb6f6b4d..dd063c1c 100644 --- a/library/think/debug/Console.php +++ b/library/think/debug/Console.php @@ -133,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; -- Gitee From a98e2ad2bfc40dce5f403c68cf8906e7ecde42f5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 24 Jan 2018 19:19:38 +0800 Subject: [PATCH 0863/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BMorphTo=E5=85=B3?= =?UTF-8?q?=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/MorphTo.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 280d815b..f89e997b 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -44,6 +44,19 @@ class MorphTo extends Relation $this->relation = $relation; } + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + /** * 延迟获取关联数据 * @access public -- Gitee From 5f47f57a8afcdbe0a20ded8f1fe58aa60803b364 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 25 Jan 2018 11:29:39 +0800 Subject: [PATCH 0864/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=8C=B9=E9=85=8D=E5=90=8E=20=E5=B8=A6?= =?UTF-8?q?=E6=96=9C=E7=BA=BF=E8=AE=BF=E9=97=AE=E5=87=BA=E9=94=99=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5=20=E4=BD=BF=E7=94=A8removeSlash=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index ed688d71..e50318bc 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -129,6 +129,7 @@ class RuleItem extends Rule // 是否区分 / 地址访问 if (!empty($option['remove_slash']) && '/' != $this->name) { $this->name = rtrim($this->name, '/'); + $url = rtrim($url, '|'); } // 检查前置行为 -- Gitee From 06a6743aa5cb68f7c3e0d6de077f4b6a0488729e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 25 Jan 2018 16:55:27 +0800 Subject: [PATCH 0865/1384] =?UTF-8?q?Build=E7=B1=BB=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=9A=84=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Build.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Build.php b/library/think/Build.php index 51a84193..ed3ffe43 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -345,7 +345,7 @@ class Build */ protected function buildCommon($module) { - $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'config.php'; + $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php'; $this->checkDirBuild(dirname($filename)); if (!is_file($filename)) { -- Gitee From 31618ae27c03effc75e179a8a008307936329981 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 26 Jan 2018 11:37:17 +0800 Subject: [PATCH 0866/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Env=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Env.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/library/think/Env.php b/library/think/Env.php index 1416a8b1..12dc8ff9 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -49,9 +49,12 @@ class Env return $this->data; } - $name = strtoupper(str_replace('.', '_', $name)); + $name = strtoupper($name); - if (isset($this->data[$name])) { + if (strpos($name, '.')) { + list($item1, $item2) = explode('.', $name, 2); + return isset($this->data[$item1][$item2]) ? $this->data[$item1][$item2] : $default; + } elseif (isset($this->data[$name])) { return $this->data[$name]; } @@ -70,10 +73,20 @@ class Env if (is_array($env)) { $env = array_change_key_case($env, CASE_UPPER); - $this->data = array_merge($this->data, $env); + 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; + } + } + } elseif (strpos($env, '.')) { + list($item1, $item2) = explode('.', strtoupper($env), 2); + $this->data[$item1][$item2] = $value; } else { - $name = strtoupper(str_replace('.', '_', $env)); - $this->data[$name] = $value; + $this->data[strtoupper($env)] = $value; } } } -- Gitee From d55867fbeca869649611a19ca39350df6551c41a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 26 Jan 2018 11:56:29 +0800 Subject: [PATCH 0867/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Env.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/library/think/Env.php b/library/think/Env.php index 12dc8ff9..9b8eb4bf 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -40,7 +40,7 @@ class Env * 获取环境变量值 * @access public * @param string $name 环境变量名(支持二级 .号分割) - * @param string $default 默认值 + * @param mixed $default 默认值 * @return mixed */ public function get($name = null, $default = null) @@ -54,18 +54,16 @@ class Env if (strpos($name, '.')) { list($item1, $item2) = explode('.', $name, 2); return isset($this->data[$item1][$item2]) ? $this->data[$item1][$item2] : $default; - } elseif (isset($this->data[$name])) { - return $this->data[$name]; + } else { + return isset($this->data[$name]) ? $this->data[$name] : $default; } - - return $default; } /** * 设置环境变量值 * @access public * @param string|array $env 环境变量 - * @param string $value 值 + * @param mixed $value 值 * @return void */ public function set($env, $value = null) -- Gitee From d130d30deededb902e4e0201ddab888a4f5a2507 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 26 Jan 2018 15:06:10 +0800 Subject: [PATCH 0868/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 3 ++- library/think/db/builder/Pgsql.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 4ba53144..6b61f2c4 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -114,7 +114,8 @@ class Mysql extends Builder if (strpos($key, '->') && false === strpos($key, '(')) { // JSON字段支持 list($field, $name) = explode('->', $key); - $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; + + $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . $name . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index cfcc8f0a..8c7d2b88 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -57,9 +57,9 @@ 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); -- Gitee From 47434a20f8726ee4799bd4656743adfeb0dd36c1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 26 Jan 2018 18:44:50 +0800 Subject: [PATCH 0869/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BEnv=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Env.php | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/library/think/Env.php b/library/think/Env.php index 9b8eb4bf..2c41794f 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -39,7 +39,7 @@ class Env /** * 获取环境变量值 * @access public - * @param string $name 环境变量名(支持二级 .号分割) + * @param string $name 环境变量名 * @param mixed $default 默认值 * @return mixed */ @@ -49,13 +49,33 @@ class Env return $this->data; } - $name = strtoupper($name); + $name = strtoupper(str_replace('.', '_', $name)); - if (strpos($name, '.')) { - list($item1, $item2) = explode('.', $name, 2); - return isset($this->data[$item1][$item2]) ? $this->data[$item1][$item2] : $default; + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default); + } + + protected function getEnv($name, $default = null) + { + $result = getenv('PHP_' . $name); + + if (false !== $result) { + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; } else { - return isset($this->data[$name]) ? $this->data[$name] : $default; + return $default; } } @@ -74,17 +94,16 @@ class Env foreach ($env as $key => $val) { if (is_array($val)) { foreach ($val as $k => $v) { - $this->data[$key][strtoupper($k)] = $v; + $this->data[$key . '_' . strtoupper($k)] = $v; } } else { $this->data[$key] = $val; } } - } elseif (strpos($env, '.')) { - list($item1, $item2) = explode('.', strtoupper($env), 2); - $this->data[$item1][$item2] = $value; } else { - $this->data[strtoupper($env)] = $value; + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; } } } -- Gitee From 09640064efc44ff4d5d47e3bd3916d5eb24bd1fa Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 28 Jan 2018 22:37:36 +0800 Subject: [PATCH 0870/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bjoin=E8=87=AA?= =?UTF-8?q?=E5=85=B3=E8=81=94=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 4 ---- library/think/db/Query.php | 17 ++++------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 83128a4d..511f3a18 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -230,10 +230,6 @@ abstract class Builder $options = $query->getOptions(); foreach ((array) $tables as $key => $table) { if (!is_numeric($key)) { - if (strpos($key, '@think')) { - $key = strstr($key, '@think', true); - } - $key = $this->connection->parseSqlTable($key); $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); } else { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 2c49a474..5c76b9f1 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -816,7 +816,7 @@ class Query /** * 获取Join表名及别名 支持 - * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * ['prefix_table或者子查询'=>'alias'] 'table alias' * @access protected * @param array|string $join * @param string $alias @@ -824,10 +824,8 @@ class Query */ protected function getJoinTable($join, &$alias = null) { - // 传入的表名为数组 if (is_array($join)) { - $table = key($join); - $alias = current($join); + $table = $join; } else { $join = trim($join); @@ -850,17 +848,10 @@ class Query $table = $this->getTable($table); } } - } - if (isset($alias) && $table != $alias) { - if (isset($this->options['alias'][$table])) { - $table = $table . '@think' . uniqid(); - } elseif ($this->gettable() == $table) { - $table = $table . '@think' . uniqid(); + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; } - - $table = [$table => $alias]; - $this->alias($table); } return $table; -- Gitee From 50a203360e7fd6e4a5f629cc17c021f556cc167f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 12:19:55 +0800 Subject: [PATCH 0871/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcase=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/template/taglib/Cx.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/template/taglib/Cx.php b/library/think/template/taglib/Cx.php index 72a3ced1..ad741f28 100644 --- a/library/think/template/taglib/Cx.php +++ b/library/think/template/taglib/Cx.php @@ -294,7 +294,7 @@ class Cx extends Taglib */ public function tagCase($tag, $content) { - $value = !empty($tag['expression']) ? $tag['expression'] : $tag['value']; + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; $flag = substr($value, 0, 1); if ('$' == $flag || ':' == $flag) { -- Gitee From dc12bbba6ecd526934be54e74e0774d1e79544c9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 14:04:31 +0800 Subject: [PATCH 0872/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=81=9A=E5=90=88?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=AF=B9JSON=E5=AD=97=E6=AE=B5=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 33 +++++++++--------- library/think/db/Connection.php | 15 +++++++++ library/think/db/Query.php | 52 ++++++++++++++++++++--------- library/think/db/builder/Mysql.php | 12 +++---- library/think/db/builder/Pgsql.php | 8 ++--- library/think/db/builder/Sqlite.php | 8 ++--- library/think/db/builder/Sqlsrv.php | 8 ++--- 7 files changed, 85 insertions(+), 51 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 511f3a18..4f98ac95 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -156,11 +156,11 @@ abstract class Builder /** * 数据绑定处理 * @access protected - * @param Query $query 查询对象 - * @param string $key - * @param mixed $data 数据 - * @param array $bind 绑定数据 - * @param string $suffix + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @param string $suffix 绑定后缀 * @return string */ protected function parseDataBind(Query $query, $key, $data, $bind = [], $suffix = '') @@ -178,12 +178,12 @@ abstract class Builder /** * 字段名分析 - * @access protected - * @param Query $query 查询对象 - * @param string $key + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey(Query $query, $key) + public function parseKey(Query $query, $key) { return $key; } @@ -191,8 +191,8 @@ abstract class Builder /** * field分析 * @access protected - * @param Query $query 查询对象 - * @param mixed $fields + * @param Query $query 查询对象 + * @param mixed $fields 字段名 * @return string */ protected function parseField(Query $query, $fields) @@ -220,8 +220,8 @@ abstract class Builder /** * table分析 * @access protected - * @param Query $query 查询对象 - * @param mixed $tables + * @param Query $query 查询对象 + * @param mixed $tables 表名 * @return string */ protected function parseTable(Query $query, $tables) @@ -249,7 +249,7 @@ abstract class Builder /** * where分析 * @access protected - * @param Query $query 查询对象 + * @param Query $query 查询对象 * @param mixed $where 查询条件 * @return string */ @@ -273,9 +273,8 @@ abstract class Builder /** * 生成查询条件SQL * @access public - * @param Query $query 查询对象 - * @param mixed $where - * @param array $options + * @param Query $query 查询对象 + * @param mixed $where 查询条件 * @return string */ public function buildWhere(Query $query, $where) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 408c69a8..c7d3cd45 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1288,6 +1288,21 @@ abstract class Connection return false !== $result ? $result : $default; } + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @return mixed + */ + public function aggregate(Query $query, $aggregate, $field) + { + $field = $aggregate . '(' . $this->builder->parseKey($query, $field) . ') AS tp_' . strtolower($aggregate); + + return $this->value($query, $field, 0); + } + /** * 得到某个列的数组 * @access public diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 5c76b9f1..54a4b5d8 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -526,10 +526,9 @@ class Query * @access public * @param string $field 字段名 * @param mixed $default 默认值 - * @param bool $force 强制转为数字类型 * @return mixed */ - public function value($field, $default = null, $force = false) + public function value($field, $default = null) { $this->parseOptions(); @@ -539,10 +538,6 @@ class Query return $result; } - if ($force) { - $result += 0; - } - return $result; } @@ -560,6 +555,31 @@ class Query return $this->connection->column($this, $field, $key); } + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + $this->parseOptions(); + + $result = $this->connection->aggregate($this, $aggregate, $field); + + if (!empty($this->options['fetch_sql'])) { + return $result; + } + + if ($force) { + $result += 0; + } + + return $result; + } + /** * COUNT查询 * @access public @@ -579,10 +599,10 @@ class Query $query->fetchSql(true); } - return $query->value('COUNT(*) AS tp_count', 0, true); + return $query->aggregate('COUNT', '*', 0, true); } - return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); + return $this->aggregate('COUNT', $field, 0, true); } /** @@ -593,31 +613,31 @@ class Query */ public function sum($field) { - return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); + return $this->aggregate('SUM', $field, 0, true); } /** * MIN查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function min($field, $force = true) { - return $this->value('MIN(' . $field . ') AS tp_min', 0, $force); + return $this->aggregate('MIN', $field, 0, $force); } /** * MAX查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function max($field, $force = true) { - return $this->value('MAX(' . $field . ') AS tp_max', 0, $force); + return $this->aggregate('MAX', $field, 0, $force); } /** @@ -628,7 +648,7 @@ class Query */ public function avg($field) { - return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); + return $this->aggregate('AVG', $field, 0, true); } /** diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 6b61f2c4..5352755a 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -102,12 +102,12 @@ class Mysql extends Builder /** * 字段和表名处理 - * @access protected - * @param Query $query 查询对象 - * @param string $key + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey(Query $query, $key) + public function parseKey(Query $query, $key) { $key = trim($key); @@ -149,8 +149,8 @@ class Mysql extends Builder /** * field分析 * @access protected - * @param Query $query 查询对象 - * @param mixed $fields + * @param Query $query 查询对象 + * @param mixed $fields 字段名 * @return string */ protected function parseField(Query $query, $fields) diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 8c7d2b88..4db6932d 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -48,12 +48,12 @@ class Pgsql extends Builder /** * 字段和表名处理 - * @access protected - * @param Query $query 查询对象 - * @param string $key + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey(Query $query, $key) + public function parseKey(Query $query, $key) { $key = trim($key); diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 56d64a23..141410c1 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -56,12 +56,12 @@ class Sqlite extends Builder /** * 字段和表名处理 - * @access protected - * @param Query $query 查询对象 - * @param string $key + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey(Query $query, $key) + public function parseKey(Query $query, $key) { $key = trim($key); if (strpos($key, '.')) { diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index f1cd0c98..b95e234b 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -72,12 +72,12 @@ class Sqlsrv extends Builder /** * 字段和表名处理 - * @access protected - * @param Query $query 查询对象 - * @param string $key + * @access public + * @param Query $query 查询对象 + * @param string $key 字段名 * @return string */ - protected function parseKey(Query $query, $key) + public function parseKey(Query $query, $key) { $key = trim($key); -- Gitee From 25a8e1d10048f44766104838220b48bf027a2c4b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 14:41:00 +0800 Subject: [PATCH 0873/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=81=9A=E5=90=88?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 54a4b5d8..f3fba4a4 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -571,9 +571,7 @@ class Query if (!empty($this->options['fetch_sql'])) { return $result; - } - - if ($force) { + } elseif ($force) { $result += 0; } @@ -599,10 +597,10 @@ class Query $query->fetchSql(true); } - return $query->aggregate('COUNT', '*', 0, true); + return $query->aggregate('COUNT', '*', true); } - return $this->aggregate('COUNT', $field, 0, true); + return $this->aggregate('COUNT', $field, true); } /** @@ -613,7 +611,7 @@ class Query */ public function sum($field) { - return $this->aggregate('SUM', $field, 0, true); + return $this->aggregate('SUM', $field, true); } /** @@ -625,7 +623,7 @@ class Query */ public function min($field, $force = true) { - return $this->aggregate('MIN', $field, 0, $force); + return $this->aggregate('MIN', $field, $force); } /** @@ -637,7 +635,7 @@ class Query */ public function max($field, $force = true) { - return $this->aggregate('MAX', $field, 0, $force); + return $this->aggregate('MAX', $field, $force); } /** @@ -648,7 +646,7 @@ class Query */ public function avg($field) { - return $this->aggregate('AVG', $field, 0, true); + return $this->aggregate('AVG', $field, true); } /** -- Gitee From 041e33e3355ba30d5ea30165cf25f21a7e6da9dc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 16:22:15 +0800 Subject: [PATCH 0874/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1URL=E7=94=9F?= =?UTF-8?q?=E6=88=90=E6=94=AF=E6=8C=81=E5=A4=9A=E7=BA=A7=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 9 +++++---- library/think/route/RuleGroup.php | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 04c9cd3e..6badc42b 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -485,12 +485,13 @@ class Route $ruleItem = new RuleItem($this, $this->group, $rule, $route, $method, $option, $pattern); if (isset($name)) { - // 当前分组名 - $group = $this->group->getName(); + // 上级完整分组名 + $group = $this->group->getFullName(); if ($group) { $rule = $group . '/' . $rule; } + // 设置路由标识 用于URL快速生成 $this->setRuleName($rule, $name, $option); } @@ -507,13 +508,13 @@ class Route /** * 设置路由标识 用于URL反解生成 - * @access protected + * @access public * @param string $rule 路由规则 * @param string $name 路由标识 * @param array $option 路由参数 * @return void */ - protected function setRuleName($rule, $name, $option = []) + public function setRuleName($rule, $name, $option = []) { $vars = $this->parseVar($rule); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index c8e464cc..3105df51 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -40,6 +40,9 @@ class RuleGroup extends Rule // 自动路由 protected $auto; + // 完整名称 + protected $fullName; + /** * 架构函数 * @access public @@ -58,6 +61,12 @@ class RuleGroup extends Rule $this->name = trim($name, '/'); $this->option = $option; $this->pattern = $pattern; + + if ($group && $group->getName() && $this->name) { + $this->fullName = $group->getName() . '/' . $this->name; + } else { + $this->fullName = $this->name; + } } /** @@ -233,6 +242,16 @@ class RuleGroup extends Rule return $this->option('prefix', $prefix); } + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName() + { + return $this->fullName; + } + /** * 获取分组的路由规则 * @access public -- Gitee From 88901d904caf430eab5bad468d0e42eeaaec67f0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 17:04:09 +0800 Subject: [PATCH 0875/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=A4=9A=E7=BA=A7=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 8 ++++++-- library/think/route/RuleGroup.php | 19 +++++++------------ library/think/route/RuleItem.php | 4 ++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index f3fba4a4..3fe1b9f6 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1061,9 +1061,11 @@ class Query if (is_string($field)) { $field = explode(',', $field); } + foreach ($field as $key => $val) { if (is_numeric($key)) { - $fields[] = $alias . '.' . $val; + $fields[] = $alias . '.' . $val; + $this->options['map'][$val] = $alias . '.' . $val; } else { if (preg_match('/[,=\.\'\"\(\s]/', $key)) { @@ -1071,7 +1073,9 @@ class Query } else { $name = $alias . '.' . $key; } - $fields[] = $name . ' AS ' . $val; + + $fields[] = $name . ' AS ' . $val; + $this->options['map'][$val] = $name; } } diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 3105df51..0435bae8 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -62,8 +62,8 @@ class RuleGroup extends Rule $this->option = $option; $this->pattern = $pattern; - if ($group && $group->getName() && $this->name) { - $this->fullName = $group->getName() . '/' . $this->name; + if ($group && $group->getFullName()) { + $this->fullName = $group->getFullName() . ($this->name ? '/' . $this->name : ''); } else { $this->fullName = $this->name; } @@ -102,13 +102,14 @@ class RuleGroup extends Rule return false; } - if ($this->name && !($this instanceof Domain)) { + if ($this->fullName) { // 分组URL匹配检查 - $pos = strpos(str_replace('<', ':', $this->name), ':'); + $pos = strpos(str_replace('<', ':', $this->fullName), ':'); + if (false !== $pos) { - $str = substr($this->name, 0, $pos); + $str = substr($this->fullName, 0, $pos); } else { - $str = $this->name; + $str = $this->fullName; } if (0 !== stripos(str_replace('|', '/', $url), $str)) { @@ -203,12 +204,6 @@ class RuleGroup extends Rule */ public function addRule($rule, $method = '*') { - $name = $rule->getName(); - - if ($this->name && $rule instanceof RuleGroup && !($this instanceof Domain)) { - $rule->name($this->name . '/' . $name); - } - if (strpos($method, '|')) { $rule->method($method); $method = '*'; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index e50318bc..71312b3f 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -104,8 +104,8 @@ class RuleItem extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { - if ($this->parent && $groupName = $this->parent->getName()) { - $this->name = $groupName . ($this->name ? '/' . $this->name : ''); + if ($this->parent && $prefix = $this->parent->getFullName()) { + $this->name = $prefix . ($this->name ? '/' . $this->name : ''); } if ($dispatch = $this->checkCrossDomain($request)) { -- Gitee From 1bdfca1e3cc6a3672323d6e7b98a74ae8149244b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 17:32:11 +0800 Subject: [PATCH 0876/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmiss=E5=92=8Cauto?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=86=85=E9=83=A8=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 4 ++-- library/think/route/RuleGroup.php | 32 +++++++++++++++++++++------- library/think/route/RuleItem.php | 35 +++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 6badc42b..ae7919e2 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -877,7 +877,7 @@ class Route */ public function miss($route, $method = '*', $option = []) { - return $this->rule('__miss__', $route, $method, $option); + return $this->rule('', $route, $method, $option)->isMiss(); } /** @@ -888,7 +888,7 @@ class Route */ public function auto($route) { - return $this->rule('__auto__', $route); + return $this->rule('', $route)->isAuto(); } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 0435bae8..7cb3ad0b 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -195,6 +195,30 @@ class RuleGroup extends Rule return $result; } + /** + * 设置自动路由 + * @access public + * @param RuleItem $rule 路由规则 + * @return $this + */ + public function setAutoRule($rule) + { + $this->auto = $rule; + return $this; + } + + /** + * 设置为MISS路由 + * @access public + * @param RuleItem $rule 路由规则 + * @return $this + */ + public function setMissRule($rule) + { + $this->miss = $rule; + return $this; + } + /** * 添加分组下的路由规则或者子分组 * @access public @@ -211,14 +235,6 @@ class RuleGroup extends Rule $this->rules[$method][] = $rule; - if ($rule instanceof RuleItem) { - if ($rule->isMiss()) { - $this->miss = $rule; - } elseif ($rule->isAuto()) { - $this->auto = $rule; - } - } - return $this; } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 71312b3f..f5e92ea0 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -15,11 +15,22 @@ use think\Route; class RuleItem extends Rule { - // 路由规则 + /** + * 路由规则 + * @var string + */ protected $name; - // 路由地址 + + /** + * 路由地址 + * @var string|\Closure + */ protected $route; - // 请求类型 + + /** + * 请求类型 + * @var string + */ protected $method; /** @@ -74,23 +85,25 @@ class RuleItem extends Rule } /** - * 是否为MISS路由 + * 设置为自动路由 * @access public - * @return bool + * @return $this */ - public function isMiss() + public function isAuto() { - return '__miss__' == $this->name; + $this->parent->setAutoRule($this); + return $this; } /** - * 是否为自动路由 + * 设置为MISS路由 * @access public - * @return bool + * @return $this */ - public function isAuto() + public function isMiss() { - return '__auto__' == $this->name; + $this->parent->setMissRule($this); + return $this; } /** -- Gitee From 33d0f92a591c608104b8c3aba25fbf9c590748ab Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 29 Jan 2018 17:42:44 +0800 Subject: [PATCH 0877/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 7cb3ad0b..bb81fb97 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -201,7 +201,7 @@ class RuleGroup extends Rule * @param RuleItem $rule 路由规则 * @return $this */ - public function setAutoRule($rule) + public function setAutoRule(RuleItem $rule) { $this->auto = $rule; return $this; @@ -213,7 +213,7 @@ class RuleGroup extends Rule * @param RuleItem $rule 路由规则 * @return $this */ - public function setMissRule($rule) + public function setMissRule(RuleItem $rule) { $this->miss = $rule; return $this; -- Gitee From 2dee4d2ed9367731ba23bb92e43ba77754efca73 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 30 Jan 2018 19:02:25 +0800 Subject: [PATCH 0878/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BUrl=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=9A=84url=5Fconvert=E5=8F=82=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E5=BD=B1=E5=93=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index 17b7b678..172f6594 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -225,21 +225,25 @@ class Url $url = substr($url, 1); } else { // 解析到 模块/控制器/操作 - $module = $request->module(); - $module = $module ? $module . '/' : ''; - - $controller = Loader::parseName($request->controller()); + $module = $request->module(); + $module = $module ? $module . '/' : ''; + $controller = $request->controller(); if ('' == $url) { - // 空字符串输出当前的 模块/控制器/操作 - $url = $module . $controller . '/' . $request->action(); + $action = $request->action(); } else { $path = explode('/', $url); - $action = $this->app['config']->get('url_convert') ? strtolower(array_pop($path)) : array_pop($path); - $controller = empty($path) ? $controller : ($this->app['config']->get('url_convert') ? Loader::parseName(array_pop($path)) : array_pop($path)); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); $module = empty($path) ? $module : array_pop($path) . '/'; - $url = $module . $controller . '/' . $action; } + + if ($this->app['config']->get('url_convert')) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + + $url = $module . $controller . '/' . $action; } return $url; -- Gitee From c983d21101184067280ac91005db7558b5dba06b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 30 Jan 2018 19:17:04 +0800 Subject: [PATCH 0879/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/facade/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/facade/Request.php b/library/think/facade/Request.php index e9ca3315..20b6429d 100644 --- a/library/think/facade/Request.php +++ b/library/think/facade/Request.php @@ -54,7 +54,7 @@ use think\Facade; * @method mixed env(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取环境变量 * @method mixed file(mixed $name = '') static 获取上传的文件信息 * @method mixed header(mixed $name = '', mixed $default = null) static 设置或者获取当前的Header - * @method mixed inpput(array $data,mixed $name = '', mixed $default = null, mixed $filter = '') static 获取变量 支持过滤和默认值 + * @method mixed input(array $data,mixed $name = '', mixed $default = null, mixed $filter = '') static 获取变量 支持过滤和默认值 * @method mixed filter(mixed $filter = null) static 设置或获取当前的过滤规则 * @method mixed has(string $name, string $type = 'param', bool $checkEmpty = false) static 是否存在某个请求参数 * @method mixed only(mixed $name, string $type = 'param') static 获取指定的参数 -- Gitee From 4f1aeb6048bade418963488864e9add51ce66672 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 31 Jan 2018 15:21:19 +0800 Subject: [PATCH 0880/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=A2=9E=E5=8A=A0g?= =?UTF-8?q?etOrFail=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index e09189b6..2a9f035b 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -772,10 +772,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param mixed $data 主键值或者查询条件(闭包) * @param mixed $with 关联预查询 * @param bool $cache 是否缓存 + * @param bool $failException 是否抛出异常 * @return static|null * @throws exception\DbException */ - public static function get($data, $with = [], $cache = false) + public static function get($data, $with = [], $cache = false, $failException = false) { if (is_null($data)) { return; @@ -788,7 +789,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query = static::parseQuery($data, $with, $cache); - return $query->find($data); + return $query->failException($failException)->find($data); + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public static function getOrFail($data, $with = [], $cache = false) + { + return self::get($data, $with, $cache, true); } /** -- Gitee From d15689f37959223269741fa6693f2c3012db8ae4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 31 Jan 2018 15:22:48 +0800 Subject: [PATCH 0881/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 2a9f035b..3178dd41 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -793,7 +793,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 查找单条记录 + * 查找单条记录 如果不存在直接抛出异常 * @access public * @param mixed $data 主键值或者查询条件(闭包) * @param mixed $with 关联预查询 -- Gitee From f2f25ed3a1e7fa01837a1623fec6bd5aef9a895c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 31 Jan 2018 15:49:34 +0800 Subject: [PATCH 0882/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=9A=84=E9=A2=84=E8=BD=BD=E5=85=A5=E5=85=B3=E8=81=94=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Query.php | 9 +++------ library/think/model/concern/RelationShip.php | 16 ++++++---------- library/think/model/relation/BelongsTo.php | 10 ++++------ library/think/model/relation/BelongsToMany.php | 14 +++++--------- library/think/model/relation/HasMany.php | 15 ++++++--------- library/think/model/relation/HasOne.php | 10 ++++------ library/think/model/relation/MorphMany.php | 15 ++++++--------- library/think/model/relation/MorphOne.php | 15 ++++++--------- library/think/model/relation/MorphTo.php | 13 +++++-------- library/think/model/relation/OneToOne.php | 15 ++++++--------- 11 files changed, 52 insertions(+), 82 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 3178dd41..3b673464 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -837,7 +837,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected static function parseQuery(&$data, $with, $cache) { - $result = self::with($with, true === $cache ? true : false)->cache($cache); + $result = self::with($with)->cache($cache); if (is_array($data) && key($data) !== 0) { $result = $result->where($data); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3fe1b9f6..99093acd 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2124,10 +2124,9 @@ class Query * 设置关联查询JOIN预查询 * @access public * @param string|array $with 关联方法名称 - * @param bool|array $cache 设置关联缓存 * @return $this */ - public function with($with, $cache = false) + public function with($with) { if (empty($with)) { return $this; @@ -2178,8 +2177,6 @@ class Query $this->options['with'] = $with; } - $this->options['relation_cache'] = $cache; - return $this; } @@ -2541,7 +2538,7 @@ class Query if (!empty($this->options['with'])) { // 预载入 - $result->eagerlyResultSet($resultSet, $this->options['with'], $this->options['relation_cache']); + $result->eagerlyResultSet($resultSet, $this->options['with']); } // 模型数据集转换 @@ -2659,7 +2656,7 @@ class Query // 预载入查询 if (!$resultSet && !empty($options['with'])) { - $result->eagerlyResult($result, $options['with'], $options['relation_cache']); + $result->eagerlyResult($result, $options['with']); } // 关联统计 diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index ad675a4b..51611958 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -210,10 +210,9 @@ trait RelationShip * @access public * @param array $resultSet 数据集 * @param string $relation 关联名 - * @param mixed $cache 关联缓存 * @return array */ - public function eagerlyResultSet(&$resultSet, $relation, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation) { $relations = is_string($relation) ? explode(',', $relation) : $relation; @@ -233,10 +232,9 @@ trait RelationShip list($relation, $subRelation) = explode('.', $relation, 2); } - $relationCache = isset($cache[$relation]) ? $cache[$relation] : $cache; - $relation = Loader::parseName($relation, 1, false); + $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $relationCache); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); } } @@ -245,10 +243,9 @@ trait RelationShip * @access public * @param Model $result 数据对象 * @param string $relation 关联名 - * @param mixed $cache 关联缓存 * @return Model */ - public function eagerlyResult(&$result, $relation, $cache = false) + public function eagerlyResult(&$result, $relation) { $relations = is_string($relation) ? explode(',', $relation) : $relation; @@ -268,10 +265,9 @@ trait RelationShip list($relation, $subRelation) = explode('.', $relation, 2); } - $relationCache = isset($cache[$relation]) ? $cache[$relation] : $cache; - $relation = Loader::parseName($relation, 1, false); + $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure, $relationCache); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); } } diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index f8073889..bcbab2ac 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -109,10 +109,9 @@ class BelongsTo extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -128,7 +127,7 @@ class BelongsTo extends OneToOne if (!empty($range)) { $data = $this->eagerlyWhere([ [$localKey, 'in', $range], - ], $localKey, $relation, $subRelation, $closure, $cache); + ], $localKey, $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -162,17 +161,16 @@ class BelongsTo extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure, $cache = false) + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; $data = $this->eagerlyWhere([ [$localKey, '=', $result->$foreignKey], - ], $localKey, $relation, $subRelation, $closure, $cache); + ], $localKey, $relation, $subRelation, $closure); // 关联模型 if (!isset($data[$result->$foreignKey])) { diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index c9fd159e..203c198b 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -263,10 +263,9 @@ class BelongsToMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -284,7 +283,7 @@ class BelongsToMany extends Relation // 查询关联数据 $data = $this->eagerlyManyToMany([ ['pivot.' . $localKey, 'in', $range], - ], $relation, $subRelation, $cache); + ], $relation, $subRelation); // 关联属性名 $attr = Loader::parseName($relation); @@ -307,10 +306,9 @@ class BelongsToMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); @@ -319,7 +317,7 @@ class BelongsToMany extends Relation // 查询管理数据 $data = $this->eagerlyManyToMany([ ['pivot.' . $this->localKey, '=', $pk], - ], $relation, $subRelation, $cache); + ], $relation, $subRelation); // 关联数据封装 if (!isset($data[$pk])) { @@ -377,15 +375,13 @@ class BelongsToMany extends Relation * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 - * @param mixed $cache 缓存 * @return array */ - protected function eagerlyManyToMany($where, $relation, $subRelation = '', $cache = false) + protected function eagerlyManyToMany($where, $relation, $subRelation = '') { // 预载入关联查询 支持嵌套预载入 $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) ->with($subRelation) - ->cache($cache) ->select(); // 组装模型数据 diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 89622a58..3a882298 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -69,10 +69,9 @@ class HasMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $range = []; @@ -88,7 +87,7 @@ class HasMany extends Relation $where = [ [$this->foreignKey, 'in', $range], ]; - $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure, $cache); + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -116,10 +115,9 @@ class HasMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; @@ -128,7 +126,7 @@ class HasMany extends Relation $where = [ [$this->foreignKey, '=', $pk], ]; - $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure, $cache); + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); // 关联数据封装 if (!isset($data[$pk])) { @@ -195,10 +193,9 @@ class HasMany extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool $closure - * @param mixed $cache 缓存 * @return array */ - protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = false, $cache = false) + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = false) { $foreignKey = $this->foreignKey; @@ -209,7 +206,7 @@ class HasMany extends Relation $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->cache($cache)->select(); + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 0f01fd21..915dea47 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -118,10 +118,9 @@ class HasOne extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -137,7 +136,7 @@ class HasOne extends OneToOne if (!empty($range)) { $data = $this->eagerlyWhere([ [$foreignKey, 'in', $range], - ], $foreignKey, $relation, $subRelation, $closure, $cache); + ], $foreignKey, $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -171,16 +170,15 @@ class HasOne extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure, $cache = false) + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; $data = $this->eagerlyWhere([ [$foreignKey, '=', $result->$localKey], - ], $foreignKey, $relation, $subRelation, $closure, $cache); + ], $foreignKey, $relation, $subRelation, $closure); // 关联模型 if (!isset($data[$result->$localKey])) { diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index a0361680..b3a4f39f 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -102,10 +102,9 @@ class MorphMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $morphType = $this->morphType; $morphKey = $this->morphKey; @@ -125,7 +124,7 @@ class MorphMany extends Relation [$morphKey, 'in', $range], [$morphType, '=', $type], ]; - $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure, $cache); + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -153,10 +152,9 @@ class MorphMany extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); @@ -166,7 +164,7 @@ class MorphMany extends Relation [$this->morphKey, '=', $key], [$this->morphType, '=', $this->type], ]; - $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure, $cache); + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); if (!isset($data[$key])) { $data[$key] = []; @@ -241,10 +239,9 @@ class MorphMany extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool|\Closure $closure 闭包 - * @param mixed $cache 缓存 * @return array */ - protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false, $cache = false) + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 $this->query->removeOptions('where'); @@ -253,7 +250,7 @@ class MorphMany extends Relation $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->cache($cache)->select(); + $list = $this->query->where($where)->with($subRelation)->select(); $morphKey = $this->morphKey; // 组装模型数据 diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index 8238d874..09444008 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -101,10 +101,9 @@ class MorphOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $morphType = $this->morphType; $morphKey = $this->morphKey; @@ -123,7 +122,7 @@ class MorphOne extends Relation $data = $this->eagerlyMorphToOne([ [$morphKey, 'in', $range], [$morphType, '=', $type], - ], $relation, $subRelation, $closure, $cache); + ], $relation, $subRelation, $closure); // 关联属性名 $attr = Loader::parseName($relation); @@ -150,10 +149,9 @@ class MorphOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); @@ -162,7 +160,7 @@ class MorphOne extends Relation $data = $this->eagerlyMorphToOne([ [$this->morphKey, '=', $pk], [$this->morphType, '=', $this->type], - ], $relation, $subRelation, $closure, $cache); + ], $relation, $subRelation, $closure); if (isset($data[$pk])) { $relationModel = $data[$pk]; @@ -183,17 +181,16 @@ class MorphOne extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool|\Closure $closure 闭包 - * @param mixed $cache 缓存 * @return array */ - protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false, $cache = false) + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { $closure($this->query); } - $list = $this->query->where($where)->with($subRelation)->cache($cache)->find(); + $list = $this->query->where($where)->with($subRelation)->find(); $morphKey = $this->morphKey; // 组装模型数据 diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index f89e997b..208c6ecf 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -162,11 +162,10 @@ class MorphTo extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void * @throws Exception */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $morphKey = $this->morphKey; $morphType = $this->morphType; @@ -220,17 +219,16 @@ class MorphTo extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $morphKey = $this->morphKey; $morphType = $this->morphType; // 多态类型映射 $model = $this->parseModel($result->{$this->morphType}); - $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); } /** @@ -252,14 +250,13 @@ class MorphTo extends Relation * @param string $relation 关联名 * @param Model $result * @param string $subRelation 子关联 - * @param mixed $cache 缓存 * @return void */ - protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '', $cache = false) + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') { // 预载入关联查询 支持嵌套预载入 $pk = $this->parent->{$this->morphKey}; - $data = (new $model)->with($subRelation)->cache($cache)->find($pk); + $data = (new $model)->with($subRelation)->find($pk); if ($data) { $data->setParent(clone $result); diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 9bfd57af..195d0bdd 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -133,14 +133,13 @@ abstract class OneToOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { if (1 == $this->eagerlyType) { // IN查询 - $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache); + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); } else { // 模型关联组装 foreach ($resultSet as $result) { @@ -156,14 +155,13 @@ abstract class OneToOne extends Relation * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param mixed $cache 缓存 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $cache = false) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { if (1 == $this->eagerlyType) { // IN查询 - $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache); + $this->eagerlyOne($result, $relation, $subRelation, $closure); } else { // 模型关联组装 $this->match($this->model, $relation, $result); @@ -316,10 +314,9 @@ abstract class OneToOne extends Relation * @param string $relation 关联名 * @param string $subRelation 子关联 * @param bool|\Closure $closure - * @param mixed $cache 缓存 * @return array */ - protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false, $cache = false) + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { @@ -330,7 +327,7 @@ abstract class OneToOne extends Relation } } - $list = $this->query->where($where)->with($subRelation)->cache($cache)->select(); + $list = $this->query->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; -- Gitee From 4e1845dbe5020e7a2278e7f6347b3e4580330c7a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 31 Jan 2018 16:20:03 +0800 Subject: [PATCH 0883/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index ceff17d7..348d4889 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.4'; + const VERSION = '5.1.5'; /** * 当前模块路径 -- Gitee From 78f0506eed5d755f8dc53ce12f59cc3dad2592b8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Feb 2018 14:29:08 +0800 Subject: [PATCH 0884/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 348d4889..8892585c 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -438,7 +438,7 @@ class App implements \ArrayAccess $files = scandir($this->routePath); foreach ($files as $file) { if (strpos($file, '.php')) { - $filename = $this->routePath . DIRECTORY_SEPARATOR . $file; + $filename = $this->routePath . $file; // 导入路由配置 $rules = include $filename; if (is_array($rules)) { -- Gitee From 1d6e9bd009726ee9f220fc1b8f5db46cdf52fc09 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Feb 2018 14:37:17 +0800 Subject: [PATCH 0885/1384] =?UTF-8?q?readme=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 57eeb304..5da1eef6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ ThinkPHP 5.1 =============== +[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) +[![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) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: + 采用容器统一管理对象 -- Gitee From a787071816127177d1e3932614e29bdef6e0c8fb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Feb 2018 16:34:02 +0800 Subject: [PATCH 0886/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Url=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index 172f6594..e7cf75d3 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -103,8 +103,6 @@ class Url if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars)) { // 匹配路由命名标识 $url = $match[0]; - // 替换可选分隔符 - $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); if (!empty($match[1])) { $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(); @@ -322,7 +320,7 @@ class Url foreach ($rule as $item) { list($url, $pattern, $domain, $suffix) = $item; if (empty($pattern)) { - return [$url, $domain, $suffix]; + return [rtrim($url, '$'), $domain, $suffix]; } $type = $this->app['config']->get('url_common_param'); -- Gitee From c7d81f7cd89a6406665bceb7a4bb7b0ff8537772 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Feb 2018 18:31:18 +0800 Subject: [PATCH 0887/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 2 ++ library/think/route/RuleGroup.php | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 3d3c3888..7072623c 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -41,6 +41,8 @@ class Resource extends RuleGroup $this->route = $route; $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + $this->setFullName(); + // 资源路由默认为完整匹配 $option['complete_match'] = true; diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index bb81fb97..3f831741 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -62,8 +62,18 @@ class RuleGroup extends Rule $this->option = $option; $this->pattern = $pattern; - if ($group && $group->getFullName()) { - $this->fullName = $group->getFullName() . ($this->name ? '/' . $this->name : ''); + $this->setFullName(); + } + + /** + * 设置分组的路由规则 + * @access public + * @return $this + */ + protected function setFullName() + { + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); } else { $this->fullName = $this->name; } -- Gitee From d51b6114d9d63ec282e05f7a253ce1e9f406e1ba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Feb 2018 11:04:21 +0800 Subject: [PATCH 0888/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BparseKey=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 5352755a..cea8d4ae 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -109,6 +109,9 @@ class Mysql extends Builder */ public function parseKey(Query $query, $key) { + if (is_int($key)) { + return $key; + } $key = trim($key); if (strpos($key, '->') && false === strpos($key, '(')) { -- Gitee From f81c4282cdf401be44fbe1a28b3e3df425e3017f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Feb 2018 13:39:38 +0800 Subject: [PATCH 0889/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E7=BB=84?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index f5e92ea0..e26637f6 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -118,7 +118,7 @@ class RuleItem extends Rule public function check($request, $url, $depr = '/', $completeMatch = false) { if ($this->parent && $prefix = $this->parent->getFullName()) { - $this->name = $prefix . ($this->name ? '/' . $this->name : ''); + $this->name = $prefix . ($this->name ? '/' . ltrim($this->name, '/') : ''); } if ($dispatch = $this->checkCrossDomain($request)) { -- Gitee From b519893439beabb3a74aab884623064ac821aad5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Feb 2018 15:14:29 +0800 Subject: [PATCH 0890/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81ext=E6=96=B9=E6=B3=95=E7=9A=84URL=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 28 ++++++------- library/think/route/Rule.php | 8 ++-- library/think/route/RuleItem.php | 69 +++++++++++++++++++++++--------- 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index ae7919e2..563057e4 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -477,24 +477,14 @@ class Route $this->ruleName = null; } elseif (is_string($route)) { $name = $route; + } else { + $name = null; } $method = strtolower($method); // 创建路由规则实例 - $ruleItem = new RuleItem($this, $this->group, $rule, $route, $method, $option, $pattern); - - if (isset($name)) { - // 上级完整分组名 - $group = $this->group->getFullName(); - - if ($group) { - $rule = $group . '/' . $rule; - } - - // 设置路由标识 用于URL快速生成 - $this->setRuleName($rule, $name, $option); - } + $ruleItem = new RuleItem($this, $this->group, $name, $rule, $route, $method, $option, $pattern); // 添加到当前分组 $this->group->addRule($ruleItem, $method); @@ -512,11 +502,13 @@ class Route * @param string $rule 路由规则 * @param string $name 路由标识 * @param array $option 路由参数 + * @param bool $first 是否插入开头 * @return void */ - public function setRuleName($rule, $name, $option = []) + public function setRuleName($rule, $name, $option = [], $first = false) { $vars = $this->parseVar($rule); + $name = strtolower($name); if (isset($option['ext'])) { $suffix = $option['ext']; @@ -526,7 +518,13 @@ class Route $suffix = null; } - $this->name[strtolower($name)][] = [$rule, $vars, $this->domain, $suffix]; + $item = [$rule, $vars, $this->domain, $suffix]; + + if ($first) { + array_unshift($this->name[$name], $item); + } else { + $this->name[$name][] = $item; + } } /** diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 7fe8d3e8..8965fced 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -75,14 +75,14 @@ abstract class Rule } /** - * 设置Name + * 设置标识 * @access public - * @param string|array $name 变量名 + * @param string $name 标识名 * @return $this */ public function name($name) { - $this->name = '/' != $name ? ltrim($name, '/') : '/'; + $this->name = $name; return $this; } @@ -484,6 +484,8 @@ abstract class Rule } $this->option = array_merge($parentOption, $this->option); + + return $this->option; } /** diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index e26637f6..a45238ac 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -19,7 +19,7 @@ class RuleItem extends Rule * 路由规则 * @var string */ - protected $name; + protected $rule; /** * 路由地址 @@ -38,22 +38,24 @@ class RuleItem extends Rule * @access public * @param Route $router 路由实例 * @param RuleGroup $group 路由所属分组对象 - * @param string|array $name 路由规则 + * @param string $name 路由标识 + * @param string|array $rule 路由规则 * @param string $method 请求类型 * @param string|\Closure $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 */ - public function __construct(Route $router, RuleGroup $group, $name, $route, $method = '*', $option = [], $pattern = []) + public function __construct(Route $router, RuleGroup $group, $name, $rule, $route, $method = '*', $option = [], $pattern = []) { $this->router = $router; $this->parent = $group; + $this->name = $name; $this->route = $route; $this->method = $method; $this->option = $option; $this->pattern = $pattern; - $this->setRule($name); + $this->setRule($rule); } /** @@ -71,7 +73,30 @@ class RuleItem extends Rule $this->option['complete_match'] = true; } - $this->name($rule); + $rule = '/' != $rule ? ltrim($rule, '/') : '/'; + + if ($this->parent && $prefix = $this->parent->getFullName()) { + $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + $this->rule = $rule; + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 检查后缀 + * @access public + * @param string $ext + * @return $this + */ + public function ext($ext = '') + { + $this->option('ext', $ext); + $this->setRuleName(true); + + return $this; } /** @@ -106,6 +131,19 @@ class RuleItem extends Rule return $this; } + /** + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void + */ + protected function setRuleName($first = false) + { + if ($this->name) { + $this->router->setRuleName($this->rule, $this->name, $this->option, $first); + } + } + /** * 检测路由 * @access public @@ -117,10 +155,6 @@ class RuleItem extends Rule */ public function check($request, $url, $depr = '/', $completeMatch = false) { - if ($this->parent && $prefix = $this->parent->getFullName()) { - $this->name = $prefix . ($this->name ? '/' . ltrim($this->name, '/') : ''); - } - if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 return $dispatch; @@ -132,16 +166,15 @@ class RuleItem extends Rule } // 合并分组参数 - $this->mergeGroupOptions(); - $option = $this->option; + $option = $this->mergeGroupOptions(); if (!empty($option['append'])) { $request->route($option['append']); } // 是否区分 / 地址访问 - if (!empty($option['remove_slash']) && '/' != $this->name) { - $this->name = rtrim($this->name, '/'); + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); $url = rtrim($url, '|'); } @@ -181,7 +214,7 @@ class RuleItem extends Rule } $len1 = substr_count($url, '|'); - $len2 = substr_count($this->name, '/'); + $len2 = substr_count($this->rule, '/'); // 多余参数是否合并 $merge = !empty($option['merge_extra_vars']) ? true : false; @@ -195,9 +228,9 @@ class RuleItem extends Rule $completeMatch = $option['complete_match']; } - if ($len1 >= $len2 || strpos($this->name, '[')) { + if ($len1 >= $len2 || strpos($this->rule, '[')) { // 完整匹配 - if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->name, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->name, '[')))) { + if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->rule, '[')))) { return false; } @@ -205,7 +238,7 @@ class RuleItem extends Rule if (false !== $match = $this->match($url, $pattern)) { // 匹配到路由规则 - return $this->parseRule($request, $this->name, $this->route, $url, $option, $match); + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); } } @@ -221,7 +254,7 @@ class RuleItem extends Rule */ private function match($url, $pattern) { - $m2 = explode('/', $this->name); + $m2 = explode('/', $this->rule); $m1 = explode('|', $url); $var = []; -- Gitee From b726b8736704b158474a3f83d6ed0c22ddb0519d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Feb 2018 16:03:51 +0800 Subject: [PATCH 0891/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=AF=B9=E4=B8=8D=E5=90=8C=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=9B=B8=E5=90=8C=E8=A1=A8=E5=90=8D=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 17 +++++++++-------- library/think/db/Query.php | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index c7d3cd45..4452ed51 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -783,7 +783,7 @@ abstract class Connection $pk = $query->getPk($options); if (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($query, $options['where']['AND'][$pk], $options); } $data = $options['data']; @@ -796,7 +796,7 @@ abstract class Connection if (is_string($cache['key'])) { $key = $cache['key']; } elseif (!isset($key)) { - $key = $this->getCacheKey($data, $options, $query->getBind(false)); + $key = $this->getCacheKey($query, $data, $options, $query->getBind(false)); } $result = Container::get('cache')->get($key); @@ -1094,7 +1094,7 @@ abstract class Connection if (is_string($pk) && isset($data[$pk])) { $where[$pk] = [$pk, '=', $data[$pk]]; if (!isset($key)) { - $key = $this->getCacheKey($data[$pk], $options); + $key = $this->getCacheKey($query, $data[$pk], $options); } unset($data[$pk]); } elseif (is_array($pk)) { @@ -1118,7 +1118,7 @@ abstract class Connection $query->setOption('where', ['AND' => $where]); } } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($query, $options['where']['AND'][$pk], $options); } // 更新数据 @@ -1179,9 +1179,9 @@ abstract class Connection if (isset($options['cache']) && is_string($options['cache']['key'])) { $key = $options['cache']['key']; } elseif (!is_null($data) && true !== $data && !is_array($data)) { - $key = $this->getCacheKey($data, $options); + $key = $this->getCacheKey($query, $data, $options); } elseif (is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($query, $options['where']['AND'][$pk], $options); } if (true !== $data && empty($options['where'])) { @@ -2049,12 +2049,13 @@ abstract class Connection /** * 生成缓存标识 * @access protected + * @param Query $query 查询对象 * @param mixed $value 缓存数据 * @param array $options 缓存参数 * @param array $bind 绑定参数 * @return string */ - protected function getCacheKey($value, $options, $bind = []) + protected function getCacheKey(Query $query, $value, $options, $bind = []) { if (is_scalar($value)) { $data = $value; @@ -2063,7 +2064,7 @@ abstract class Connection } if (isset($data)) { - return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + return 'think:' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $data; } else { try { return md5(serialize($options) . serialize($bind)); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 99093acd..e3140f1b 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1622,6 +1622,7 @@ class Query } } } + $this->options['table'] = $table; return $this; -- Gitee From cdaae33941617d8d4bfdb1281dd35185c5ac46d8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Feb 2018 18:07:02 +0800 Subject: [PATCH 0892/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomposer=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8A=A0=E8=BD=BD=E7=9A=84=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index 76fbde93..ac129154 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -58,29 +58,42 @@ class Loader // 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - // 注册命名空间定义 - self::addNamespace([ - 'think' => __DIR__ . '/', - 'traits' => __DIR__ . '/../traits/', - ]); - $path = dirname($_SERVER['SCRIPT_FILENAME']); + if (is_file('./think')) { $rootPath = realpath($path) . '/'; } else { $rootPath = realpath($path . '/../') . '/'; } - // 加载类库映射文件 - if (is_file($rootPath . 'runtime/classmap.php')) { - self::addClassMap(__include_file($rootPath . 'runtime/classmap.php')); - } - self::$composerPath = $rootPath . 'vendor/composer/'; // Composer自动加载支持 if (is_dir(self::$composerPath)) { - self::registerComposerLoader(self::$composerPath); + if (is_file(self::$composerPath . 'autoload_static.php')) { + require self::$composerPath . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + self::$prefixLengthsPsr4 = $composerClass::$prefixLengthsPsr4; + self::$prefixDirsPsr4 = $composerClass::$prefixDirsPsr4; + self::$prefixesPsr0 = $composerClass::$prefixesPsr0; + self::$map = $composerClass::$classMap; + } else { + self::registerComposerLoader(self::$composerPath); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => __DIR__ . '/', + 'traits' => __DIR__ . '/../traits/', + ]); + + // 加载类库映射文件 + if (is_file($rootPath . 'runtime/classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime/classmap.php')); } // 自动加载extend目录 -- Gitee From 2177ee32182ea753053411e413d1874d63ca707e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Feb 2018 18:52:22 +0800 Subject: [PATCH 0893/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomposer=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index ac129154..0a028419 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -77,9 +77,11 @@ class Loader $composerClass = array_pop($declaredClass); self::$prefixLengthsPsr4 = $composerClass::$prefixLengthsPsr4; - self::$prefixDirsPsr4 = $composerClass::$prefixDirsPsr4; - self::$prefixesPsr0 = $composerClass::$prefixesPsr0; - self::$map = $composerClass::$classMap; + + self::$prefixDirsPsr4 = property_exists($composerClass, 'prefixDirsPsr4') ? $composerClass::$prefixDirsPsr4 : []; + + self::$prefixesPsr0 = property_exists($composerClass, 'prefixesPsr0') ? $composerClass::$prefixesPsr0 : []; + self::$map = property_exists($composerClass, 'classMap') ? $composerClass::$classMap : []; } else { self::registerComposerLoader(self::$composerPath); } -- Gitee From 14d70568c643f39fd4e1262c809937922825e4c3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 3 Feb 2018 09:36:56 +0800 Subject: [PATCH 0894/1384] =?UTF-8?q?readme=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5da1eef6..c06bd0d5 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ ThinkPHP 5.1 =============== -[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) [![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) @@ -30,12 +28,12 @@ ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特 + 内置控制器扩展类 + 模型自动验证 -> ThinkPHP5的运行环境要求PHP5.6以上。 +> ThinkPHP5.1的运行环境要求PHP5.6+。 ## 在线手册 -+ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1) ++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content) + [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) ## 命名规范 -- Gitee From 3534285466f16c5a1c59b9f618eca19e21e0fa89 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 3 Feb 2018 09:39:06 +0800 Subject: [PATCH 0895/1384] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c06bd0d5..96018e56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ThinkPHP 5.1 +ThinkPHP 5.1 —— 12载初心,你值得信赖的PHP框架 =============== [![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) -- Gitee From ac922948543716175ff0ef1f1f5be9530ef05986 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 3 Feb 2018 10:08:58 +0800 Subject: [PATCH 0896/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 96018e56..52c34a4b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ +![](http://www.thinkphp.cn/Uploads/editor/2016-06-23/576b4732a6e04.png) + ThinkPHP 5.1 —— 12载初心,你值得信赖的PHP框架 =============== [![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) [![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) [![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) -[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) [![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: -- Gitee From c0ba248311694de486c23c23068a0c676fd448be Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 6 Feb 2018 15:30:51 +0800 Subject: [PATCH 0897/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 2 +- library/think/route/RuleGroup.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index dd8084d1..7f6ff030 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -32,7 +32,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 规范错误', diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 3f831741..f2535cb7 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -68,11 +68,11 @@ class RuleGroup extends Rule /** * 设置分组的路由规则 * @access public - * @return $this + * @return void */ protected function setFullName() { - if ($this->parent && $this->parent->getFullName()) { + if ($this->parent) { $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); } else { $this->fullName = $this->name; -- Gitee From a47258df2f57080ea7ad912cbe91736da5a7f791 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 6 Feb 2018 15:55:31 +0800 Subject: [PATCH 0898/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E7=9A=84=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index a45238ac..3e62e946 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -318,7 +318,9 @@ class RuleItem extends Rule } } - $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + if (isset($m1[$key])) { + $var[$name] = $m1[$key]; + } } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { return false; } -- Gitee From 0346b0ef490d223604ed8e198dc6f25f3151ce69 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Feb 2018 12:44:33 +0800 Subject: [PATCH 0899/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=80=E5=AF=B9?= =?UTF-8?q?=E4=B8=80=E6=9F=A5=E8=AF=A2=20=E6=94=B9=E8=BF=9Bmysql=E7=9A=84j?= =?UTF-8?q?son=E5=AD=97=E6=AE=B5=E6=9F=A5=E8=AF=A2=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 4 ++-- library/think/model/relation/BelongsTo.php | 17 +++++++++++++++++ library/think/model/relation/HasOne.php | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index cea8d4ae..d13ac016 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -116,9 +116,9 @@ class Mysql extends Builder if (strpos($key, '->') && false === strpos($key, '(')) { // JSON字段支持 - list($field, $name) = explode('->', $key); + list($field, $name) = explode('->', $key, 2); - $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . $name . '\')'; + $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . str_replace('->', '.', $name) . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index bcbab2ac..954553d3 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -221,4 +221,21 @@ class BelongsTo extends OneToOne return $this->parent->setRelation($this->relation, null); } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } } diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 915dea47..62e1e7e8 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -197,4 +197,20 @@ class HasOne extends OneToOne } } + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } } -- Gitee From b8b3b06ecdeb0262b218dc886cb9c2014ae4404c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 8 Feb 2018 12:49:06 +0800 Subject: [PATCH 0900/1384] =?UTF-8?q?Query=E7=B1=BB=E5=A2=9E=E5=8A=A0optio?= =?UTF-8?q?n=E6=96=B9=E6=B3=95=20=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 13 +++++++++++++ library/think/route/RuleGroup.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index e3140f1b..70bad036 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2081,6 +2081,19 @@ class Query return isset($this->bind[$key]); } + /** + * 查询参数赋值 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value) + { + $this->options[$name] = $value; + return $this; + } + /** * 查询参数赋值 * @access protected diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index f2535cb7..40c4f3dd 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -72,7 +72,7 @@ class RuleGroup extends Rule */ protected function setFullName() { - if ($this->parent) { + if ($this->parent && $this->parent->getFullName()) { $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); } else { $this->fullName = $this->name; -- Gitee From 4d491a8a5acaaa89ff7e0abe380bac27ee263d99 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 11 Feb 2018 00:32:52 +0800 Subject: [PATCH 0901/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BConnection=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 481 ++++++++++++++++---------------- 1 file changed, 246 insertions(+), 235 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 4452ed51..524875d9 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -193,9 +193,9 @@ abstract class Connection { if (!empty($this->builderClassName)) { return $this->builderClassName; - } else { - return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); } + + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); } /** @@ -352,6 +352,7 @@ abstract class Connection if (!isset(self::$info[$schema])) { // 读取缓存 $cacheFile = Container::get('app')->getRuntimePath() . 'schema/' . $schema . '.php'; + if (!$this->config['debug'] && is_file($cacheFile)) { $info = include $cacheFile; } else { @@ -365,6 +366,7 @@ abstract class Connection // 记录字段类型 $type[$key] = $val['type']; $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { $pk[] = $key; } @@ -472,49 +474,51 @@ abstract class Connection */ public function connect(array $config = [], $linkNum = 0, $autoConnection = false) { - if (!isset($this->links[$linkNum])) { - if (!$config) { - $config = $this->config; - } else { - $config = array_merge($this->config, $config); - } + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } - // 连接参数 - if (isset($config['params']) && is_array($config['params'])) { - $params = $config['params'] + $this->params; - } else { - $params = $this->params; - } + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } - // 记录当前字段属性大小写设置 - $this->attrCase = $params[PDO::ATTR_CASE]; + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } - try { - if (empty($config['dsn'])) { - $config['dsn'] = $this->parseDsn($config); - } + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; - if ($config['debug']) { - $startTime = microtime(true); - } + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } - $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + if ($config['debug']) { + $startTime = microtime(true); + } - if ($config['debug']) { - // 记录数据库连接信息 - $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); - } - } catch (\PDOException $e) { - if ($autoConnection) { - $this->log($e->getMessage(), 'error'); - return $this->connect($autoConnection, $linkNum); - } else { - throw $e; - } + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + + if ($config['debug']) { + // 记录数据库连接信息 + $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); } - } - return $this->links[$linkNum]; + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } } /** @@ -535,9 +539,9 @@ abstract class Connection { if (!$this->linkID) { return false; - } else { - return $this->linkID; } + + return $this->linkID; } /** @@ -783,11 +787,10 @@ abstract class Connection $pk = $query->getPk($options); if (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($query, $options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($query, $options['where']['AND'][$pk]); } - $data = $options['data']; - $result = false; + $data = $options['data']; if (empty($options['fetch_sql']) && !empty($options['cache'])) { // 判断查询缓存 @@ -796,55 +799,57 @@ abstract class Connection if (is_string($cache['key'])) { $key = $cache['key']; } elseif (!isset($key)) { - $key = $this->getCacheKey($query, $data, $options, $query->getBind(false)); + $key = $this->getCacheKey($query, $data); } $result = Container::get('cache')->get($key); + + if (false !== $result) { + return $result; + } } - if (false === $result) { - if (is_string($pk)) { - if (!is_array($data)) { - if (isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - } else { - $item[$pk] = $data; - } - $data = $item; - } + if (is_string($pk) && !is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; } - $query->setOption('data', $data); - $query->setOption('limit', 1); + $data = $item; + } - // 生成查询SQL - $sql = $this->builder->select($query); + $query->setOption('data', $data); + $query->setOption('limit', 1); - $bind = $query->getBind(); + // 生成查询SQL + $sql = $this->builder->select($query); - if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 - return $this->getRealSql($sql, $bind); - } + $bind = $query->getBind(); - // 事件回调 - if ($result = $query->trigger('before_find')) { - } else { - // 执行查询 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } + // 事件回调 + $result = $query->trigger('before_find'); - $result = isset($resultSet[0]) ? $resultSet[0] : null; - } + if (!$result) { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - if (isset($cache) && $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; } + + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); } return $result; @@ -885,44 +890,43 @@ abstract class Connection public function select(Query $query) { // 分析查询表达式 - $options = $query->getOptions(); - $resultSet = false; + $options = $query->getOptions(); if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options) . serialize($query->getBind(false))); - $resultSet = Container::get('cache')->get($key); + $resultSet = $this->getCacheData($query, $options['cache'], null, $key); + + if (false !== $resultSet) { + return $resultSet; + } } - if (false === $resultSet) { - // 生成查询SQL - $sql = $this->builder->select($query); + // 生成查询SQL + $sql = $this->builder->select($query); - $bind = $query->getBind(); + $bind = $query->getBind(); - if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 - return $this->getRealSql($sql, $bind); - } + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } - if ($resultSet = $query->trigger('before_select')) { - } else { - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + $resultSet = $query->trigger('before_select'); - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - } + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - if (isset($cache) && false !== $resultSet) { - // 缓存数据集 - $this->cacheData($key, $resultSet, $cache); + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; } } + if (isset($cache) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $cache); + } + return $resultSet; } @@ -1008,6 +1012,7 @@ abstract class Connection foreach ($array as $item) { $sql = $this->builder->insertAll($query, $item, $replace); $bind = $query->getBind(); + if (!empty($options['fetch_sql'])) { $fetchSql[] = $this->getRealSql($sql, $bind); } else { @@ -1032,12 +1037,10 @@ abstract class Connection $bind = $query->getBind(); if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 return $this->getRealSql($sql, $bind); - } else { - // 执行操作 - return $this->execute($sql, $bind); } + + return $this->execute($sql, $bind); } /** @@ -1054,7 +1057,6 @@ abstract class Connection // 分析查询表达式 $options = $query->getOptions(); - // 生成SQL语句 $table = $this->parseSqlTable($table); $sql = $this->builder->selectInsert($query, $fields, $table); @@ -1062,12 +1064,10 @@ abstract class Connection $bind = $query->getBind(); if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 return $this->getRealSql($sql, $bind); - } else { - // 执行操作 - return $this->execute($sql, $bind); } + + return $this->execute($sql, $bind); } /** @@ -1094,7 +1094,7 @@ abstract class Connection if (is_string($pk) && isset($data[$pk])) { $where[$pk] = [$pk, '=', $data[$pk]]; if (!isset($key)) { - $key = $this->getCacheKey($query, $data[$pk], $options); + $key = $this->getCacheKey($query, $data[$pk]); } unset($data[$pk]); } elseif (is_array($pk)) { @@ -1118,7 +1118,7 @@ abstract class Connection $query->setOption('where', ['AND' => $where]); } } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($query, $options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($query, $options['where']['AND'][$pk]); } // 更新数据 @@ -1131,34 +1131,34 @@ abstract class Connection if (!empty($options['fetch_sql'])) { // 获取实际执行的SQL语句 return $this->getRealSql($sql, $bind); - } else { - // 检测缓存 - $cache = Container::get('cache'); - - if (isset($key) && $cache->get($key)) { - // 删除缓存 - $cache->rm($key); - } elseif (!empty($options['cache']['tag'])) { - $cache->clear($options['cache']['tag']); - } + } - // 执行操作 - $result = '' == $sql ? 0 : $this->execute($sql, $bind); + // 检测缓存 + $cache = Container::get('cache'); - if ($result) { - if (is_string($pk) && isset($where[$pk])) { - $data[$pk] = $where[$pk]; - } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $data[$pk] = $val; - } + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind); - $query->setOption('data', $data); - $query->trigger('after_update'); + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; } - return $result; + $query->setOption('data', $data); + $query->trigger('after_update'); } + + return $result; } /** @@ -1179,9 +1179,9 @@ abstract class Connection if (isset($options['cache']) && is_string($options['cache']['key'])) { $key = $options['cache']['key']; } elseif (!is_null($data) && true !== $data && !is_array($data)) { - $key = $this->getCacheKey($query, $data, $options); + $key = $this->getCacheKey($query, $data); } elseif (is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($query, $options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($query, $options['where']['AND'][$pk]); } if (true !== $data && empty($options['where'])) { @@ -1239,50 +1239,44 @@ abstract class Connection { $options = $query->getOptions(); - $result = false; if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false))); - $result = Container::get('cache')->get($key); - } + $result = $this->getCacheData($query, $options['cache'], $field, $key); - if (false === $result) { - if (isset($options['field'])) { - $query->removeOption('field'); + if (false !== $result) { + return $result; } + } - if (is_string($field)) { - $field = array_map('trim', explode(',', $field)); - } + if (isset($options['field'])) { + $query->removeOption('field'); + } - $query->setOption('field', $field); - $query->setOption('limit', 1); - // 生成查询SQL - $sql = $this->builder->select($query); + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } - $bind = $query->getBind(); + $query->setOption('field', $field); + $query->setOption('limit', 1); - if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 - return $this->getRealSql($sql, $bind); - } + // 生成查询SQL + $sql = $this->builder->select($query); - // 执行查询操作 - $pdo = $this->query($sql, $bind, $options['master'], true); + $bind = $query->getBind(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } - $result = $pdo->fetchColumn(); + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); - if (isset($cache) && false !== $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } + $result = $pdo->fetchColumn(); + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); } return false !== $result ? $result : $default; @@ -1315,84 +1309,85 @@ abstract class Connection { $options = $query->getOptions(); - $result = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { // 判断查询缓存 $cache = $options['cache']; - $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false))); + $guid = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $field); + $result = Container::get('cache')->get($guid); - } - if (false === $result) { - if (isset($options['field'])) { - $query->removeOption('field'); + if (false !== $result) { + return $result; } + } - if (is_null($field)) { - $field = '*'; - } elseif ($key && '*' != $field) { - $field = $key . ',' . $field; - } + if (isset($options['field'])) { + $query->removeOption('field'); + } - if (is_string($field)) { - $field = array_map('trim', explode(',', $field)); - } + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } - $query->setOption('field', $field); + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } - // 生成查询SQL - $sql = $this->builder->select($query); + $query->setOption('field', $field); - $bind = $query->getBind(); + // 生成查询SQL + $sql = $this->builder->select($query); - if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 - return $this->getRealSql($sql, $bind); - } + $bind = $query->getBind(); - // 执行查询操作 - $pdo = $this->query($sql, $bind, $options['master'], true); + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } - 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); - } + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); - if (2 == $count) { - $column = $key2; - } elseif (1 == $count) { - $column = $key1; - } else { - $column = null; - } + 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); + } - $result = array_column($resultSet, $column, $key); + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; } else { - $result = []; + $column = null; } - } - if (isset($cache) && isset($guid)) { - // 缓存数据 - $this->cacheData($guid, $result, $cache); + $result = array_column($resultSet, $column, $key); + } else { + $result = []; } } + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + return $result; } @@ -2046,16 +2041,30 @@ abstract class Connection } } + /** + * 获取缓存数据 + * @access protected + * @param Query $query 查询对象 + * @param mixed $cache 缓存设置 + * @param array $options 缓存 + * @return mixed + */ + protected function getCacheData(Query $query, $cache, $data, &$key = null) + { + // 判断查询缓存 + $key = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $data); + + return Container::get('cache')->get($key); + } + /** * 生成缓存标识 * @access protected * @param Query $query 查询对象 * @param mixed $value 缓存数据 - * @param array $options 缓存参数 - * @param array $bind 绑定参数 * @return string */ - protected function getCacheKey(Query $query, $value, $options, $bind = []) + protected function getCacheKey(Query $query, $value) { if (is_scalar($value)) { $data = $value; @@ -2063,14 +2072,16 @@ abstract class Connection $data = $value[2]; } + $prefix = 'think:' . $this->getConfig('database') . '.'; + if (isset($data)) { - return 'think:' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $data; - } else { - try { - return md5(serialize($options) . serialize($bind)); - } catch (\Exception $e) { - return; - } + return $prefix . $query->getTable() . '|' . $data; + } + + try { + return md5($prefix . serialize($query->getOptions()) . serialize($query->getBind(false))); + } catch (\Exception $e) { + return; } } -- Gitee From 9f7e47d874666be0d77ea69602ebed90e1d7da5b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 11 Feb 2018 11:26:56 +0800 Subject: [PATCH 0902/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 70bad036..8c85d489 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -292,7 +292,8 @@ class Query public function connect($config = [], $name = false) { $this->connection = Connection::instance($config, $name); - $query = $this->connection->getConfig('query'); + + $query = $this->connection->getConfig('query'); if (__CLASS__ != trim($query, '\\')) { return new $query($this->connection); @@ -507,18 +508,17 @@ class Query } } return $this->getTable() . '_' . $seq; - } else { - // 当设置的分表字段不在查询条件或者数据中 - // 进行联合查询,必须设定 partition['num'] - $tableName = []; - for ($i = 0; $i < $rule['num']; $i++) { - $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); - } + } + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } - $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; - return $tableName; - } + return $tableName; } /** @@ -1649,33 +1649,35 @@ class Query */ public function order($field, $order = null) { - if (!empty($field)) { - if (is_string($field)) { - if (!empty($this->options['via'])) { - $field = $this->options['via'] . '.' . $field; - } + if (empty($field)) { + return $this; + } - $field = empty($order) ? $field : [$field => $order]; - } elseif (!empty($this->options['via'])) { - foreach ($field as $key => $val) { - if (is_numeric($key)) { - $field[$key] = $this->options['via'] . '.' . $val; - } else { - $field[$this->options['via'] . '.' . $key] = $val; - unset($field[$key]); - } - } + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; } - if (!isset($this->options['order'])) { - $this->options['order'] = []; + $field = empty($order) ? $field : [$field => $order]; + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } } + } - if (is_array($field)) { - $this->options['order'] = array_merge($this->options['order'], $field); - } else { - $this->options['order'][] = $field; - } + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; } return $this; @@ -2116,9 +2118,8 @@ class Query { if ('' === $name) { return $this->options; - } else { - return isset($this->options[$name]) ? $this->options[$name] : null; } + return isset($this->options[$name]) ? $this->options[$name] : null; } /** @@ -2693,10 +2694,9 @@ class Query if (!empty($this->model)) { $class = get_class($this->model); throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); - } else { - $table = is_array($options['table']) ? key($options['table']) : $options['table']; - throw new DataNotFoundException('table data not Found:' . $table, $table, $options); } + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); } /** -- Gitee From fa15b0b23f2c30b355d647e9dee39eabcc393112 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 11 Feb 2018 11:57:33 +0800 Subject: [PATCH 0903/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 12 +++-- library/think/Response.php | 8 +-- library/think/Route.php | 6 +-- library/think/db/Builder.php | 10 ++-- library/think/model/concern/Attribute.php | 55 ++++++++++---------- library/think/model/concern/RelationShip.php | 3 +- library/think/model/concern/SoftDelete.php | 7 ++- 7 files changed, 50 insertions(+), 51 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 3b673464..a05267cc 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -861,13 +861,15 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public static function destroy($data) { + if (empty($data) && 0 !== $data) { + return 0; + } + $model = new static(); $query = $model->db(); - if (empty($data) && 0 !== $data) { - return 0; - } elseif (is_array($data) && key($data) !== 0) { + if (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; } elseif ($data instanceof \Closure) { @@ -947,9 +949,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { return true; - } else { - return false; } + + return false; } /** diff --git a/library/think/Response.php b/library/think/Response.php index e554ab31..071d075c 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -103,9 +103,9 @@ class Response if (class_exists($class)) { return new $class($data, $code, $header, $options); - } else { - return new static($data, $code, $header, $options); } + + return new static($data, $code, $header, $options); } /** @@ -352,9 +352,9 @@ class Response { if (!empty($name)) { return isset($this->header[$name]) ? $this->header[$name] : null; - } else { - return $this->header; } + + return $this->header; } /** diff --git a/library/think/Route.php b/library/think/Route.php index 563057e4..ee73f097 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -918,10 +918,10 @@ class Route } elseif ($must) { // 强制路由不匹配则抛出异常 throw new RouteNotFoundException(); - } else { - // 默认路由解析 - return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]); } + + // 默认路由解析 + return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]); } /** diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 4f98ac95..3d8e5071 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -168,12 +168,11 @@ abstract class Builder // 过滤非标量数据 if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) { return $data; - } else { - $key = str_replace(['.', '->'], '_', $key); - $name = 'data__' . $key . $suffix; - $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - return ':' . $name; } + $key = str_replace(['.', '->'], '_', $key); + $name = 'data__' . $key . $suffix; + $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + return ':' . $name; } /** @@ -228,6 +227,7 @@ abstract class Builder { $item = []; $options = $query->getOptions(); + foreach ((array) $tables as $key => $table) { if (!is_numeric($key)) { $key = $this->connection->parseSqlTable($key); diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index dc104481..ceeae508 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -139,37 +139,38 @@ trait Attribute { if (is_string($data)) { $this->data[$data] = $value; - } else { - // 清空数据 - $this->data = []; + return $this; + } - if (is_object($data)) { - $data = get_object_vars($data); - } + // 清空数据 + $this->data = []; - if ($this->disuse) { - // 废弃字段 - foreach ((array) $this->disuse as $key) { - if (array_key_exists($key, $data)) { - unset($data[$key]); - } + if (is_object($data)) { + $data = get_object_vars($data); + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); } } + } - if (true === $value) { - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - } elseif (is_array($value)) { - foreach ($value as $name) { - if (isset($data[$name])) { - $this->data[$name] = $data[$name]; - } + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } elseif (is_array($value)) { + foreach ($value as $name) { + if (isset($data[$name])) { + $this->data[$name] = $data[$name]; } - } else { - $this->data = $data; } + } else { + $this->data = $data; } return $this; @@ -210,9 +211,8 @@ trait Attribute { if (is_null($name)) { return $this->origin; - } else { - return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; } + return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; } /** @@ -230,9 +230,8 @@ trait Attribute return $this->data[$name]; } elseif (array_key_exists($name, $this->relation)) { return $this->relation[$name]; - } else { - throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); } + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); } /** diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index 51611958..a406a44d 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -89,9 +89,8 @@ trait RelationShip return $this->relation; } elseif (array_key_exists($name, $this->relation)) { return $this->relation[$name]; - } else { - return; } + return; } /** diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 16b75b72..473f9e3e 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -52,9 +52,8 @@ trait SoftDelete return $model ->db(false) ->useSoftDelete($field, ['not null', '']); - } else { - return $model->db(false); } + return $model->db(false); } /** @@ -154,9 +153,9 @@ trait SoftDelete ->where($where) ->useSoftDelete($name, ['not null', '']) ->update([$name => null]); - } else { - return 0; } + + return 0; } /** -- Gitee From 4cf375a85130bf5cf27593ad69026aa0ced9b2bc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 12 Feb 2018 17:23:11 +0800 Subject: [PATCH 0904/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 134 ++++++++++--------------------- 1 file changed, 41 insertions(+), 93 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 3e62e946..a4ae0a95 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -188,6 +188,10 @@ class RuleItem extends Rule $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); } + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + return $this->checkRule($request, $url, $depr, $completeMatch, $option); } @@ -208,38 +212,11 @@ class RuleItem extends Rule return false; } - // 检查路由的参数分隔符 - if (isset($option['param_depr'])) { - $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); - } - - $len1 = substr_count($url, '|'); - $len2 = substr_count($this->rule, '/'); - - // 多余参数是否合并 - $merge = !empty($option['merge_extra_vars']) ? true : false; - - if ($merge && $len1 > $len2) { - $url = str_replace('|', $depr, $url); - $url = implode('|', explode($depr, $url, $len2 + 1)); - } + $pattern = array_merge($this->parent->getPattern(), $this->pattern); - if (isset($option['complete_match'])) { - $completeMatch = $option['complete_match']; - } - - if ($len1 >= $len2 || strpos($this->rule, '[')) { - // 完整匹配 - if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->rule, '[')))) { - return false; - } - - $pattern = array_merge($this->parent->getPattern(), $this->pattern); - - if (false !== $match = $this->match($url, $pattern)) { - // 匹配到路由规则 - return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); - } + if (false !== $match = $this->match($url, $pattern, $depr, $completeMatch)) { + // 匹配到路由规则 + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); } return false; @@ -250,80 +227,51 @@ class RuleItem extends Rule * @access private * @param string $url URL地址 * @param array $pattern 变量规则 + * @param string $depr URL分隔符(全局) + * @param bool $completeMatch 路由是否完全匹配 * @return array|false */ - private function match($url, $pattern) + private function match($url, $pattern, $depr, $completeMatch) { - $m2 = explode('/', $this->rule); - $m1 = explode('|', $url); - - $var = []; - - foreach ($m2 as $key => $val) { - // val中定义了多个变量 - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - $value = []; - $replace = []; - - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; - } else { - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; - } - $value[] = $name; - } - - $val = str_replace($matches[0], $replace, $val); - - if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { - array_shift($match); - foreach ($value as $k => $name) { - if (isset($match[$k])) { - $var[$name] = $match[$k]; - } - } - continue; + $var = []; + $url = str_replace('|', $depr, $url); + $rule = str_replace('/', $depr, $this->rule); + + if (preg_match_all('/(?:<\w+\??>|\[?\:\w+\]?)/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = ''; + if (strpos($name, ']')) { + $name = substr($name, 2, -1); + $optional = '?'; + } elseif (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); } else { - return false; + $name = substr($name, 1); } - } - if (0 === strpos($val, '[:')) { - // 可选参数 - $val = substr($val, 1, -1); - $optional = true; - } else { - $optional = false; + $value[] = $name; + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')' . $optional; } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); + $rule = str_replace(['/', '-'], ['\/', '\-'], $rule); + $regex = str_replace($matches[0], $replace, $rule); + $regex = str_replace([')?\/', ')?\-'], [')?\/?', ')?\-?'], $regex); - if (!$optional && !isset($m1[$key])) { - return false; - } - - if (isset($m1[$key]) && isset($pattern[$name])) { - // 检查变量规则 - if ($pattern[$name] instanceof \Closure) { - $result = call_user_func_array($pattern[$name], [$m1[$key]]); - if (false === $result) { - return false; - } - } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { - return false; - } - } + if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { + return false; + } - if (isset($m1[$key])) { - $var[$name] = $m1[$key]; + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; } - } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { - return false; } + } elseif (0 !== strcasecmp($this->rule, $url)) { + return false; } // 成功匹配后返回URL中的动态变量数组 -- Gitee From cedb5cb2a11bb986a4293c362bdc1aa1758d245a Mon Sep 17 00:00:00 2001 From: dphong Date: Mon, 12 Feb 2018 15:47:15 +0800 Subject: [PATCH 0905/1384] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E9=94=AE=E5=90=8D=E4=B8=A2=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复数组包含数字键名,后面的值将不会覆盖原来的值,而是附加到后面,且键名会以连续方式重新索引,导致规则中键名丢失的情况。 Fixed a problem that the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended, and values in the input array with numeric keys will be renumbered with incrementing keys starting from zero in the result array, caused the keys to be lost in the rules. --- library/think/Validate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index e078c88c..1b2ded06 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -187,7 +187,7 @@ class Validate */ public function __construct(array $rules = [], array $message = [], array $field = []) { - $this->rule = array_merge($this->rule, $rules); + $this->rule = $rules + $this->rule; $this->message = array_merge($this->message, $message); $this->field = array_merge($this->field, $field); } @@ -214,7 +214,7 @@ class Validate public function rule($name, $rule = '') { if (is_array($name)) { - $this->rule = array_merge($this->rule, $name); + $this->rule = $name + $this->rule; if (is_array($rule)) { $this->field = array_merge($this->field, $rule); } -- Gitee From 4375fd97cec17fa583fcd9befc083f73a29d81c3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 13 Feb 2018 11:50:09 +0800 Subject: [PATCH 0906/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 4 +- library/think/Config.php | 7 +- library/think/Container.php | 76 +++++++++--------- library/think/Controller.php | 17 ++-- library/think/Debug.php | 5 +- library/think/Env.php | 24 +++--- library/think/Error.php | 4 +- library/think/File.php | 104 ++++++++++++------------- library/think/Hook.php | 9 +-- library/think/Lang.php | 4 +- library/think/Loader.php | 4 +- library/think/Log.php | 56 ++++++------- library/think/Model.php | 12 ++- library/think/Request.php | 147 ++++++++++++++++++----------------- library/think/Route.php | 11 ++- 15 files changed, 239 insertions(+), 245 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 8892585c..49a33c1f 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -574,9 +574,9 @@ class App implements \ArrayAccess return $this->__get($class); } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { return $this->__get($emptyClass); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); } + + throw new ClassNotFoundException('class not exists:' . $class, $class); } /** diff --git a/library/think/Config.php b/library/think/Config.php index e30471a5..23df8495 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -72,12 +72,11 @@ class Config implements \ArrayAccess return $this->set(include $file, $name); } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { return $this->set(yaml_parse_file($file), $name); - } else { - return $this->parse($file, $type, $name); } - } else { - return $this->config; + return $this->parse($file, $type, $name); } + + return $this->config; } /** diff --git a/library/think/Container.php b/library/think/Container.php index b934cb34..9181989d 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -155,23 +155,23 @@ class Container } if (isset($this->instances[$abstract]) && !$newInstance) { - $object = $this->instances[$abstract]; - } else { - if (isset($this->bind[$abstract])) { - $concrete = $this->bind[$abstract]; - - if ($concrete instanceof Closure) { - $object = $this->invokeFunction($concrete, $vars); - } else { - $object = $this->make($concrete, $vars, $newInstance); - } + return $this->instances[$abstract]; + } + + if (isset($this->bind[$abstract])) { + $concrete = $this->bind[$abstract]; + + if ($concrete instanceof Closure) { + $object = $this->invokeFunction($concrete, $vars); } else { - $object = $this->invokeClass($abstract, $vars); + $object = $this->make($concrete, $vars, $newInstance); } + } else { + $object = $this->invokeClass($abstract, $vars); + } - if (!$newInstance) { - $this->instances[$abstract] = $object; - } + if (!$newInstance) { + $this->instances[$abstract] = $object; } return $object; @@ -262,30 +262,30 @@ class Container */ protected function bindParams($reflect, $vars = []) { - $args = []; - - if ($reflect->getNumberOfParameters() > 0) { - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; - $params = $reflect->getParameters(); - - foreach ($params as $param) { - $name = $param->getName(); - $class = $param->getClass(); - - if ($class) { - $className = $class->getName(); - $args[] = $this->make($className); - } elseif (1 == $type && !empty($vars)) { - $args[] = array_shift($vars); - } elseif (0 == $type && isset($vars[$name])) { - $args[] = $vars[$name]; - } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); - } else { - throw new InvalidArgumentException('method param miss:' . $name); - } + if ($reflect->getNumberOfParameters() == 0) { + return []; + } + + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + + foreach ($params as $param) { + $name = $param->getName(); + $class = $param->getClass(); + + if ($class) { + $className = $class->getName(); + $args[] = $this->make($className); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $args[] = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); } } diff --git a/library/think/Controller.php b/library/think/Controller.php index cc3953d2..ffa38186 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -70,12 +70,10 @@ class Controller $this->initialize(); // 前置操作方法 - if ($this->beforeActionList) { - foreach ($this->beforeActionList as $method => $options) { - is_numeric($method) ? - $this->beforeAction($options) : - $this->beforeAction($method, $options); - } + foreach ((array) $this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); } } @@ -232,11 +230,10 @@ class Controller if (!$v->check($data)) { if ($this->failException) { throw new ValidateException($v->getError()); - } else { - return $v->getError(); } - } else { - return true; + return $v->getError(); } + + return true; } } diff --git a/library/think/Debug.php b/library/think/Debug.php index 8a384e14..21dd87be 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -221,11 +221,10 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo($output); + echo ($output); return; - } else { - return $output; } + return $output; } public function inject(Response $response, &$content) diff --git a/library/think/Env.php b/library/think/Env.php index 2c41794f..ef5d9468 100644 --- a/library/think/Env.php +++ b/library/think/Env.php @@ -62,21 +62,21 @@ class Env { $result = getenv('PHP_' . $name); - if (false !== $result) { - if ('false' === $result) { - $result = false; - } elseif ('true' === $result) { - $result = true; - } + if (false === $result) { + return $default; + } - if (!isset($this->data[$name])) { - $this->data[$name] = $result; - } + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } - return $result; - } else { - return $default; + if (!isset($this->data[$name])) { + $this->data[$name] = $result; } + + return $result; } /** diff --git a/library/think/Error.php b/library/think/Error.php index c00a36a6..b8c8fc58 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -66,9 +66,9 @@ class Error if (error_reporting() & $errno) { // 将错误信息托管至 think\exception\ErrorException throw $exception; - } else { - self::getExceptionHandler()->report($exception); } + + self::getExceptionHandler()->report($exception); } /** diff --git a/library/think/File.php b/library/think/File.php index 00e83a58..79b6eeff 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -147,7 +147,7 @@ class File extends SplFileObject /** * 检查目录是否可写 - * @access public + * @access protected * @param string $path 目录 * @return boolean */ @@ -159,10 +159,10 @@ class File extends SplFileObject if (mkdir($path, 0755, true)) { return true; - } else { - $this->error = ['directory {:path} creation failed', ['path' => $path]]; - return false; } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + return false; } /** @@ -227,27 +227,10 @@ class File extends SplFileObject { $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; } @@ -269,6 +252,7 @@ class File extends SplFileObject $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); if (!in_array($extension, $ext)) { + $this->error = 'extensions to upload is not allowed'; return false; } @@ -286,6 +270,7 @@ class File extends SplFileObject /* 对图像文件进行严格检测 */ 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; } @@ -297,13 +282,13 @@ class File extends SplFileObject { if (function_exists('exif_imagetype')) { return exif_imagetype($image); - } else { - try { - $info = getimagesize($image); - return $info ? $info[2] : false; - } catch (\Exception $e) { - return false; - } + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; } } @@ -316,6 +301,7 @@ class File extends SplFileObject public function checkSize($size) { if ($this->getSize() > $size) { + $this->error = 'filesize not match'; return false; } @@ -335,6 +321,7 @@ class File extends SplFileObject } if (!in_array(strtolower($this->getMime()), $mime)) { + $this->error = 'mimetype to upload is not allowed'; return false; } @@ -402,7 +389,7 @@ class File extends SplFileObject /** * 获取保存文件名 - * @access public + * @access protected * @param string|bool $savename 保存的文件名 默认自动生成 * @return string */ @@ -410,25 +397,9 @@ class File extends SplFileObject { 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') . '/' . md5(microtime(true)); - break; - default: - if (in_array($this->rule, hash_algos())) { - $hash = $this->hash($this->rule); - $savename = substr($hash, 0, 2) . '/' . substr($hash, 2); - } elseif (is_callable($this->rule)) { - $savename = call_user_func($this->rule); - } else { - $savename = date('Ymd') . '/' . md5(microtime(true)); - } - } - } + $savename = $this->autoBuildName(); } elseif ('' === $savename || false === $savename) { + // 保留原文件名 $savename = $this->getInfo('name'); } @@ -439,9 +410,38 @@ class File extends SplFileObject 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') . '/' . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . '/' . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . '/' . md5(microtime(true)); + } + } + } + + return $savename; + } + /** * 获取错误代码信息 - * @access public + * @access private * @param int $errorNo 错误号 */ private function error($errorNo) diff --git a/library/think/Hook.php b/library/think/Hook.php index 30b67307..b82fb5a0 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -116,9 +116,9 @@ class Hook if (empty($tag)) { //获取全部的插件信息 return $this->tags; - } else { - return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : []; } + + return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : []; } /** @@ -137,10 +137,7 @@ class Hook foreach ($tags as $key => $name) { $results[$key] = $this->execTag($name, $tag, $params); - if (false === $results[$key]) { - // 如果返回false 则中断行为执行 - break; - } elseif (!is_null($results[$key]) && $once) { + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { break; } } diff --git a/library/think/Lang.php b/library/think/Lang.php index ba4f700a..2168c13a 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -79,9 +79,9 @@ class Lang if (is_array($name)) { return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; - } else { - return $this->lang[$range][strtolower($name)] = $value; } + + return $this->lang[$range][strtolower($name)] = $value; } /** diff --git a/library/think/Loader.php b/library/think/Loader.php index 0a028419..ce334dc3 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -361,9 +361,9 @@ class Loader return strtoupper($match[1]); }, $name); return $ucfirst ? ucfirst($name) : lcfirst($name); - } else { - return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); } + + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); } } diff --git a/library/think/Log.php b/library/think/Log.php index 49258141..d04f8c34 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -174,41 +174,41 @@ class Log implements LoggerInterface */ public function save() { - if (!empty($this->log)) { - if (is_null($this->driver)) { - $this->init($this->app['config']->pull('log')); - } + if (empty($this->log)) { + return true; + } - if (!$this->check($this->config)) { - // 检测日志写入权限 - return false; - } + if (is_null($this->driver)) { + $this->init($this->app['config']->pull('log')); + } - if (empty($this->config['level'])) { - // 获取全部日志 - $log = $this->log; - if (!$this->app->isDebug() && isset($log['debug'])) { - unset($log['debug']); - } - } else { - // 记录允许级别 - $log = []; - foreach ($this->config['level'] as $level) { - if (isset($this->log[$level])) { - $log[$level] = $this->log[$level]; - } - } - } + if (!$this->check($this->config)) { + // 检测日志写入权限 + return false; + } - $result = $this->driver->save($log); - if ($result) { - $this->log = []; + if (empty($this->config['level'])) { + // 获取全部日志 + $log = $this->log; + if (!$this->app->isDebug() && isset($log['debug'])) { + unset($log['debug']); } + } else { + // 记录允许级别 + $log = []; + foreach ($this->config['level'] as $level) { + if (isset($this->log[$level])) { + $log[$level] = $this->log[$level]; + } + } + } - return $result; + $result = $this->driver->save($log); + if ($result) { + $this->log = []; } - return true; + return $result; } /** diff --git a/library/think/Model.php b/library/think/Model.php index a05267cc..a24722aa 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -478,13 +478,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $where = $array; } - if (!empty($this->relationWrite)) { - foreach ($this->relationWrite as $name => $val) { - if (is_array($val)) { - foreach ($val as $key) { - if (isset($data[$key])) { - unset($data[$key]); - } + foreach ((array) $this->relationWrite as $name => $val) { + if (is_array($val)) { + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); } } } diff --git a/library/think/Request.php b/library/think/Request.php index 1868c5f4..db80db31 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -279,9 +279,9 @@ class Request if (array_key_exists($method, $this->hook)) { array_unshift($args, $this); return call_user_func_array($this->hook[$method], $args); - } else { - throw new Exception('method not exists:' . static::class . '->' . $method); } + + throw new Exception('method not exists:' . static::class . '->' . $method); } /** @@ -437,10 +437,10 @@ class Request { if (is_null($domain)) { return $this->panDomain; - } else { - $this->panDomain = $domain; - return $this; } + + $this->panDomain = $domain; + return $this; } /** @@ -1256,9 +1256,9 @@ class Request { if (is_null($filter)) { return $this->filter; - } else { - $this->filter = $filter; } + + $this->filter = $filter; } protected function getFilter($filter, $default) @@ -1478,9 +1478,9 @@ class Request if (true === $ajax) { return $result; - } else { - return $this->param($this->config->get('var_ajax')) ? true : $result; } + + return $this->param($this->config->get('var_ajax')) ? true : $result; } /** @@ -1495,9 +1495,9 @@ class Request if (true === $pjax) { return $result; - } else { - return $this->param($this->config->get('var_pjax')) ? true : $result; } + + return $this->param($this->config->get('var_pjax')) ? true : $result; } /** @@ -1555,9 +1555,9 @@ class Request return true; } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { return true; - } else { - return false; } + + return false; } /** @@ -1655,9 +1655,9 @@ class Request { if (!empty($route)) { $this->routeInfo = $route; - } else { - return $this->routeInfo; } + + return $this->routeInfo; } /** @@ -1686,9 +1686,9 @@ class Request if (!is_null($module)) { $this->module = $module; return $this; - } else { - return $this->module ?: ''; } + + return $this->module ?: ''; } /** @@ -1702,9 +1702,9 @@ class Request if (!is_null($controller)) { $this->controller = $controller; return $this; - } else { - return $this->controller ?: ''; } + + return $this->controller ?: ''; } /** @@ -1718,9 +1718,9 @@ class Request if (!is_null($action)) { $this->action = $action; return $this; - } else { - return $this->action ?: ''; } + + return $this->action ?: ''; } /** @@ -1734,9 +1734,9 @@ class Request if (!is_null($lang)) { $this->langset = $lang; return $this; - } else { - return $this->langset ?: ''; } + + return $this->langset ?: ''; } /** @@ -1800,66 +1800,67 @@ class Request $except = []; } - if (false !== $key && $this->isGet() && !$this->isCheckCache) { - // 标记请求缓存检查 - $this->isCheckCache = true; - if (false === $expire) { - // 关闭当前缓存 - return; - } + if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) { + // 关闭当前缓存 + return; + } - foreach ($except as $rule) { - if (0 === stripos($this->url(), $rule)) { - return; - } - } + // 标记请求缓存检查 + $this->isCheckCache = true; - if ($key instanceof \Closure) { - $key = call_user_func_array($key, [$this]); - } elseif (true === $key) { - // 自动缓存功能 - $key = '__URL__'; - } elseif (strpos($key, '|')) { - list($key, $fun) = explode('|', $key); + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; } + } - // 特殊规则替换 - if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); - } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } - if (false !== strpos($key, ':')) { - $param = $this->param(); - foreach ($param as $item => $val) { - if (is_string($val) && false !== strpos($key, ':' . $item)) { - $key = str_replace(':' . $item, $val, $key); - } - } - } elseif (strpos($key, ']')) { - if ('[' . $this->ext() . ']' == $key) { - // 缓存某个后缀的请求 - $key = md5($this->url()); - } else { - return; - } - } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } - if (isset($fun)) { - $key = $fun($key); + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } } - $cache = Container::get('cache'); - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { - // 读取缓存 - $response = Response::create()->code(304); - throw new HttpResponseException($response); - } elseif ($cache->has($key)) { - list($content, $header) = $cache->get($key); - $response = Response::create($content)->header($header); - throw new HttpResponseException($response); + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); } else { - $this->cache = [$key, $expire, $tag]; + return; } } + + if (isset($fun)) { + $key = $fun($key); + } + $cache = Container::get('cache'); + + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($cache->has($key)) { + list($content, $header) = $cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + + $this->cache = [$key, $expire, $tag]; } /** diff --git a/library/think/Route.php b/library/think/Route.php index ee73f097..c90e3123 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -719,21 +719,24 @@ class Route } /** - * 注册控制器路由 操作方法对应不同的请求后缀 + * 注册控制器路由 操作方法对应不同的请求前缀 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 - * @return $this + * @return RuleGroup */ public function controller($rule, $route = '', $option = [], $pattern = []) { + $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); + foreach ($this->methodPrefix as $type => $val) { - $this->$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + $item = $this->$type(':action', $val . ':action'); + $group->addRule($item, $type); } - return $this; + return $group->prefix($route . '/'); } /** -- Gitee From e2743f2f50ce892edb5d91f19e2bdc7c9607520d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 13 Feb 2018 21:29:15 +0800 Subject: [PATCH 0907/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1U?= =?UTF-8?q?rl=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 40 ++++++++++++++-------------------------- library/think/Url.php | 10 +++++----- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index c90e3123..2609a2e2 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -993,32 +993,20 @@ class Route // 提取路由规则中的变量 $var = []; - foreach (explode('/', $rule) as $val) { - $optional = false; - - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $optional = true; - } else { - $optional = false; - } - $var[$name] = $optional ? 2 : 1; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $optional = true; - $val = substr($val, 1, -1); - } - - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - if ('$' == substr($name, -1)) { - $name = substr($name, 0, -1); + if (preg_match_all('/(?:<\w+\??>|\[?\:\w+\]?)/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, ']')) { + $name = substr($name, 2, -1); + $optional = true; + } elseif (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } else { + $name = substr($name, 1); } $var[$name] = $optional ? 2 : 1; diff --git a/library/think/Url.php b/library/think/Url.php index e7cf75d3..17c0177b 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -320,20 +320,20 @@ class Url foreach ($rule as $item) { list($url, $pattern, $domain, $suffix) = $item; if (empty($pattern)) { - return [rtrim($url, '$'), $domain, $suffix]; + return [rtrim($url, '/-'), $domain, $suffix]; } $type = $this->app['config']->get('url_common_param'); foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); unset($vars[$key]); - $result = [$url, $domain, $suffix]; + $result = [rtrim($url, '/-'), $domain, $suffix]; } elseif (2 == $val) { - $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>'], '', $url); - $result = [$url, $domain, $suffix]; + $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>'], '', $url); + $result = [rtrim($url, '/-'), $domain, $suffix]; } else { break; } -- Gitee From 53b1acccb1138609a6308ff9136234088d6a1d6c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 13 Feb 2018 21:44:03 +0800 Subject: [PATCH 0908/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BUrl=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index 17c0177b..82dda9c3 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -320,7 +320,7 @@ class Url foreach ($rule as $item) { list($url, $pattern, $domain, $suffix) = $item; if (empty($pattern)) { - return [rtrim($url, '/-'), $domain, $suffix]; + return [rtrim($url, '?/-'), $domain, $suffix]; } $type = $this->app['config']->get('url_common_param'); @@ -329,11 +329,12 @@ class Url if (isset($vars[$key])) { $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); unset($vars[$key]); - - $result = [rtrim($url, '/-'), $domain, $suffix]; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; } elseif (2 == $val) { $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>'], '', $url); - $result = [rtrim($url, '/-'), $domain, $suffix]; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; } else { break; } -- Gitee From c80ddc8323da20ca1b84caa5e1fbfe7f81646d30 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 13 Feb 2018 21:56:54 +0800 Subject: [PATCH 0909/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index a4ae0a95..e7564431 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -258,7 +258,7 @@ class RuleItem extends Rule $rule = str_replace(['/', '-'], ['\/', '\-'], $rule); $regex = str_replace($matches[0], $replace, $rule); - $regex = str_replace([')?\/', ')?\-'], [')?\/?', ')?\-?'], $regex); + $regex = str_replace([')?\/', ')?\-', '\/(', '\-('], [')?\/?', ')?\-?', '\/?(', '\-?('], $regex); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; -- Gitee From 5b5cfb665a5e55aa833617395797b7d36f5d7763 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 13 Feb 2018 22:41:34 +0800 Subject: [PATCH 0910/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index e7564431..ca0d58a3 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -258,7 +258,7 @@ class RuleItem extends Rule $rule = str_replace(['/', '-'], ['\/', '\-'], $rule); $regex = str_replace($matches[0], $replace, $rule); - $regex = str_replace([')?\/', ')?\-', '\/(', '\-('], [')?\/?', ')?\-?', '\/?(', '\-?('], $regex); + $regex = str_replace([')?\/(', ')?\-('], [')?\/?(', ')?\-?('], $regex); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; -- Gitee From 653afec192c0558c42c187f0fc32b5c4865d32de Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 13 Feb 2018 22:46:16 +0800 Subject: [PATCH 0911/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index 82dda9c3..ea66c7d1 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -327,12 +327,12 @@ class Url foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); unset($vars[$key]); $url = str_replace(['/?', '-?'], ['/', '-'], $url); $result = [rtrim($url, '?/-'), $domain, $suffix]; } elseif (2 == $val) { - $url = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>'], '', $url); + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); $url = str_replace(['/?', '-?'], ['/', '-'], $url); $result = [rtrim($url, '?/-'), $domain, $suffix]; } else { -- Gitee From 67283a610624086737b0714e2d9103bdc96f9db7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 14 Feb 2018 12:18:34 +0800 Subject: [PATCH 0912/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=80=E5=AF=B9?= =?UTF-8?q?=E4=B8=80=E5=85=B3=E8=81=94=E9=A2=84=E8=BD=BD=E5=85=A5=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsTo.php | 4 ++++ library/think/model/relation/HasOne.php | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 954553d3..7fd92a1d 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -125,6 +125,8 @@ class BelongsTo extends OneToOne } if (!empty($range)) { + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere([ [$localKey, 'in', $range], ], $localKey, $relation, $subRelation, $closure); @@ -168,6 +170,8 @@ class BelongsTo extends OneToOne $localKey = $this->localKey; $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere([ [$localKey, '=', $result->$foreignKey], ], $localKey, $relation, $subRelation, $closure); diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 62e1e7e8..4744d505 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -134,6 +134,8 @@ class HasOne extends OneToOne } if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere([ [$foreignKey, 'in', $range], ], $foreignKey, $relation, $subRelation, $closure); @@ -176,7 +178,10 @@ class HasOne extends OneToOne { $localKey = $this->localKey; $foreignKey = $this->foreignKey; - $data = $this->eagerlyWhere([ + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ [$foreignKey, '=', $result->$localKey], ], $foreignKey, $relation, $subRelation, $closure); -- Gitee From da15e03a0ab2cd681164f42379a28a58d7f60310 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 15 Feb 2018 12:23:47 +0800 Subject: [PATCH 0913/1384] =?UTF-8?q?Request=E7=B1=BB=E5=A2=9E=E5=8A=A0roo?= =?UTF-8?q?tDomain=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 20 ++++++++- library/think/Route.php | 20 +-------- library/think/Template.php | 78 +++++++++++++---------------------- library/think/Validate.php | 49 +++++++++++----------- library/think/route/Rule.php | 79 ++++++++++++++++++++++++------------ 5 files changed, 127 insertions(+), 119 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index db80db31..9b3138bd 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -403,10 +403,28 @@ class Request return $this->domain; } + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain() + { + $root = $this->config->get('app.url_domain_root'); + + if (!$root) { + $item = explode('.', $this->host()); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + /** * 获取当前子域名 * @access public - * @return string|$this + * @return string */ public function subDomain() { diff --git a/library/think/Route.php b/library/think/Route.php index 2609a2e2..d6df54c1 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -204,22 +204,6 @@ class Route return $this; } - /** - * 获取当前根域名 - * @access protected - * @return string - */ - protected function getRootDomain() - { - $root = $this->config->get('app.url_domain_root'); - if (!$root) { - $item = explode('.', $this->host); - $count = count($item); - $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; - } - return $root; - } - /** * 注册域名路由 * @access public @@ -235,7 +219,7 @@ class Route $domainName = is_array($name) ? array_shift($name) : $name; if ('*' != $domainName && !strpos($domainName, '.')) { - $domainName .= '.' . $this->getRootDomain(); + $domainName .= '.' . $this->request->rootDomain(); } $route = $this->config->get('url_lazy_route') ? $rule : null; @@ -261,7 +245,7 @@ class Route $this->domains[$domainName] = $domain; if (is_array($name) && !empty($name)) { - $root = $this->getRootDomain(); + $root = $this->request->rootDomain(); foreach ($name as $item) { if (!strpos($item, '.')) { $item .= '.' . $root; diff --git a/library/think/Template.php b/library/think/Template.php index b97c8390..9956c85a 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -85,18 +85,21 @@ class Template */ public function __construct(array $config = []) { - $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; - $this->config = array_merge($this->config, $config); + $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; + $this->config = array_merge($this->config, $config); + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; $this->config['taglib_end_origin'] = $this->config['taglib_end']; - $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); - $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); - $this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']); - $this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']); + + $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); + $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); + $this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']); + $this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']); // 初始化模板编译存储器 - $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + $this->storage = new $class(); } @@ -153,8 +156,6 @@ class Template $this->config = array_merge($this->config, $config); } elseif (isset($this->config[$config])) { return $this->config[$config]; - } else { - return; } } @@ -168,20 +169,20 @@ class Template { if ('' == $name) { return $this->data; - } else { - $data = $this->data; + } - foreach (explode('.', $name) as $key => $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - $data = null; - break; - } - } + $data = $this->data; - return $data; + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } } + + return $data; } /** @@ -311,18 +312,7 @@ class Template */ private function checkCache($cacheFile) { - // 未开启缓存功能 - if (!$this->config['tpl_cache']) { - return false; - } - - // 缓存文件不存在 - if (!is_file($cacheFile)) { - return false; - } - - // 读取缓存文件失败 - if (!$handle = @fopen($cacheFile, "r")) { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) { return false; } @@ -417,8 +407,6 @@ class Template $this->storage->write($cacheFile, $content); $this->includeFile = []; - - return; } /** @@ -490,8 +478,6 @@ class Template // 还原被替换的Literal标签 $this->parseLiteral($content, true); - - return; } /** @@ -508,10 +494,8 @@ class Template // PHP语法检查 if ($this->config['tpl_deny_php'] && false !== strpos($content, 'parseTag($content, $hide ? '' : $tagLib); - - return; } /** @@ -827,9 +805,9 @@ class Template if (!empty($name) && isset($array[$name])) { return $array[$name]; - } else { - return $array; } + + return $array; } /** @@ -1268,9 +1246,9 @@ class Template $this->includeFile[$template] = filemtime($template); return $template; - } else { - throw new TemplateNotFoundException('template not exists:' . $template, $template); } + + throw new TemplateNotFoundException('template not exists:' . $template, $template); } /** diff --git a/library/think/Validate.php b/library/think/Validate.php index 1b2ded06..984332f5 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -784,13 +784,13 @@ class Validate { if (function_exists('exif_imagetype')) { return exif_imagetype($image); - } else { - try { - $info = getimagesize($image); - return $info ? $info[2] : false; - } catch (\Exception $e) { - return false; - } + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; } } @@ -844,9 +844,9 @@ class Validate return true; } elseif ($file instanceof File) { return $file->checkExt($rule); - } else { - return false; } + + return false; } /** @@ -867,9 +867,9 @@ class Validate return true; } elseif ($file instanceof File) { return $file->checkMime($rule); - } else { - return false; } + + return false; } /** @@ -890,9 +890,9 @@ class Validate return true; } elseif ($file instanceof File) { return $file->checkSize($rule); - } else { - return false; } + + return false; } /** @@ -928,9 +928,9 @@ class Validate list($w, $h) = $rule; return $w == $width && $h == $height; - } else { - return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); } /** @@ -1010,6 +1010,7 @@ class Validate if ($db->where($map)->field($pk)->find()) { return false; } + return true; } @@ -1061,9 +1062,9 @@ class Validate if ($this->getDataValue($data, $field) == $val) { return !empty($value) || '0' == $value; - } else { - return true; } + + return true; } /** @@ -1080,9 +1081,9 @@ class Validate if ($result) { return !empty($value) || '0' == $value; - } else { - return true; } + + return true; } /** @@ -1099,9 +1100,9 @@ class Validate if (!empty($val)) { return !empty($value) || '0' == $value; - } else { - return true; } + + return true; } /** @@ -1183,10 +1184,10 @@ class Validate // 长度区间 list($min, $max) = explode(',', $rule); return $length >= $min && $length <= $max; - } else { - // 指定长度 - return $length == $rule; } + + // 指定长度 + return $length == $rule; } /** diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 8965fced..b8446646 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -713,9 +713,9 @@ abstract class Rule return new ResponseDispatch($result); } elseif ($result instanceof Dispatch) { return $result; - } else { - return false; } + + return false; } /** @@ -740,24 +740,13 @@ abstract class Rule $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301); } elseif (false !== strpos($route, '\\')) { // 路由到方法 - list($path, $var) = $this->parseUrlPath($route); - $route = str_replace('/', '@', implode('/', $path)); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = new CallbackDispatch($method, $var); + $result = $this->dispatchMethod($route); } elseif (0 === strpos($route, '@')) { // 路由到控制器 - $route = substr($route, 1); - list($route, $var) = $this->parseUrlPath($route); - $result = new ControllerDispatch(implode('/', $route), $var); - - $request->action(array_pop($route)); - $app = Container::get('app'); - $request->controller($route ? array_pop($route) : $app->config('default_controller')); - $request->module($route ? array_pop($route) : $app->config('default_module')); - $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : '')); + $result = $this->dispatchController(substr($route, 1)); } else { // 路由到模块/控制器/操作 - $result = $this->parseModule($route); + $result = $this->dispatchModule($route); } return $result; @@ -766,18 +755,56 @@ abstract class Rule /** * 解析URL地址为 模块/控制器/操作 * @access protected - * @param string $url URL地址 - * @return array + * @param string $route 路由地址 + * @return CallbackDispatch */ - protected function parseModule($url) + protected function dispatchMethod($route) { - list($path, $var) = $this->parseUrlPath($url); - $config = Container::get('config'); - $request = Container::get('request'); - $action = array_pop($path); - $controller = !empty($path) ? array_pop($path) : null; - $module = $config->get('app_multi_module') && !empty($path) ? array_pop($path) : null; - $method = $request->method(); + list($path, $var) = $this->parseUrlPath($route); + + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + + return new CallbackDispatch($method, $var); + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController($route) + { + list($route, $var) = $this->parseUrlPath($route); + + $result = new ControllerDispatch(implode('/', $route), $var); + + $request->action(array_pop($route)); + $app = Container::get('app'); + $request->controller($route ? array_pop($route) : $app->config('default_controller')); + $request->module($route ? array_pop($route) : $app->config('default_module')); + $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : '')); + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param string $route 路由地址 + * @return ModuleDispatch + */ + protected function dispatchModule($route) + { + list($path, $var) = $this->parseUrlPath($route); + + $config = Container::get('config'); + $request = Container::get('request'); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = $config->get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = $request->method(); if ($config->get('use_action_prefix') && $this->router->getMethodPrefix($method)) { $prefix = $this->router->getMethodPrefix($method); -- Gitee From c822a149d08e9e0ce4d121eb27c1c7c8dbe7bd27 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 16 Feb 2018 09:59:28 +0800 Subject: [PATCH 0914/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=AF=E9=80=89=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index ca0d58a3..20f706f3 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -259,6 +259,7 @@ class RuleItem extends Rule $rule = str_replace(['/', '-'], ['\/', '\-'], $rule); $regex = str_replace($matches[0], $replace, $rule); $regex = str_replace([')?\/(', ')?\-('], [')?\/?(', ')?\-?('], $regex); + $regex = substr_replace($regex, '\/?(', strrpos($regex, '\/('), 3); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; -- Gitee From 36d96201c067f37fd72ab6c27165c8872d19ecc8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 16 Feb 2018 22:37:08 +0800 Subject: [PATCH 0915/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 20f706f3..c15c449b 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -237,38 +237,36 @@ class RuleItem extends Rule $url = str_replace('|', $depr, $url); $rule = str_replace('/', $depr, $this->rule); - if (preg_match_all('/(?:<\w+\??>|\[?\:\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { foreach ($matches[0] as $name) { $optional = ''; + $slash = substr($name, 0, 1); + if (strpos($name, ']')) { - $name = substr($name, 2, -1); + $name = substr($name, 3, -1); $optional = '?'; } elseif (strpos($name, '?')) { - $name = substr($name, 1, -2); + $name = substr($name, 2, -2); $optional = '?'; } elseif (strpos($name, '>')) { - $name = substr($name, 1, -1); + $name = substr($name, 2, -1); } else { - $name = substr($name, 1); + $name = substr($name, 2); } - $value[] = $name; - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')' . $optional; + $replace[] = '([$\\' . $slash . '](?<' . $name . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; } - $rule = str_replace(['/', '-'], ['\/', '\-'], $rule); $regex = str_replace($matches[0], $replace, $rule); - $regex = str_replace([')?\/(', ')?\-('], [')?\/?(', ')?\-?('], $regex); - $regex = substr_replace($regex, '\/?(', strrpos($regex, '\/('), 3); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; } array_shift($match); - foreach ($value as $k => $name) { - if (isset($match[$k])) { - $var[$name] = $match[$k]; + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; } } } elseif (0 !== strcasecmp($this->rule, $url)) { -- Gitee From 4fa1f5f13b2fbf7df40d25bb7bd15762cdc26015 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 17 Feb 2018 19:35:58 +0800 Subject: [PATCH 0916/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index c15c449b..1ac7b900 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -258,6 +258,7 @@ class RuleItem extends Rule } $regex = str_replace($matches[0], $replace, $rule); + $regex = str_replace([')/', ')-'], [')\/', ')\-'], $regex); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; -- Gitee From 6be5eaf06e7372ceeb879f026284cdd30062bbee Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 17 Feb 2018 20:38:28 +0800 Subject: [PATCH 0917/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 1ac7b900..aba710e4 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -233,6 +233,13 @@ class RuleItem extends Rule */ private function match($url, $pattern, $depr, $completeMatch) { + $ruleItem = explode('/', $this->rule); + $urlItem = explode('|', $url); + + if (false === strpos($ruleItem[0], ':') && false === strpos($ruleItem[0], '<') && $ruleItem[0] != $urlItem[0]) { + return false; + } + $var = []; $url = str_replace('|', $depr, $url); $rule = str_replace('/', $depr, $this->rule); -- Gitee From 5930f4a3074a207f833a25207032ccb14ee57f2a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 17 Feb 2018 21:12:48 +0800 Subject: [PATCH 0918/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index aba710e4..175a4269 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -207,11 +207,6 @@ class RuleItem extends Rule */ private function checkRule($request, $url, $depr, $completeMatch = false, $option = []) { - // 检查完整规则定义 - if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { - return false; - } - $pattern = array_merge($this->parent->getPattern(), $this->pattern); if (false !== $match = $this->match($url, $pattern, $depr, $completeMatch)) { @@ -233,9 +228,20 @@ class RuleItem extends Rule */ private function match($url, $pattern, $depr, $completeMatch) { + // 检查完整规则定义 + if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + $ruleItem = explode('/', $this->rule); $urlItem = explode('|', $url); + // 检查URL长度 + if (count($urlItem) < count($ruleItem) && false === strpos($this->rule, '[') && false === strpos($this->rule, '?')) { + return false; + } + + // 检查第一个元素 if (false === strpos($ruleItem[0], ':') && false === strpos($ruleItem[0], '<') && $ruleItem[0] != $urlItem[0]) { return false; } -- Gitee From 4e00bf976e63add8bf7c10b80a79f8aec28bc3d8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Feb 2018 21:40:28 +0800 Subject: [PATCH 0919/1384] =?UTF-8?q?=E5=88=86=E7=BB=84=E5=A2=9E=E5=8A=A0m?= =?UTF-8?q?ergeRuleRegex=E6=96=B9=E6=B3=95=E8=BF=9B=E8=A1=8C=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E8=B7=AF=E7=94=B1=E8=A7=84=E5=88=99=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 3 +- library/think/route/Rule.php | 37 ++++++++++ library/think/route/RuleGroup.php | 72 ++++++++++++++++++- library/think/route/RuleItem.php | 112 ++++++++++++++++++++---------- 4 files changed, 183 insertions(+), 41 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index d6df54c1..9d9f8b0b 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -895,7 +895,7 @@ class Route $result = $domain->check($this->request, $url, $depr, $completeMatch); if (false === $result && !empty($this->cross)) { - // 检测跨越路由 + // 检测跨域路由 $result = $this->cross->check($this->request, $url, $depr, $completeMatch); } @@ -914,7 +914,6 @@ class Route /** * 检测域名的路由规则 * @access protected - * @param string $host 当前主机地址 * @return Domain */ protected function checkDomain() diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index b8446646..d61cc3d3 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -919,6 +919,43 @@ abstract class Rule return [$path, $var]; } + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex($rule, $match, $pattern = [], $completeMatch = false, $suffix = '') + { + foreach ($match as $name) { + $optional = ''; + $slash = substr($name, 0, 1); + + if (strpos($name, ']')) { + $name = substr($name, 3, -1); + $optional = '?'; + } elseif (strpos($name, '?')) { + $name = substr($name, 2, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 2, -1); + } else { + $name = substr($name, 2); + } + + $replace[] = '([$\\' . $slash . '](?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; + } + + $regex = str_replace($match, $replace, $rule); + $regex = str_replace([')?/', ')/', ')?-', ')-'], [')\/', ')\/', ')\-', ')\-'], $regex); + + return $regex . ($completeMatch ? '$' : ''); + } + /** * 设置路由参数 * @access public diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 40c4f3dd..afe21ed0 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -183,7 +183,16 @@ class RuleGroup extends Rule } } - // 遍历分组路由 + if (!empty($this->option['merge_rule_regex'])) { + // 合并路由正则规则进行路由匹配检查 + $result = $this->checkMergeRuleRegex($request, $rules, $url, $depr, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 检查分组路由 foreach ($rules as $key => $item) { $result = $item->check($request, $url, $depr, $completeMatch); @@ -205,6 +214,56 @@ class RuleGroup extends Rule return $result; } + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex($request, &$rules, $url, $depr, $completeMatch) + { + foreach ($rules as $key => $item) { + if ($item instanceof RuleItem) { + $items[$key] = $item; + unset($rules[$key]); + $rule = str_replace('/', $depr, $item->getRule()); + + if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; + $regex[] = $this->buildRuleRegex($rule, $matches[0], $pattern, $complete, '_THINK_' . $key); + } + } + } + + $url = str_replace('|', $depr, $url); + + if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/', $url, $match)) { + + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + list($name, $pos) = explode('_THINK_', $key); + + $var[$name] = $val; + } + } + + if (isset($pos)) { + $result = $items[$pos]->checkHasMatchRule($request, $url, $var); + + if (false !== $result) { + return $result; + } + } + } + + return false; + } + /** * 设置自动路由 * @access public @@ -263,6 +322,17 @@ class RuleGroup extends Rule return $this->option('prefix', $prefix); } + /** + * 合并分组的路由规则正则 + * @access public + * @param bool $merge + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + return $this->option('merge_rule_regex', $merge); + } + /** * 获取完整分组Name * @access public diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 175a4269..a3864f58 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -85,6 +85,16 @@ class RuleItem extends Rule $this->setRuleName(); } + /** + * 获取当前路由规则 + * @access public + * @return string + */ + public function getRule() + { + return $this->rule; + } + /** * 检查后缀 * @access public @@ -151,7 +161,7 @@ class RuleItem extends Rule * @param string $url 访问地址 * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 - * @return Dispatch + * @return Dispatch|false */ public function check($request, $url, $depr = '/', $completeMatch = false) { @@ -172,26 +182,11 @@ class RuleItem extends Rule $request->route($option['append']); } - // 是否区分 / 地址访问 - if (!empty($option['remove_slash']) && '/' != $this->rule) { - $this->rule = rtrim($this->rule, '/'); - $url = rtrim($url, '|'); - } - // 检查前置行为 if (isset($option['before']) && false === $this->checkBefore($option['before'])) { return false; } - if (isset($option['ext'])) { - // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); - } - - if (isset($option['complete_match'])) { - $completeMatch = $option['complete_match']; - } - return $this->checkRule($request, $url, $depr, $completeMatch, $option); } @@ -209,6 +204,21 @@ class RuleItem extends Rule { $pattern = array_merge($this->parent->getPattern(), $this->pattern); + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + if (false !== $match = $this->match($url, $pattern, $depr, $completeMatch)) { // 匹配到路由规则 return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); @@ -217,6 +227,53 @@ class RuleItem extends Rule return false; } + /** + * 检测已经匹配的路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $var 路由变量 + * @return Dispatch|false + */ + public function checkHasMatchRule($request, $url, $var = []) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $option = $this->mergeGroupOptions(); + + if (!empty($option['append'])) { + $request->route($option['append']); + } + + // 检查前置行为 + if (isset($option['before']) && false === $this->checkBefore($option['before'])) { + return false; + } + + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + // 匹配到路由规则 + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $var); + } + /** * 检测URL和规则路由是否匹配 * @access private @@ -251,33 +308,12 @@ class RuleItem extends Rule $rule = str_replace('/', $depr, $this->rule); if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { - foreach ($matches[0] as $name) { - $optional = ''; - $slash = substr($name, 0, 1); - - if (strpos($name, ']')) { - $name = substr($name, 3, -1); - $optional = '?'; - } elseif (strpos($name, '?')) { - $name = substr($name, 2, -2); - $optional = '?'; - } elseif (strpos($name, '>')) { - $name = substr($name, 2, -1); - } else { - $name = substr($name, 2); - } - - $replace[] = '([$\\' . $slash . '](?<' . $name . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; - } - - $regex = str_replace($matches[0], $replace, $rule); - $regex = str_replace([')/', ')-'], [')\/', ')\-'], $regex); + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $completeMatch); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; } - array_shift($match); foreach ($match as $key => $val) { if (is_string($key)) { $var[$key] = $val; -- Gitee From 3dcbace31132816f31a7a4569d22364c426b91ae Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Feb 2018 22:06:35 +0800 Subject: [PATCH 0920/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 8 +++++++- library/think/route/RuleGroup.php | 6 ++++-- library/think/route/RuleItem.php | 7 ++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index d61cc3d3..6687ff0c 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -925,11 +925,12 @@ abstract class Rule * @param string $rule 路由规则 * @param array $match 匹配的变量 * @param array $pattern 路由变量规则 + * @param array $option 路由参数 * @param bool $completeMatch 路由是否完全匹配 * @param string $suffix 路由正则变量后缀 * @return string */ - protected function buildRuleRegex($rule, $match, $pattern = [], $completeMatch = false, $suffix = '') + protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '') { foreach ($match as $name) { $optional = ''; @@ -950,6 +951,11 @@ abstract class Rule $replace[] = '([$\\' . $slash . '](?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; } + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $rule) { + $rule = rtrim($rule, '/'); + } + $regex = str_replace($match, $replace, $rule); $regex = str_replace([')?/', ')/', ')?-', ')-'], [')\/', ')\/', ')\-', ')\-'], $regex); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index afe21ed0..4be7ed5d 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -233,9 +233,11 @@ class RuleGroup extends Rule $rule = str_replace('/', $depr, $item->getRule()); if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { - $pattern = array_merge($this->getPattern(), $item->getPattern()); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; - $regex[] = $this->buildRuleRegex($rule, $matches[0], $pattern, $complete, '_THINK_' . $key); + $regex[] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); } } } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index a3864f58..430b4f9e 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -219,7 +219,7 @@ class RuleItem extends Rule $completeMatch = $option['complete_match']; } - if (false !== $match = $this->match($url, $pattern, $depr, $completeMatch)) { + if (false !== $match = $this->match($url, $pattern, $option, $depr, $completeMatch)) { // 匹配到路由规则 return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); } @@ -279,11 +279,12 @@ class RuleItem extends Rule * @access private * @param string $url URL地址 * @param array $pattern 变量规则 + * @param array $option 路由参数 * @param string $depr URL分隔符(全局) * @param bool $completeMatch 路由是否完全匹配 * @return array|false */ - private function match($url, $pattern, $depr, $completeMatch) + private function match($url, $pattern, $option, $depr, $completeMatch) { // 检查完整规则定义 if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { @@ -308,7 +309,7 @@ class RuleItem extends Rule $rule = str_replace('/', $depr, $this->rule); if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { - $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $completeMatch); + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { return false; -- Gitee From d5b5fcf3c2b6142c7c6058bd05bb87a1dc900c0d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 19 Feb 2018 23:09:49 +0800 Subject: [PATCH 0921/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBpat?= =?UTF-8?q?hinfo=E6=96=B9=E6=B3=95=20=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- library/think/response/View.php | 1 - library/think/route/RuleItem.php | 39 ++++++++++++++++---------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 9b3138bd..c11721cb 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -610,7 +610,7 @@ class Request } } - $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + $this->pathinfo = empty($_SERVER['PATH_INFO']) || '/' == $_SERVER['PATH_INFO'] ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); } return $this->pathinfo; diff --git a/library/think/response/View.php b/library/think/response/View.php index fc32aa56..0b4cb28e 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -64,7 +64,6 @@ class View extends Response { if (is_array($name)) { $this->vars = array_merge($this->vars, $name); - return $this; } else { $this->vars[$name] = $value; } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 430b4f9e..88b269b7 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -73,7 +73,7 @@ class RuleItem extends Rule $this->option['complete_match'] = true; } - $rule = '/' != $rule ? ltrim($rule, '/') : '/'; + $rule = ('' != $rule && '/' != $rule) ? ltrim($rule, '/') : '/'; if ($this->parent && $prefix = $this->parent->getFullName()) { $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); @@ -175,19 +175,7 @@ class RuleItem extends Rule return false; } - // 合并分组参数 - $option = $this->mergeGroupOptions(); - - if (!empty($option['append'])) { - $request->route($option['append']); - } - - // 检查前置行为 - if (isset($option['before']) && false === $this->checkBefore($option['before'])) { - return false; - } - - return $this->checkRule($request, $url, $depr, $completeMatch, $option); + return $this->checkRule($request, $url, $depr, $completeMatch); } /** @@ -197,11 +185,18 @@ class RuleItem extends Rule * @param string $url URL地址 * @param string $depr URL分隔符(全局) * @param bool $completeMatch 路由是否完全匹配 - * @param array $option 路由参数 * @return array|false */ - private function checkRule($request, $url, $depr, $completeMatch = false, $option = []) + private function checkRule($request, $url, $depr, $completeMatch = false) { + // 合并分组参数 + $option = $this->mergeGroupOptions(); + + // 检查前置行为 + if (isset($option['before']) && false === $this->checkBefore($option['before'])) { + return false; + } + $pattern = array_merge($this->parent->getPattern(), $this->pattern); // 是否区分 / 地址访问 @@ -221,6 +216,10 @@ class RuleItem extends Rule if (false !== $match = $this->match($url, $pattern, $option, $depr, $completeMatch)) { // 匹配到路由规则 + if (!empty($option['append'])) { + $request->route($option['append']); + } + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); } @@ -250,15 +249,15 @@ class RuleItem extends Rule // 合并分组参数 $option = $this->mergeGroupOptions(); - if (!empty($option['append'])) { - $request->route($option['append']); - } - // 检查前置行为 if (isset($option['before']) && false === $this->checkBefore($option['before'])) { return false; } + if (!empty($option['append'])) { + $request->route($option['append']); + } + // 是否区分 / 地址访问 if (!empty($option['remove_slash']) && '/' != $this->rule) { $this->rule = rtrim($this->rule, '/'); -- Gitee From 58c367ab1ccd50eb0ba0d332d09b7938ae1fdc74 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 20 Feb 2018 11:09:45 +0800 Subject: [PATCH 0922/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 7 +++++-- library/think/route/RuleGroup.php | 34 ++++++++++++++++++------------- library/think/route/RuleItem.php | 4 ++-- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 6687ff0c..c8864ac4 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -944,11 +944,14 @@ abstract class Rule $optional = '?'; } elseif (strpos($name, '>')) { $name = substr($name, 2, -1); - } else { + } elseif (strpos($name, ':')) { $name = substr($name, 2); + } else { + $replace[] = '\\' . $name; + continue; } - $replace[] = '([$\\' . $slash . '](?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; + $replace[] = '(\\' . $slash . '(?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; } // 是否区分 / 地址访问 diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 4be7ed5d..67dff3c9 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -226,26 +226,29 @@ class RuleGroup extends Rule */ protected function checkMergeRuleRegex($request, &$rules, $url, $depr, $completeMatch) { + $url = str_replace('|', $depr, $url); + foreach ($rules as $key => $item) { if ($item instanceof RuleItem) { - $items[$key] = $item; - unset($rules[$key]); $rule = str_replace('/', $depr, $item->getRule()); - if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { - $pattern = array_merge($this->getPattern(), $item->getPattern()); - $option = array_merge($this->getOption(), $item->getOption()); + if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { + unset($rules[$key]); + $items[$key] = $item; + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; - $regex[] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + } elseif (0 === strcasecmp($rule, $url)) { + return $item->checkHasMatchRule($request, $url); } } } - $url = str_replace('|', $depr, $url); - if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/', $url, $match)) { - + $var = []; foreach ($match as $key => $val) { if (is_string($key) && '' !== $val) { list($name, $pos) = explode('_THINK_', $key); @@ -254,13 +257,16 @@ class RuleGroup extends Rule } } - if (isset($pos)) { - $result = $items[$pos]->checkHasMatchRule($request, $url, $var); - - if (false !== $result) { - return $result; + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-'], ['/', '-'], $item), $match[0])) { + $pos = $key; + break; + } } } + + return $items[$pos]->checkHasMatchRule($request, $url, $var); } return false; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 88b269b7..52a13372 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -73,7 +73,7 @@ class RuleItem extends Rule $this->option['complete_match'] = true; } - $rule = ('' != $rule && '/' != $rule) ? ltrim($rule, '/') : '/'; + $rule = '/' != $rule ? ltrim($rule, '/') : '/'; if ($this->parent && $prefix = $this->parent->getFullName()) { $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); @@ -307,7 +307,7 @@ class RuleItem extends Rule $url = str_replace('|', $depr, $url); $rule = str_replace('/', $depr, $this->rule); - if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { -- Gitee From cba7adacc417c559b25ad21cd52d2e534eb94f1c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 20 Feb 2018 11:51:57 +0800 Subject: [PATCH 0923/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 67dff3c9..f02ace31 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -173,16 +173,6 @@ class RuleGroup extends Rule $request->route($this->option['append']); } - if (isset($rules[$url])) { - // 快速定位 - $item = $rules[$url]; - $result = $item->check($request, $url, $depr, $completeMatch); - - if (false !== $result) { - return $result; - } - } - if (!empty($this->option['merge_rule_regex'])) { // 合并路由正则规则进行路由匹配检查 $result = $this->checkMergeRuleRegex($request, $rules, $url, $depr, $completeMatch); @@ -232,6 +222,10 @@ class RuleGroup extends Rule if ($item instanceof RuleItem) { $rule = str_replace('/', $depr, $item->getRule()); + if (false === strpos($rule, ':') && false === strpos($rule, '<') && 0 === strcasecmp($rule, $url)) { + return $item->checkHasMatchRule($request, $url); + } + if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { unset($rules[$key]); $items[$key] = $item; @@ -241,8 +235,6 @@ class RuleGroup extends Rule $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); - } elseif (0 === strcasecmp($rule, $url)) { - return $item->checkHasMatchRule($request, $url); } } } -- Gitee From fd1b571cde3c3cedac6aee7d3ea5cdade392d8f6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 20 Feb 2018 12:53:45 +0800 Subject: [PATCH 0924/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E4=B8=80=E5=A4=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index c8864ac4..b2cf96de 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -960,7 +960,7 @@ abstract class Rule } $regex = str_replace($match, $replace, $rule); - $regex = str_replace([')?/', ')/', ')?-', ')-'], [')\/', ')\/', ')\-', ')\-'], $regex); + $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); return $regex . ($completeMatch ? '$' : ''); } -- Gitee From 8bc5713b874de40ec9a5bbbf3f4fc1318f0fc1d2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 20 Feb 2018 18:45:17 +0800 Subject: [PATCH 0925/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 19 ++-------------- library/think/route/Resource.php | 10 ++++++--- library/think/route/RuleGroup.php | 36 ++++++++++++++++++++++--------- library/think/route/RuleItem.php | 28 ++++++++++++------------ 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 9d9f8b0b..a5206756 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -148,11 +148,7 @@ class Route */ protected function createTopGroup(Domain $domain) { - $group = new RuleGroup($this); - // 注册分组到当前域名 - $domain->addRule($group); - - return $group; + return new RuleGroup($this, $domain); } /** @@ -470,9 +466,6 @@ class Route // 创建路由规则实例 $ruleItem = new RuleItem($this, $this->group, $name, $rule, $route, $method, $option, $pattern); - // 添加到当前分组 - $this->group->addRule($ruleItem, $method); - if (!empty($option['cross_domain'])) { $this->setCrossDomainRule($ruleItem, $method); } @@ -589,9 +582,6 @@ class Route $this->group = $parent; } - // 注册子分组 - $this->group->addRule($group); - if (!empty($option['cross_domain'])) { $this->setCrossDomainRule($group); } @@ -694,12 +684,7 @@ class Route */ public function resource($rule, $route = '', $option = [], $pattern = []) { - $resource = new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest); - - // 添加到当前分组 - $this->group->addRule($resource); - - return $resource; + return new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest); } /** diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 7072623c..ca10d2b4 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -26,17 +26,17 @@ class Resource extends RuleGroup * 架构函数 * @access public * @param Route $router 路由对象 - * @param RuleGroup $group 路由所属分组对象 + * @param RuleGroup $parent 上级对象 * @param string $name 资源名称 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @param array $rest 资源定义 */ - public function __construct(Route $router, RuleGroup $group = null, $name = '', $route = '', $option = [], $pattern = [], $rest = []) + public function __construct(Route $router, RuleGroup $parent = null, $name = '', $route = '', $option = [], $pattern = [], $rest = []) { $this->router = $router; - $this->parent = $group; + $this->parent = $parent; $this->resource = $name; $this->route = $route; $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; @@ -49,6 +49,10 @@ class Resource extends RuleGroup $this->pattern = $pattern; $this->option = $option; $this->rest = $rest; + + if ($this->parent) { + $this->parent->addRule($this); + } } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index f02ace31..f6da87dc 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -47,22 +47,26 @@ class RuleGroup extends Rule * 架构函数 * @access public * @param Route $router 路由对象 - * @param RuleGroup $group 路由所属分组对象 + * @param RuleGroup $parent 上级对象 * @param string $name 分组名称 * @param mixed $rule 分组路由 * @param array $option 路由参数 * @param array $pattern 变量规则 */ - public function __construct(Route $router, RuleGroup $group = null, $name = '', $rule = [], $option = [], $pattern = []) + public function __construct(Route $router, RuleGroup $parent = null, $name = '', $rule = [], $option = [], $pattern = []) { $this->router = $router; - $this->parent = $group; + $this->parent = $parent; $this->rule = $rule; $this->name = trim($name, '/'); $this->option = $option; $this->pattern = $pattern; $this->setFullName(); + + if ($this->parent) { + $this->parent->addRule($this); + } } /** @@ -222,19 +226,31 @@ class RuleGroup extends Rule if ($item instanceof RuleItem) { $rule = str_replace('/', $depr, $item->getRule()); - if (false === strpos($rule, ':') && false === strpos($rule, '<') && 0 === strcasecmp($rule, $url)) { - return $item->checkHasMatchRule($request, $url); + $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; + + if (false === strpos($rule, ':') && false === strpos($rule, '<')) { + if (($complete && 0 === strcasecmp($rule, $url)) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkHasMatchRule($request, $url); + } + + unset($rules[$key]); + continue; } - if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { + // 检查第一个元素 + $pos = strpos($rule, $depr); + if ($pos && 0 !== strncasecmp($rule, $url, $pos)) { unset($rules[$key]); - $items[$key] = $item; - $pattern = array_merge($this->getPattern(), $item->getPattern()); - $option = array_merge($this->getOption(), $item->getOption()); + continue; + } - $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; + if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; } } } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 52a13372..80563b3e 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -37,7 +37,7 @@ class RuleItem extends Rule * 架构函数 * @access public * @param Route $router 路由实例 - * @param RuleGroup $group 路由所属分组对象 + * @param RuleGroup $parent 上级对象 * @param string $name 路由标识 * @param string|array $rule 路由规则 * @param string $method 请求类型 @@ -45,10 +45,10 @@ class RuleItem extends Rule * @param array $option 路由参数 * @param array $pattern 变量规则 */ - public function __construct(Route $router, RuleGroup $group, $name, $rule, $route, $method = '*', $option = [], $pattern = []) + public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = []) { $this->router = $router; - $this->parent = $group; + $this->parent = $parent; $this->name = $name; $this->route = $route; $this->method = $method; @@ -56,6 +56,8 @@ class RuleItem extends Rule $this->pattern = $pattern; $this->setRule($rule); + + $this->parent->addRule($this, $method); } /** @@ -290,23 +292,23 @@ class RuleItem extends Rule return false; } - $ruleItem = explode('/', $this->rule); - $urlItem = explode('|', $url); + $var = []; + $url = str_replace('|', $depr, $url); + $rule = str_replace('/', $depr, $this->rule); - // 检查URL长度 - if (count($urlItem) < count($ruleItem) && false === strpos($this->rule, '[') && false === strpos($this->rule, '?')) { + if (false === strpos($rule, ':') && false === strpos($rule, '<')) { + if (($completeMatch && 0 === strcasecmp($rule, $url)) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $var; + } return false; } // 检查第一个元素 - if (false === strpos($ruleItem[0], ':') && false === strpos($ruleItem[0], '<') && $ruleItem[0] != $urlItem[0]) { + $pos = strpos($rule, $depr); + if ($pos && 0 !== strncasecmp($rule, $url, $pos)) { return false; } - $var = []; - $url = str_replace('|', $depr, $url); - $rule = str_replace('/', $depr, $this->rule); - if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); @@ -319,8 +321,6 @@ class RuleItem extends Rule $var[$key] = $val; } } - } elseif (0 !== strcasecmp($this->rule, $url)) { - return false; } // 成功匹配后返回URL中的动态变量数组 -- Gitee From f50658105119926f4f9bf6200fc229752de43f5a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 20 Feb 2018 20:06:23 +0800 Subject: [PATCH 0926/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 10 +++++----- library/think/route/RuleItem.php | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index f6da87dc..44a9086d 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -237,11 +237,11 @@ class RuleGroup extends Rule continue; } - // 检查第一个元素 - $pos = strpos($rule, $depr); - if ($pos && 0 !== strncasecmp($rule, $url, $pos)) { - unset($rules[$key]); - continue; + if ($matchRule = preg_split('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } } if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 80563b3e..f028d0e4 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -304,8 +304,7 @@ class RuleItem extends Rule } // 检查第一个元素 - $pos = strpos($rule, $depr); - if ($pos && 0 !== strncasecmp($rule, $url, $pos)) { + if ($pos = strpos($rule, $depr) && 0 !== strncasecmp($rule, $url, $pos)) { return false; } -- Gitee From e26b2f54a09019c2951af3bbf3d3501c6734b5d9 Mon Sep 17 00:00:00 2001 From: ThinkPHP Date: Tue, 20 Feb 2018 12:06:48 +0000 Subject: [PATCH 0927/1384] Apply fixes from StyleCI --- library/think/Debug.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Debug.php b/library/think/Debug.php index 21dd87be..75003770 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -221,7 +221,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); + echo($output); return; } return $output; -- Gitee From 977960b1429876dcbc2f1e519dc01f333ad3ae13 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Feb 2018 12:02:10 +0800 Subject: [PATCH 0928/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=BC=80=E5=A4=B4=E4=BD=BF=E7=94=A8=E5=8F=98=E9=87=8F=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 2 +- library/think/route/RuleGroup.php | 4 ++-- library/think/route/RuleItem.php | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index b2cf96de..27bc4f82 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -959,7 +959,7 @@ abstract class Rule $rule = rtrim($rule, '/'); } - $regex = str_replace($match, $replace, $rule); + $regex = str_replace($match, $replace, '/' . ltrim($rule, '/')); $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); return $regex . ($completeMatch ? '$' : ''); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 44a9086d..26e0fa5f 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -220,11 +220,11 @@ class RuleGroup extends Rule */ protected function checkMergeRuleRegex($request, &$rules, $url, $depr, $completeMatch) { - $url = str_replace('|', $depr, $url); + $url = $depr . str_replace('|', $depr, $url); foreach ($rules as $key => $item) { if ($item instanceof RuleItem) { - $rule = str_replace('/', $depr, $item->getRule()); + $rule = $depr . str_replace('/', $depr, $item->getRule()); $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index f028d0e4..47e42966 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -293,8 +293,8 @@ class RuleItem extends Rule } $var = []; - $url = str_replace('|', $depr, $url); - $rule = str_replace('/', $depr, $this->rule); + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); if (false === strpos($rule, ':') && false === strpos($rule, '<')) { if (($completeMatch && 0 === strcasecmp($rule, $url)) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { @@ -303,9 +303,10 @@ class RuleItem extends Rule return false; } - // 检查第一个元素 - if ($pos = strpos($rule, $depr) && 0 !== strncasecmp($rule, $url, $pos)) { - return false; + if ($matchRule = preg_split('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + return false; + } } if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { -- Gitee From 66d20b86ffdc976d72e65ea5e8b88fc2cf0707a4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Feb 2018 09:14:35 +0800 Subject: [PATCH 0929/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 27bc4f82..ba86a770 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -743,10 +743,10 @@ abstract class Rule $result = $this->dispatchMethod($route); } elseif (0 === strpos($route, '@')) { // 路由到控制器 - $result = $this->dispatchController(substr($route, 1)); + $result = $this->dispatchController($request, substr($route, 1)); } else { // 路由到模块/控制器/操作 - $result = $this->dispatchModule($route); + $result = $this->dispatchModule($request, $route); } return $result; @@ -771,10 +771,11 @@ abstract class Rule /** * 解析URL地址为 模块/控制器/操作 * @access protected + * @param Request $request Request对象 * @param string $route 路由地址 * @return ControllerDispatch */ - protected function dispatchController($route) + protected function dispatchController($request, $route) { list($route, $var) = $this->parseUrlPath($route); @@ -792,15 +793,15 @@ abstract class Rule /** * 解析URL地址为 模块/控制器/操作 * @access protected + * @param Request $request Request对象 * @param string $route 路由地址 * @return ModuleDispatch */ - protected function dispatchModule($route) + protected function dispatchModule($request, $route) { list($path, $var) = $this->parseUrlPath($route); $config = Container::get('config'); - $request = Container::get('request'); $action = array_pop($path); $controller = !empty($path) ? array_pop($path) : null; $module = $config->get('app_multi_module') && !empty($path) ? array_pop($path) : null; -- Gitee From bc7098d39c2f89ae91c6253b4834191bd70455ab Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Feb 2018 15:25:57 +0800 Subject: [PATCH 0930/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=20make:controller?= =?UTF-8?q?=20--api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../think/console/command/make/Controller.php | 5 ++ .../command/make/stubs/controller.api.stub | 64 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 library/think/console/command/make/stubs/controller.api.stub diff --git a/library/think/console/command/make/Controller.php b/library/think/console/command/make/Controller.php index d702bd1a..511958e3 100644 --- a/library/think/console/command/make/Controller.php +++ b/library/think/console/command/make/Controller.php @@ -24,12 +24,17 @@ class Controller extends Make { 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('api')) { + return __DIR__ . '/stubs/controller.api.stub'; + } + if ($this->input->getOption('plain')) { return __DIR__ . '/stubs/controller.plain.stub'; } diff --git a/library/think/console/command/make/stubs/controller.api.stub b/library/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 00000000..aed9edfb --- /dev/null +++ b/library/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ + Date: Thu, 22 Feb 2018 21:55:53 +0800 Subject: [PATCH 0931/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=96=E6=B6=88createTopGroup=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 15 ++------------- library/think/route/RuleGroup.php | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index a5206756..5c5bacc0 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -137,18 +137,7 @@ class Route $this->domains[$this->host] = $domain; // 默认分组 - $this->group = $this->createTopGroup($domain); - } - - /** - * 创建一个域名下的顶级路由分组 - * @access protected - * @param Domain $domain 域名 - * @return RuleGroup - */ - protected function createTopGroup(Domain $domain) - { - return new RuleGroup($this, $domain); + $this->group = $domain; } /** @@ -227,7 +216,7 @@ class Route $originGroup = $this->group; // 设置当前域名 $this->domain = $domainName; - $this->group = $this->createTopGroup($domain); + $this->group = $domain; // 解析域名路由规则 $this->parseGroupRule($domain, $rule); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 26e0fa5f..a38e4dab 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -331,7 +331,7 @@ class RuleGroup extends Rule */ public function prefix($prefix) { - if ($this->parent->getOption('prefix')) { + if ($this->parent && $this->parent->getOption('prefix')) { $prefix = $this->parent->getOption('prefix') . $prefix; } -- Gitee From 792c23ca58e3463150d751318c96fbc688de3a45 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 23 Feb 2018 17:45:10 +0800 Subject: [PATCH 0932/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base.php | 1 + library/think/App.php | 1 + library/think/Route.php | 225 +++--------------- .../think/console/command/optimize/Route.php | 6 +- library/think/route/Domain.php | 14 +- library/think/route/Resource.php | 28 +-- library/think/route/Rule.php | 36 ++- library/think/route/RuleGroup.php | 103 ++++++-- library/think/route/RuleItem.php | 21 +- library/think/route/RuleName.php | 63 +++++ 10 files changed, 255 insertions(+), 243 deletions(-) create mode 100644 library/think/route/RuleName.php diff --git a/base.php b/base.php index fc8b7b13..deb5840b 100644 --- a/base.php +++ b/base.php @@ -47,6 +47,7 @@ Container::getInstance()->bind([ 'url' => Url::class, 'validate' => Validate::class, 'view' => View::class, + 'rule_name' => route\RuleName::class, 'middlewareDispatcher' => http\middleware\Dispatcher::class, // 接口依赖注入 'think\LoggerInterface' => Log::class, diff --git a/library/think/App.php b/library/think/App.php index 49a33c1f..9b3f7290 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -335,6 +335,7 @@ class App implements \ArrayAccess $dispatch = $this->dispatch; if (empty($dispatch)) { // 进行URL路由检测 + $this->route->lazy($this->config('app.url_lazy_route')); $dispatch = $this->routeCheck(); } diff --git a/library/think/Route.php b/library/think/Route.php index 5c5bacc0..87e80827 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -76,12 +76,6 @@ class Route */ protected $group; - /** - * 路由标识 - * @var array - */ - protected $name = []; - /** * 路由绑定 * @var array @@ -100,18 +94,18 @@ class Route */ protected $cross; - /** - * 当前路由标识 - * @var string - */ - protected $ruleName; - /** * 路由别名 * @var array */ protected $alias = []; + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = true; + public function __construct(Request $request, Config $config) { $this->config = $config; @@ -121,6 +115,18 @@ class Route $this->setDefaultDomain(); } + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + $this->lazy = $lazy; + return $this; + } + /** * 初始化默认域名 * @access protected @@ -207,25 +213,8 @@ class Route $domainName .= '.' . $this->request->rootDomain(); } - $route = $this->config->get('url_lazy_route') ? $rule : null; - - $domain = new Domain($this, $domainName, $route, $option, $pattern); - - if (is_null($route)) { - // 获取原始分组 - $originGroup = $this->group; - // 设置当前域名 - $this->domain = $domainName; - $this->group = $domain; - - // 解析域名路由规则 - $this->parseGroupRule($domain, $rule); - - // 还原默认域名 - $this->domain = $this->host; - // 还原默认分组 - $this->group = $originGroup; - } + $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) + ->lazy($this->lazy); $this->domains[$domainName] = $domain; @@ -244,32 +233,6 @@ class Route return $domain; } - /** - * 解析分组和域名的路由规则及绑定 - * @access public - * @param RuleGroup $group 分组路由对象 - * @param mixed $rule 路由规则 - * @return void - */ - public function parseGroupRule($group, $rule) - { - if ($rule instanceof \Closure) { - Container::getInstance()->invokeFunction($rule); - } elseif ($rule instanceof Response) { - $group->setRule($rule); - } elseif (is_array($rule)) { - $this->rules($rule); - } elseif ($rule) { - if (false !== strpos($rule, '?')) { - list($rule, $query) = explode('?', $rule); - parse_str($query, $vars); - $group->append($vars); - } - - $this->bind($rule); - } - } - /** * 获取域名 * @access public @@ -284,10 +247,13 @@ class Route * 设置路由绑定 * @access public * @param string $bind 绑定信息 + * @param string $domain 域名 * @return $this */ - public function bind($bind) + public function bind($bind, $domain = null) { + $domain = is_null($domain) ? $this->domain : $domain; + $this->bind[$this->domain] = $bind; return $this; @@ -324,19 +290,6 @@ class Route return $result; } - /** - * 设置当前路由标识 - * @access public - * @param string $name 路由命名标识 - * @return $this - */ - public function name($name) - { - $this->ruleName = $name; - - return $this; - } - /** * 读取路由标识 * @access public @@ -345,13 +298,7 @@ class Route */ public function getName($name = null) { - if (is_null($name)) { - return $this->name; - } - - $name = strtolower($name); - - return isset($this->name[$name]) ? $this->name[$name] : null; + return Container::get('rule_name')->get($name); } /** @@ -362,7 +309,7 @@ class Route */ public function setName($name) { - $this->name = $name; + Container::get('rule_name')->import($name); return $this; } @@ -436,61 +383,7 @@ class Route */ public function rule($rule, $route, $method = '*', $option = [], $pattern = []) { - // 读取路由标识 - if (is_array($rule)) { - $name = $rule[0]; - $rule = $rule[1]; - } elseif ($this->ruleName) { - $name = $this->ruleName; - - $this->ruleName = null; - } elseif (is_string($route)) { - $name = $route; - } else { - $name = null; - } - - $method = strtolower($method); - - // 创建路由规则实例 - $ruleItem = new RuleItem($this, $this->group, $name, $rule, $route, $method, $option, $pattern); - - if (!empty($option['cross_domain'])) { - $this->setCrossDomainRule($ruleItem, $method); - } - - return $ruleItem; - } - - /** - * 设置路由标识 用于URL反解生成 - * @access public - * @param string $rule 路由规则 - * @param string $name 路由标识 - * @param array $option 路由参数 - * @param bool $first 是否插入开头 - * @return void - */ - public function setRuleName($rule, $name, $option = [], $first = false) - { - $vars = $this->parseVar($rule); - $name = strtolower($name); - - if (isset($option['ext'])) { - $suffix = $option['ext']; - } elseif ($this->group->getOption('ext')) { - $suffix = $this->group->getOption('ext'); - } else { - $suffix = null; - } - - $item = [$rule, $vars, $this->domain, $suffix]; - - if ($first) { - array_unshift($this->name[$name], $item); - } else { - $this->name[$name][] = $item; - } + return $this->group->addRule($rule, $route, $method, $option, $pattern); } /** @@ -506,7 +399,7 @@ class Route $this->cross = new RuleGroup($this); } - $this->cross->addRule($rule, $method); + $this->cross->addRuleItem($rule, $method); return $this; } @@ -555,27 +448,8 @@ class Route $name = isset($option['name']) ? $option['name'] : ''; } - // 创建分组实例 - $rule = $this->config->get('url_lazy_route') ? $route : null; - $group = new RuleGroup($this, $this->group, $name, $rule, $option, $pattern); - - if (is_null($rule)) { - // 解析分组路由 - $parent = $this->getGroup(); - - $this->group = $group; - - // 解析分组路由规则 - $this->parseGroupRule($group, $route); - - $this->group = $parent; - } - - if (!empty($option['cross_domain'])) { - $this->setCrossDomainRule($group); - } - - return $group; + return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern)) + ->lazy($this->lazy); } /** @@ -673,7 +547,8 @@ class Route */ public function resource($rule, $route = '', $option = [], $pattern = []) { - return new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest); + return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest)) + ->lazy($this->lazy); } /** @@ -691,7 +566,7 @@ class Route foreach ($this->methodPrefix as $type => $val) { $item = $this->$type(':action', $val . ':action'); - $group->addRule($item, $type); + $group->addRuleItem($item, $type); } return $group->prefix($route . '/'); @@ -939,40 +814,6 @@ class Route return $item; } - /** - * 分析路由规则中的变量 - * @access public - * @param string $rule 路由规则 - * @return array - */ - public function parseVar($rule) - { - // 提取路由规则中的变量 - $var = []; - - if (preg_match_all('/(?:<\w+\??>|\[?\:\w+\]?)/', $rule, $matches)) { - foreach ($matches[0] as $name) { - $optional = false; - - if (strpos($name, ']')) { - $name = substr($name, 2, -1); - $optional = true; - } elseif (strpos($name, '?')) { - $name = substr($name, 1, -2); - $optional = true; - } elseif (strpos($name, '>')) { - $name = substr($name, 1, -1); - } else { - $name = substr($name, 1); - } - - $var[$name] = $optional ? 2 : 1; - } - } - - return $var; - } - /** * 设置全局的路由分组参数 * @access public diff --git a/library/think/console/command/optimize/Route.php b/library/think/console/command/optimize/Route.php index 0b188583..d5f06d00 100644 --- a/library/think/console/command/optimize/Route.php +++ b/library/think/console/command/optimize/Route.php @@ -28,14 +28,16 @@ class Route extends Command protected function execute(Input $input, Output $output) { - file_put_contents(Container::get('app')->getRuntimePath() . 'route.php', $this->buildRouteCache()); + $filename = Container::get('app')->getRuntimePath() . 'route.php'; + unlink($filename); + file_put_contents($filename, $this->buildRouteCache()); $output->writeln('Succeed!'); } protected function buildRouteCache() { Container::get('route')->setName([]); - Container::get('config')->set('url_lazy_route', false); + Container::get('route')->lazy(false); // 路由检测 $path = Container::get('app')->getRoutePath(); diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index d8f3e4f2..2dae4006 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -26,7 +26,7 @@ class Domain extends RuleGroup * 架构函数 * @access public * @param Route $router 路由对象 - * @param string $name 分组名称 + * @param string $name 路由域名 * @param mixed $rule 域名路由 * @param array $option 路由参数 * @param array $pattern 变量规则 @@ -34,7 +34,7 @@ class Domain extends RuleGroup public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = []) { $this->router = $router; - $this->name = trim($name, '/'); + $this->domain = $name; $this->option = $option; $this->rule = $rule; $this->pattern = $pattern; @@ -52,18 +52,12 @@ class Domain extends RuleGroup public function check($request, $url, $depr = '/', $completeMatch = false) { if ($this->rule) { - // 延迟解析域名路由 + // 解析域名路由 if ($this->rule instanceof Response) { return new ResponseDispatch($this->rule); } - $group = new RuleGroup($this->router); - - $this->addRule($group); - - $this->router->setGroup($group); - - $this->router->parseGroupRule($this, $this->rule); + $this->parseGroupRule(); $this->rule = null; } diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index ca10d2b4..51f60e4a 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -51,25 +51,24 @@ class Resource extends RuleGroup $this->rest = $rest; if ($this->parent) { - $this->parent->addRule($this); + $this->parent->addRuleItem($this); } } /** - * 检测分组路由 - * @access public - * @param Request $request 请求对象 - * @param string $url 访问地址 - * @param string $depr 路径分隔符 - * @param bool $completeMatch 路由是否完全匹配 - * @return Dispatch + * 解析资源路由规则 + * @access protected + * @return void */ - public function check($request, $url, $depr = '/', $completeMatch = false) + protected function parseGroupRule() { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + // 生成资源路由的路由规则 $this->buildResourceRule($this->resource, $this->option); - return parent::check($request, $url, $depr, $completeMatch); + $this->router->setGroup($origin); } /** @@ -94,11 +93,6 @@ class Resource extends RuleGroup $rule = implode('/', $item) . '/' . $last; } - // 注册分组 - $group = $this->router->getGroup(); - - $this->router->setGroup($this); - // 注册资源路由 foreach ($this->rest as $key => $val) { if ((isset($option['only']) && !in_array($key, $option['only'])) @@ -114,10 +108,8 @@ class Resource extends RuleGroup $option['rest'] = $key; - $this->router->rule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); + $this->addRule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); } - - $this->router->setGroup($group); } /** diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index ba86a770..23dffe86 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -416,7 +416,7 @@ abstract class Rule } if ($allow && $this->parent) { - $this->parent->addRule($this, 'options'); + $this->parent->addRuleItem($this, 'options'); } return $this->option('cross_domain', $allow); @@ -966,6 +966,40 @@ abstract class Rule return $regex . ($completeMatch ? '$' : ''); } + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/(?:<\w+\??>|\[?\:\w+\]?)/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, ']')) { + $name = substr($name, 2, -1); + $optional = true; + } elseif (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } else { + $name = substr($name, 1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + /** * 设置路由参数 * @access public diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index a38e4dab..fc1198d0 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -43,6 +43,9 @@ class RuleGroup extends Rule // 完整名称 protected $fullName; + // 所在域名 + protected $domain; + /** * 架构函数 * @access public @@ -65,7 +68,12 @@ class RuleGroup extends Rule $this->setFullName(); if ($this->parent) { - $this->parent->addRule($this); + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this); } } @@ -84,15 +92,13 @@ class RuleGroup extends Rule } /** - * 设置分组的路由规则 + * 获取所属域名 * @access public - * @param mixed $rule 路由规则 - * @return $this + * @return string */ - public function setRule($rule) + public function getDomain() { - $this->rule = $rule; - return $this; + return $this->domain; } /** @@ -131,21 +137,13 @@ class RuleGroup extends Rule } } + // 解析分组路由 if ($this->rule) { - // 延迟解析分组路由 if ($this->rule instanceof Response) { return new ResponseDispatch($this->rule); } - $group = $this->router->getGroup(); - - $this->router->setGroup($this); - - $this->router->parseGroupRule($this, $this->rule); - - $this->router->setGroup($group); - - $this->rule = null; + $this->parseGroupRule(); } // 分组匹配后执行的行为 @@ -208,6 +206,49 @@ class RuleGroup extends Rule return $result; } + /** + * 延迟解析分组的路由规则 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + if (!$lazy) { + $this->parseGroupRule(); + } + + return $this; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access protected + * @return void + */ + protected function parseGroupRule() + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($this->rule instanceof \Closure) { + Container::getInstance()->invokeFunction($this->rule); + } elseif (is_array($this->rule)) { + $this->router->rules($this->rule); + } elseif ($this->rule) { + if (false !== strpos($this->rule, '?')) { + list($rule, $query) = explode('?', $this->rule); + parse_str($query, $vars); + $this->append($vars); + $this->rule = $rule; + } + + $this->router->bind($this->rule, $this->domain); + } + + $this->router->setGroup($origin); + } + /** * 检测分组路由 * @access public @@ -311,7 +352,33 @@ class RuleGroup extends Rule * @param string $method 请求类型 * @return $this */ - public function addRule($rule, $method = '*') + public function addRule($rule, $route, $method = '*', $option = [], $pattern = []) + { + // 读取路由标识 + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } else { + $name = null; + } + + $method = strtolower($method); + + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern); + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($ruleItem, $method); + } + + $this->addRuleItem($ruleItem, $method); + + return $ruleItem; + } + + public function addRuleItem($rule, $method = '*') { if (strpos($method, '|')) { $rule->method($method); diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 47e42966..8a4b6ed2 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -11,6 +11,7 @@ namespace think\route; +use think\Container; use think\Route; class RuleItem extends Rule @@ -57,7 +58,10 @@ class RuleItem extends Rule $this->setRule($rule); - $this->parent->addRule($this, $method); + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this, $method); + } + } /** @@ -152,7 +156,20 @@ class RuleItem extends Rule protected function setRuleName($first = false) { if ($this->name) { - $this->router->setRuleName($this->rule, $this->name, $this->option, $first); + $vars = $this->parseVar($this->rule); + $name = strtolower($this->name); + + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix]; + + Container::get('rule_name')->set($name, $value, $first); } } diff --git a/library/think/route/RuleName.php b/library/think/route/RuleName.php new file mode 100644 index 00000000..460ccab3 --- /dev/null +++ b/library/think/route/RuleName.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +class RuleName +{ + protected $item = []; + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param array $value 路由规则 + * @param bool $first 是否置顶 + * @return void + */ + public function set($name, $value, $first = false) + { + if ($first) { + array_unshift($this->item[$name], $value); + } else { + $this->item[$name][] = $value; + } + } + + /** + * 导入路由标识 + * @access public + * @param array $name 路由标识 + * @return void + */ + public function import($item) + { + $this->item = $item; + } + + /** + * 根据路由标识获取路由信息(用于URL生成) + * @access public + * @param string $name 路由标识 + * @return array|null + */ + public function get($name = null) + { + if (is_null($name)) { + return $this->item; + } + + $name = strtolower($name); + + return isset($this->item[$name]) ? $this->item[$name] : null; + } + +} -- Gitee From 9399c16703e8e8519a61008bcb06b2ed0037da2d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 23 Feb 2018 18:50:13 +0800 Subject: [PATCH 0933/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3url=E5=88=86?= =?UTF-8?q?=E9=9A=94=E7=AC=A6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 2 +- library/think/route/RuleGroup.php | 8 +++++--- library/think/route/RuleItem.php | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 23dffe86..c25f99c3 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -960,7 +960,7 @@ abstract class Rule $rule = rtrim($rule, '/'); } - $regex = str_replace($match, $replace, '/' . ltrim($rule, '/')); + $regex = str_replace($match, $replace, $rule); $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); return $regex . ($completeMatch ? '$' : ''); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index fc1198d0..6d54a365 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -278,14 +278,16 @@ class RuleGroup extends Rule continue; } - if ($matchRule = preg_split('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, 2)) { + $slash = implode('', array_unique(['\/', '\-', '\\' . $depr])); + + if ($matchRule = preg_split('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:\w+\]?)/', $rule, 2)) { if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { unset($rules[$key]); continue; } } - if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:?\w+\]?)/', $rule, $matches)) { unset($rules[$key]); $pattern = array_merge($this->getPattern(), $item->getPattern()); $option = array_merge($this->getOption(), $item->getOption()); @@ -308,7 +310,7 @@ class RuleGroup extends Rule if (!isset($pos)) { foreach ($regex as $key => $item) { - if (0 === strpos(str_replace(['\/', '\-'], ['/', '-'], $item), $match[0])) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { $pos = $key; break; } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 8a4b6ed2..5ae01fe1 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -320,13 +320,15 @@ class RuleItem extends Rule return false; } - if ($matchRule = preg_split('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:\w+\]?)/', $rule, 2)) { + $slash = implode('', array_unique(['\/', '\-', '\\' . $depr])); + + if ($matchRule = preg_split('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:\w+\]?)/', $rule, 2)) { if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { return false; } } - if (preg_match_all('/(?:[\/\-]<\w+\??>|[\/\-]\[?\:?\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:?\w+\]?)/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { -- Gitee From c0e3ed038515bed89a735cf33ad4190053952890 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 23 Feb 2018 21:15:30 +0800 Subject: [PATCH 0934/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96Template=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/library/think/Template.php b/library/think/Template.php index 9956c85a..4061507f 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -91,10 +91,10 @@ class Template $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; $this->config['taglib_end_origin'] = $this->config['taglib_end']; - $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); - $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); - $this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']); - $this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']); + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); // 初始化模板编译存储器 $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; @@ -103,20 +103,6 @@ class Template $this->storage = new $class(); } - /** - * 字符串替换 避免正则混淆 - * @access private - * @param string $str - * @return string - */ - private function stripPreg($str) - { - return str_replace( - ['{', '}', '(', ')', '|', '[', ']', '-', '+', '*', '.', '^', '?'], - ['\{', '\}', '\(', '\)', '\|', '\[', '\]', '\-', '\+', '\*', '\.', '\^', '\?'], - $str); - } - /** * 模板变量赋值 * @access public -- Gitee From efd77f0224daefe7c071615ed33add5df0f044b1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 23 Feb 2018 23:04:10 +0800 Subject: [PATCH 0935/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 16 +--------------- library/think/route/Domain.php | 14 ++++++++------ library/think/route/RuleGroup.php | 30 +++++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 87e80827..6f803535 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -415,21 +415,7 @@ class Route */ public function rules($rules, $method = '*', $option = [], $pattern = []) { - foreach ($rules as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - - if (is_array($val)) { - $route = array_shift($val); - $option = $val ? array_shift($val) : []; - $pattern = $val ? array_shift($val) : []; - } else { - $route = $val; - } - - $this->rule($key, $route, $method, $option, $pattern); - } + $this->group->addRules($rules, $method, $option, $pattern); } /** diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 2dae4006..cef355d6 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -63,12 +63,10 @@ class Domain extends RuleGroup } // 检测别名路由 - if ($this->router->getAlias($url) || $this->router->getAlias(strstr($url, '|', true))) { - // 检测路由别名 - $result = $this->checkRouteAlias($request, $url, $depr); - if (false !== $result) { - return $result; - } + $result = $this->checkRouteAlias($request, $url, $depr); + + if (false !== $result) { + return $result; } // 检测URL绑定 @@ -95,6 +93,10 @@ class Domain extends RuleGroup $alias = array_shift($array); $item = $this->router->getAlias($alias); + if (is_null($item)) { + return false; + } + if (is_array($item)) { list($rule, $option) = $item; $action = $array[0]; diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 6d54a365..74681235 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -234,7 +234,7 @@ class RuleGroup extends Rule if ($this->rule instanceof \Closure) { Container::getInstance()->invokeFunction($this->rule); } elseif (is_array($this->rule)) { - $this->router->rules($this->rule); + $this->addRules($this->rule); } elseif ($this->rule) { if (false !== strpos($this->rule, '?')) { list($rule, $query) = explode('?', $this->rule); @@ -380,6 +380,34 @@ class RuleGroup extends Rule return $ruleItem; } + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function addRules($rules, $method = '*', $option = [], $pattern = []) + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (is_array($val)) { + $route = array_shift($val); + $option = $val ? array_shift($val) : []; + $pattern = $val ? array_shift($val) : []; + } else { + $route = $val; + } + + $this->addRule($key, $route, $method, $option, $pattern); + } + } + public function addRuleItem($rule, $method = '*') { if (strpos($method, '|')) { -- Gitee From 112e8e2a6c04df6124ce0baf4eef0d9e7551d531 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 23 Feb 2018 23:41:16 +0800 Subject: [PATCH 0936/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- library/think/route/RuleItem.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index c11721cb..6ac15d78 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -610,7 +610,7 @@ class Request } } - $this->pathinfo = empty($_SERVER['PATH_INFO']) || '/' == $_SERVER['PATH_INFO'] ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + $this->pathinfo = empty($_SERVER['PATH_INFO']) || '/' == $_SERVER['PATH_INFO'] ? '' : ltrim($_SERVER['PATH_INFO'], '/'); } return $this->pathinfo; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 5ae01fe1..1e844147 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -79,7 +79,7 @@ class RuleItem extends Rule $this->option['complete_match'] = true; } - $rule = '/' != $rule ? ltrim($rule, '/') : '/'; + $rule = '/' != $rule ? ltrim($rule, '/') : ''; if ($this->parent && $prefix = $this->parent->getFullName()) { $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); -- Gitee From 146b138d0775b534d1b01dda9230a7d466ad89c9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Feb 2018 11:39:51 +0800 Subject: [PATCH 0937/1384] =?UTF-8?q?=E5=AE=B9=E5=99=A8=E7=B1=BB=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=88=A0=E9=99=A4=E5=92=8C=E6=B8=85=E9=99=A4=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=AE=9E=E4=BE=8B=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/library/think/Container.php b/library/think/Container.php index 9181989d..32a95746 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -76,6 +76,27 @@ class Container return static::getInstance()->bind($abstract, $concrete); } + /** + * 移除容器中的对象实例 + * @access public + * @param string $abstract 类标识、接口 + * @return void + */ + public static function remove($abstract) + { + return static::getInstance()->delete($abstract); + } + + /** + * 清除容器中的对象实例 + * @access public + * @return void + */ + public static function clear() + { + return static::getInstance()->flush(); + } + /** * 绑定一个类、闭包、实例、接口实现到容器 * @access public @@ -177,6 +198,30 @@ class Container return $object; } + /** + * 删除容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return void + */ + public function delete($abstract) + { + if (isset($this->instances[$abstract])) { + unset($this->instances[$abstract]); + } + } + + /** + * 清除容器中的对象实例 + * @access public + * @return void + */ + public function flush() + { + $this->instances = []; + $this->bind = []; + } + /** * 执行函数或者闭包方法 支持参数调用 * @access public -- Gitee From bec7c3b878d5c1c4509e4858991959b99bb27850 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Feb 2018 16:23:24 +0800 Subject: [PATCH 0938/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=AF=B9=E8=BF=9E=E7=BB=AD=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=20=E6=94=B9=E8=BF=9B=E5=88=AB?= =?UTF-8?q?=E5=90=8D=E8=B7=AF=E7=94=B1=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 65 +++++++++---------- library/think/route/AliasRule.php | 104 ++++++++++++++++++++++++++++++ library/think/route/Domain.php | 39 +---------- library/think/route/Rule.php | 27 +++++--- library/think/route/RuleGroup.php | 3 +- library/think/route/RuleItem.php | 2 +- 6 files changed, 158 insertions(+), 82 deletions(-) create mode 100644 library/think/route/AliasRule.php diff --git a/library/think/Route.php b/library/think/Route.php index 6f803535..3403b845 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -12,6 +12,7 @@ namespace think; use think\exception\RouteNotFoundException; +use think\route\AliasRule; use think\route\dispatch\Url as UrlDispatch; use think\route\Domain; use think\route\Resource; @@ -71,8 +72,8 @@ class Route protected $domain; /** - * 当前分组 - * @var string + * 当前分组对象 + * @var RuleGroup */ protected $group; @@ -381,7 +382,7 @@ class Route * @param array $pattern 变量规则 * @return RuleItem */ - public function rule($rule, $route, $method = '*', $option = [], $pattern = []) + public function rule($rule, $route, $method = '*', array $option = [], array $pattern = []) { return $this->group->addRule($rule, $route, $method, $option, $pattern); } @@ -413,7 +414,7 @@ class Route * @param array $pattern 变量规则 * @return void */ - public function rules($rules, $method = '*', $option = [], $pattern = []) + public function rules($rules, $method = '*', array $option = [], array $pattern = []) { $this->group->addRules($rules, $method, $option, $pattern); } @@ -427,7 +428,7 @@ class Route * @param array $pattern 变量规则 * @return RuleGroup */ - public function group($name, $route, $option = [], $pattern = []) + public function group($name, $route, array $option = [], array $pattern = []) { if (is_array($name)) { $option = $name; @@ -442,12 +443,12 @@ class Route * 注册路由 * @access public * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param mixed $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return RuleItem */ - public function any($rule, $route = '', $option = [], $pattern = []) + public function any($rule, $route = '', array $option = [], array $pattern = []) { return $this->rule($rule, $route, '*', $option, $pattern); } @@ -456,12 +457,12 @@ class Route * 注册GET路由 * @access public * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param mixed $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return RuleItem */ - public function get($rule, $route = '', $option = [], $pattern = []) + public function get($rule, $route = '', array $option = [], array $pattern = []) { return $this->rule($rule, $route, 'GET', $option, $pattern); } @@ -470,12 +471,12 @@ class Route * 注册POST路由 * @access public * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param mixed $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return RuleItem */ - public function post($rule, $route = '', $option = [], $pattern = []) + public function post($rule, $route = '', array $option = [], array $pattern = []) { return $this->rule($rule, $route, 'POST', $option, $pattern); } @@ -484,12 +485,12 @@ class Route * 注册PUT路由 * @access public * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param mixed $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return RuleItem */ - public function put($rule, $route = '', $option = [], $pattern = []) + public function put($rule, $route = '', array $option = [], array $pattern = []) { return $this->rule($rule, $route, 'PUT', $option, $pattern); } @@ -498,12 +499,12 @@ class Route * 注册DELETE路由 * @access public * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param mixed $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return RuleItem */ - public function delete($rule, $route = '', $option = [], $pattern = []) + public function delete($rule, $route = '', array $option = [], array $pattern = []) { return $this->rule($rule, $route, 'DELETE', $option, $pattern); } @@ -512,12 +513,12 @@ class Route * 注册PATCH路由 * @access public * @param string $rule 路由规则 - * @param string $route 路由地址 + * @param mixed $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return RuleItem */ - public function patch($rule, $route = '', $option = [], $pattern = []) + public function patch($rule, $route = '', array $option = [], array $pattern = []) { return $this->rule($rule, $route, 'PATCH', $option, $pattern); } @@ -531,7 +532,7 @@ class Route * @param array $pattern 变量规则 * @return Resource */ - public function resource($rule, $route = '', $option = [], $pattern = []) + public function resource($rule, $route = '', array $option = [], array $pattern = []) { return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest)) ->lazy($this->lazy); @@ -546,7 +547,7 @@ class Route * @param array $pattern 变量规则 * @return RuleGroup */ - public function controller($rule, $route = '', $option = [], $pattern = []) + public function controller($rule, $route = '', array $option = [], array $pattern = []) { $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); @@ -568,7 +569,7 @@ class Route * @param array $pattern 变量规则 * @return RuleItem */ - public function view($rule, $template = '', $vars = [], $option = [], $pattern = []) + public function view($rule, $template = '', array $vars = [], array $option = [], array $pattern = []) { return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); } @@ -583,7 +584,7 @@ class Route * @param array $pattern 变量规则 * @return RuleItem */ - public function redirect($rule, $route = '', $status = 301, $option = [], $pattern = []) + public function redirect($rule, $route = '', $status = 301, array $option = [], array $pattern = []) { return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status); } @@ -591,20 +592,18 @@ class Route /** * 注册别名路由 * @access public - * @param string|array $rule 路由别名 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @return $this + * @param string $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return AliasRule */ - public function alias($rule = null, $route = '', $option = []) + public function alias($rule, $route, array $option = []) { - if (is_array($rule)) { - $this->alias = array_merge($this->alias, $rule); - } else { - $this->alias[$rule] = $option ? [$route, $option] : $route; - } + $aliasRule = new AliasRule($this, $this->group, $rule, $route, $option); - return $this; + $this->alias[$rule] = $aliasRule; + + return $aliasRule; } /** @@ -695,7 +694,7 @@ class Route * @param array $option 路由参数 * @return RuleItem */ - public function miss($route, $method = '*', $option = []) + public function miss($route, $method = '*', array $option = []) { return $this->rule('', $route, $method, $option)->isMiss(); } diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php new file mode 100644 index 00000000..c4092449 --- /dev/null +++ b/library/think/route/AliasRule.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +class AliasRule extends Domain +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string|array $rule 路由规则 + * @param string|\Closure $route 路由地址 + * @param array $option 路由参数 + */ + public function __construct(Route $router, RuleGroup $parent, $name, $route, $option = []) + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->option = $option; + } + + public function check($request, $url, $depr = '/') + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + $array = explode('|', $url); + array_shift($array); + $action = $array[1]; + + if (isset($this->option['allow']) && !in_array($action, $this->option['allow'])) { + // 允许操作 + return false; + } elseif (isset($this->option['except']) && in_array($action, $this->option['except'])) { + // 排除操作 + return false; + } + + if (isset($this->option['method'][$action])) { + $this->option['method'] = $this->option['method'][$action]; + } + + // 指定Response响应数据 + if (!empty($this->option['response'])) { + Container::get('hook')->add('response_send', $this->option['response']); + } + + // 开启请求缓存 + if (isset($this->option['cache']) && $request->isGet()) { + $this->parseRequestCache($request, $this->option['cache']); + } + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + } + + if (!empty($this->option['append'])) { + $request->route($this->option['append']); + } + + $bind = implode('|', $array); + + if (0 === strpos($this->route, '\\')) { + // 路由到类 + return $this->bindToClass($bind, substr($this->route, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return $this->bindToController($bind, substr($this->route, 1), $depr); + } else { + // 路由到模块/控制器 + return $this->bindToModule($bind, $this->route, $depr); + } + } + + public function allow($action = []) + { + return $this->option('allow', $action); + } + + public function except($action = []) + { + return $this->option('except', $action); + } +} diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index cef355d6..ab81be4a 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -89,49 +89,14 @@ class Domain extends RuleGroup */ private function checkRouteAlias($request, $url, $depr) { - $array = explode('|', $url); - $alias = array_shift($array); + $alias = strpos($url, '|') ? strstr($url, '|', true) : $url; $item = $this->router->getAlias($alias); if (is_null($item)) { return false; } - if (is_array($item)) { - list($rule, $option) = $item; - $action = $array[0]; - - if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { - // 允许操作 - return false; - } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { - // 排除操作 - return false; - } - - if (isset($option['method'][$action])) { - $option['method'] = $option['method'][$action]; - } - } else { - $rule = $item; - } - - $bind = implode('|', $array); - - // 参数有效性检查 - if (isset($option) && !$this->checkOption($option, $request)) { - // 路由不匹配 - return false; - } elseif (0 === strpos($rule, '\\')) { - // 路由到类 - return $this->bindToClass($bind, substr($rule, 1), $depr); - } elseif (0 === strpos($rule, '@')) { - // 路由到控制器类 - return $this->bindToController($bind, substr($rule, 1), $depr); - } else { - // 路由到模块/控制器 - return $this->bindToModule($bind, $rule, $depr); - } + return $item->check($request, $url, $depr); } /** diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index c25f99c3..c0b42d1a 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -936,23 +936,30 @@ abstract class Rule foreach ($match as $name) { $optional = ''; $slash = substr($name, 0, 1); + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } - if (strpos($name, ']')) { - $name = substr($name, 3, -1); + if (!in_array($slash, [':', '[', '<'])) { + $replace[] = $prefix . preg_quote($name, '/'); + continue; + } elseif (strpos($name, ']')) { + $name = substr($name, 2, -1); $optional = '?'; } elseif (strpos($name, '?')) { - $name = substr($name, 2, -2); + $name = substr($name, 1, -2); $optional = '?'; } elseif (strpos($name, '>')) { - $name = substr($name, 2, -1); - } elseif (strpos($name, ':')) { - $name = substr($name, 2); - } else { - $replace[] = '\\' . $name; - continue; + $name = substr($name, 1, -1); + } elseif (false !== strpos($name, ':')) { + $name = substr($name, 1); } - $replace[] = '(\\' . $slash . '(?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; + $replace[] = '(' . $prefix . '(?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; } // 是否区分 / 地址访问 diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 74681235..45e031e0 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -216,6 +216,7 @@ class RuleGroup extends Rule { if (!$lazy) { $this->parseGroupRule(); + $this->rule = null; } return $this; @@ -287,7 +288,7 @@ class RuleGroup extends Rule } } - if (preg_match_all('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:?\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/(?:[' . $slash . ']?<\w+\??>|[' . $slash . ']?\[?\:?\w+\]?)/', $rule, $matches)) { unset($rules[$key]); $pattern = array_merge($this->getPattern(), $item->getPattern()); $option = array_merge($this->getOption(), $item->getOption()); diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 1e844147..ca5698f7 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -328,7 +328,7 @@ class RuleItem extends Rule } } - if (preg_match_all('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:?\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/(?:[' . $slash . ']?<\w+\??>|[' . $slash . ']?\[?\:?\w+\]?)/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { -- Gitee From 42fbde9300be5ea6dbaf5e81e67b824b0a9f06fc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Feb 2018 16:25:41 +0800 Subject: [PATCH 0939/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/AliasRule.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index c4092449..abd7cd7d 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -11,6 +11,9 @@ namespace think\route; +use think\Container; +use think\Route; + class AliasRule extends Domain { /** -- Gitee From c1bcffa8ab19eb394218ec368e427f8f10d2060a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Feb 2018 18:36:31 +0800 Subject: [PATCH 0940/1384] =?UTF-8?q?=E7=AE=80=E5=8C=96=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 75 +++++++++++++++++-------------- library/think/route/RuleGroup.php | 14 +++--- library/think/route/RuleItem.php | 17 ++++--- 3 files changed, 60 insertions(+), 46 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index c0b42d1a..ec13a976 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -934,32 +934,7 @@ abstract class Rule protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '') { foreach ($match as $name) { - $optional = ''; - $slash = substr($name, 0, 1); - if (in_array($slash, ['/', '-'])) { - $prefix = '\\' . $slash; - $name = substr($name, 1); - $slash = substr($name, 0, 1); - } else { - $prefix = ''; - } - - if (!in_array($slash, [':', '[', '<'])) { - $replace[] = $prefix . preg_quote($name, '/'); - continue; - } elseif (strpos($name, ']')) { - $name = substr($name, 2, -1); - $optional = '?'; - } elseif (strpos($name, '?')) { - $name = substr($name, 1, -2); - $optional = '?'; - } elseif (strpos($name, '>')) { - $name = substr($name, 1, -1); - } elseif (false !== strpos($name, ':')) { - $name = substr($name, 1); - } - - $replace[] = '(' . $prefix . '(?<' . $name . $suffix . '>' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . '))' . $optional; + $replace[] = $this->buildNameRegex($name, $pattern, $suffix); } // 是否区分 / 地址访问 @@ -973,6 +948,43 @@ abstract class Rule return $regex . ($completeMatch ? '$' : ''); } + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param string $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex($name, $pattern, $suffix) + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return $prefix . preg_quote($name, '/'); + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + $nameRule = isset($pattern[$name]) ? $pattern[$name] : '\w+'; + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + /** * 分析路由规则中的变量 * @access protected @@ -984,20 +996,15 @@ abstract class Rule // 提取路由规则中的变量 $var = []; - if (preg_match_all('/(?:<\w+\??>|\[?\:\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { foreach ($matches[0] as $name) { $optional = false; - if (strpos($name, ']')) { - $name = substr($name, 2, -1); - $optional = true; - } elseif (strpos($name, '?')) { + if (strpos($name, '?')) { $name = substr($name, 1, -2); $optional = true; - } elseif (strpos($name, '>')) { - $name = substr($name, 1, -1); } else { - $name = substr($name, 1); + $name = substr($name, 1, -1); } $var[$name] = $optional ? 2 : 1; diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 45e031e0..9ec6697d 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -84,6 +84,10 @@ class RuleGroup extends Rule */ protected function setFullName() { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + if ($this->parent && $this->parent->getFullName()) { $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); } else { @@ -124,7 +128,7 @@ class RuleGroup extends Rule if ($this->fullName) { // 分组URL匹配检查 - $pos = strpos(str_replace('<', ':', $this->fullName), ':'); + $pos = strpos($this->fullName, '<'); if (false !== $pos) { $str = substr($this->fullName, 0, $pos); @@ -270,7 +274,7 @@ class RuleGroup extends Rule $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; - if (false === strpos($rule, ':') && false === strpos($rule, '<')) { + if (false === strpos($rule, '<')) { if (($complete && 0 === strcasecmp($rule, $url)) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { return $item->checkHasMatchRule($request, $url); } @@ -279,16 +283,16 @@ class RuleGroup extends Rule continue; } - $slash = implode('', array_unique(['\/', '\-', '\\' . $depr])); + $slash = preg_quote('/-' . $depr, '/'); - if ($matchRule = preg_split('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:\w+\]?)/', $rule, 2)) { + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { unset($rules[$key]); continue; } } - if (preg_match_all('/(?:[' . $slash . ']?<\w+\??>|[' . $slash . ']?\[?\:?\w+\]?)/', $rule, $matches)) { + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { unset($rules[$key]); $pattern = array_merge($this->getPattern(), $item->getPattern()); $option = array_merge($this->getOption(), $item->getOption()); diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index ca5698f7..72519e1d 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -61,7 +61,6 @@ class RuleItem extends Rule if (!empty($option['cross_domain'])) { $this->router->setCrossDomainRule($this, $method); } - } /** @@ -85,7 +84,11 @@ class RuleItem extends Rule $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); } - $this->rule = $rule; + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } // 生成路由标识的快捷访问 $this->setRuleName(); @@ -313,22 +316,22 @@ class RuleItem extends Rule $url = $depr . str_replace('|', $depr, $url); $rule = $depr . str_replace('/', $depr, $this->rule); - if (false === strpos($rule, ':') && false === strpos($rule, '<')) { + if (false === strpos($rule, '<')) { if (($completeMatch && 0 === strcasecmp($rule, $url)) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { return $var; } return false; } - $slash = implode('', array_unique(['\/', '\-', '\\' . $depr])); - - if ($matchRule = preg_split('/(?:[' . $slash . ']<\w+\??>|[' . $slash . ']\[?\:\w+\]?)/', $rule, 2)) { + if ($matchRule = preg_split('/<\w+\??>/', $rule, 2)) { if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { return false; } } - if (preg_match_all('/(?:[' . $slash . ']?<\w+\??>|[' . $slash . ']?\[?\:?\w+\]?)/', $rule, $matches)) { + $slash = preg_quote('/-' . $depr, '/'); + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { -- Gitee From 68ddcb30488d90287522e0cba9e678d63fca4669 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Feb 2018 18:46:55 +0800 Subject: [PATCH 0941/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 51f60e4a..4ff06e6e 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -87,7 +87,7 @@ class Resource extends RuleGroup $item = []; foreach ($array as $val) { - $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + $item[] = $val . '/<' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id') . '>'; } $rule = implode('/', $item) . '/' . $last; @@ -100,10 +100,10 @@ class Resource extends RuleGroup continue; } - if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { - $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); - } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { - $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); + if (isset($last) && strpos($val[1], '') && isset($option['var'][$last])) { + $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]); + } elseif (strpos($val[1], '') && isset($option['var'][$rule])) { + $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); } $option['rest'] = $key; -- Gitee From d20de17835098956f9b11231ee5ae5b1f5695a6b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Feb 2018 19:39:37 +0800 Subject: [PATCH 0942/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3MorphMany=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=9A=84eagerlyMorphToMany=E6=96=B9=E6=B3=95=E4=B8=80?= =?UTF-8?q?=E5=A4=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/MorphMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index b3a4f39f..2b489113 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -244,7 +244,7 @@ class MorphMany extends Relation protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 - $this->query->removeOptions('where'); + $this->query->removeOption('where'); if ($closure) { $closure($this->query); -- Gitee From aa8d2892cb0f39b603fe65ace051d809da90b82d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 12:18:19 +0800 Subject: [PATCH 0943/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=AB=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84URL=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 6 ++---- library/think/route/AliasRule.php | 7 +++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index ea66c7d1..a7281df4 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -123,10 +123,8 @@ class Url if ($alias) { // 别名路由解析 - foreach ($alias as $key => $val) { - if (is_array($val)) { - $val = $val[0]; - } + foreach ($alias as $key => $item) { + $val = $item->gerRoute(); if (0 === strpos($url, $val)) { $url = $key . substr($url, strlen($val)); diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index abd7cd7d..0de261aa 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -16,6 +16,8 @@ use think\Route; class AliasRule extends Domain { + protected $route; + /** * 架构函数 * @access public @@ -104,4 +106,9 @@ class AliasRule extends Domain { return $this->option('except', $action); } + + public function getRoute() + { + return $this->route; + } } -- Gitee From a8d469646dd98ec4f8b0e614976e39d87169b6ac Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 12:26:25 +0800 Subject: [PATCH 0944/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 72519e1d..e9eff63b 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -323,14 +323,14 @@ class RuleItem extends Rule return false; } - if ($matchRule = preg_split('/<\w+\??>/', $rule, 2)) { + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { return false; } } - $slash = preg_quote('/-' . $depr, '/'); - if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); -- Gitee From b3bb06fafe4f31c8b1ffb0bd06c4a179bd9a4bc9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 12:39:27 +0800 Subject: [PATCH 0945/1384] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/AliasRule.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index 0de261aa..8ff0d1eb 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -36,6 +36,14 @@ class AliasRule extends Domain $this->option = $option; } + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @return Dispatch|false + */ public function check($request, $url, $depr = '/') { if ($dispatch = $this->checkCrossDomain($request)) { @@ -97,16 +105,33 @@ class AliasRule extends Domain } } + /** + * 设置允许的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ public function allow($action = []) { return $this->option('allow', $action); } + /** + * 设置排除的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ public function except($action = []) { return $this->option('except', $action); } + /** + * 获取当前的路由 + * @access public + * @return string + */ public function getRoute() { return $this->route; -- Gitee From 3c262e6c71c4e0884a94c99e8c71543d57f0293c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 12:52:05 +0800 Subject: [PATCH 0946/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=AB=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/AliasRule.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index 8ff0d1eb..bfb63761 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -56,9 +56,7 @@ class AliasRule extends Domain return false; } - $array = explode('|', $url); - array_shift($array); - $action = $array[1]; + list($action, $bind) = array_pad(explode('|', $url, 2), 2, ''); if (isset($this->option['allow']) && !in_array($action, $this->option['allow'])) { // 允许操作 @@ -91,12 +89,10 @@ class AliasRule extends Domain $request->route($this->option['append']); } - $bind = implode('|', $array); - if (0 === strpos($this->route, '\\')) { // 路由到类 return $this->bindToClass($bind, substr($this->route, 1), $depr); - } elseif (0 === strpos($rule, '@')) { + } elseif (0 === strpos($this->route, '@')) { // 路由到控制器类 return $this->bindToController($bind, substr($this->route, 1), $depr); } else { -- Gitee From 5d6934f8c780a44786e3642f522c313a36f6eec5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 22:55:53 +0800 Subject: [PATCH 0947/1384] =?UTF-8?q?Container=E7=B1=BB=E7=9A=84=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=8D=95=E8=8E=B7=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 72 +++++++++++++++++++--------------- library/think/route/Domain.php | 55 +++++++++++--------------- 2 files changed, 64 insertions(+), 63 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index 32a95746..f07bcf5e 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -14,8 +14,10 @@ namespace think; use Closure; use InvalidArgumentException; use ReflectionClass; +use ReflectionException; use ReflectionFunction; use ReflectionMethod; +use think\exception\ClassNotFoundException; class Container { @@ -225,84 +227,92 @@ class Container /** * 执行函数或者闭包方法 支持参数调用 * @access public - * @param string|array|\Closure $function 函数或者闭包 - * @param array $vars 变量 + * @param mixed $function 函数或者闭包 + * @param array $vars 参数 * @return mixed */ public function invokeFunction($function, $vars = []) { - $reflect = new ReflectionFunction($function); - $args = $this->bindParams($reflect, $vars); + try { + $reflect = new ReflectionFunction($function); - return $reflect->invokeArgs($args); + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($args); + } catch (ReflectionException $e) { + throw new Exception('method not exists:' . $function); + } } /** * 调用反射执行类的方法 支持参数绑定 * @access public - * @param string|array $method 方法 - * @param array $vars 变量 + * @param mixed $method 方法 + * @param array $vars 参数 * @return mixed */ public function invokeMethod($method, $vars = []) { - if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]); - $reflect = new ReflectionMethod($class, $method[1]); - } else { - // 静态方法 - $reflect = new ReflectionMethod($method); - } + try { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]); + $reflect = new ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new ReflectionMethod($method); + } - $args = $this->bindParams($reflect, $vars); + $args = $this->bindParams($reflect, $vars); - return $reflect->invokeArgs(isset($class) ? $class : null, $args); + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } catch (ReflectionException $e) { + throw new Exception('method not exists:' . $function); + } } /** * 调用反射执行callable 支持参数绑定 * @access public * @param mixed $callable - * @param array $vars 变量 + * @param array $vars 参数 * @return mixed */ public function invoke($callable, $vars = []) { if ($callable instanceof Closure) { - $result = $this->invokeFunction($callable, $vars); - } else { - $result = $this->invokeMethod($callable, $vars); + return $this->invokeFunction($callable, $vars); } - return $result; + return $this->invokeMethod($callable, $vars); } /** * 调用反射执行类的实例化 支持依赖注入 * @access public * @param string $class 类名 - * @param array $vars 变量 + * @param array $vars 参数 * @return mixed */ public function invokeClass($class, $vars = []) { - $reflect = new ReflectionClass($class); - $constructor = $reflect->getConstructor(); + try { + $reflect = new ReflectionClass($class); - if ($constructor) { - $args = $this->bindParams($constructor, $vars); - } else { - $args = []; - } + $constructor = $reflect->getConstructor(); + + $args = $constructor ? $this->bindParams($constructor, $vars) : []; - return $reflect->newInstanceArgs($args); + return $reflect->newInstanceArgs($args); + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } } /** * 绑定参数 * @access protected * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 + * @param array $vars 参数 * @return array */ protected function bindParams($reflect, $vars = []) diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index ab81be4a..d6e144ba 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -90,13 +90,10 @@ class Domain extends RuleGroup private function checkRouteAlias($request, $url, $depr) { $alias = strpos($url, '|') ? strstr($url, '|', true) : $url; - $item = $this->router->getAlias($alias); - if (is_null($item)) { - return false; - } + $item = $this->router->getAlias($alias); - return $item->check($request, $url, $depr); + return $item ? $item->check($request, $url, $depr) : false; } /** @@ -106,24 +103,26 @@ class Domain extends RuleGroup * @param string $depr URL分隔符 * @return Dispatch|false */ - private function checkUrlBind(&$url, $depr = '/') + private function checkUrlBind($url, $depr = '/') { - $bind = $this->router->getBind($this->name); + $bind = $this->router->getBind($this->domain); if (!empty($bind)) { // 记录绑定信息 Container::get('app')->log('[ BIND ] ' . var_export($bind, true)); // 如果有URL绑定 则进行绑定检测 - if (0 === strpos($bind, '\\')) { - // 绑定到类 - return $this->bindToClass($url, substr($bind, 1), $depr); - } elseif (0 === strpos($bind, '@')) { - // 绑定到控制器类 - return $this->bindToController($url, substr($bind, 1), $depr); - } elseif (0 === strpos($bind, ':')) { - // 绑定到命名空间 - return $this->bindToNamespace($url, substr($bind, 1), $depr); + $type = substr($bind, 0, 1); + $bind = substr($bind, 1); + + $bindTo = [ + '\\' => 'bindToClass', + '@' => 'bindToController', + ':' => 'bindToNamespace', + ]; + + if (isset($bindTo[$type])) { + return $this->{$bindTo[$type]}($url, $bind, $depr); } } @@ -132,15 +131,13 @@ class Domain extends RuleGroup /** * 绑定到类 - * @access public + * @access protected * @param string $url URL地址 * @param string $class 类名(带命名空间) - * @param string $depr URL分隔符 * @return CallbackDispatch */ - public function bindToClass($url, $class, $depr = '/') + protected function bindToClass($url, $class) { - $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); @@ -153,15 +150,13 @@ class Domain extends RuleGroup /** * 绑定到命名空间 - * @access public + * @access protected * @param string $url URL地址 * @param string $namespace 命名空间 - * @param string $depr URL分隔符 * @return CallbackDispatch */ - public function bindToNamespace($url, $namespace, $depr = '/') + protected function bindToNamespace($url, $namespace) { - $url = str_replace($depr, '|', $url); $array = explode('|', $url, 3); $class = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_controller'); $method = !empty($array[1]) ? $array[1] : Container::get('config')->get('default_action'); @@ -175,15 +170,13 @@ class Domain extends RuleGroup /** * 绑定到控制器类 - * @access public + * @access protected * @param string $url URL地址 * @param string $controller 控制器名 (支持带模块名 index/user ) - * @param string $depr URL分隔符 * @return ControllerDispatch */ - public function bindToController($url, $controller, $depr = '/') + protected function bindToController($url, $controller) { - $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); @@ -196,15 +189,13 @@ class Domain extends RuleGroup /** * 绑定到模块/控制器 - * @access public + * @access protected * @param string $url URL地址 * @param string $controller 控制器类名(带命名空间) - * @param string $depr URL分隔符 * @return ModuleDispatch */ - public function bindToModule($url, $controller, $depr = '/') + protected function bindToModule($url, $controller) { - $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); -- Gitee From ebc3eb06f4c71e1072b1855f2a6e980f39707d9a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 23:06:21 +0800 Subject: [PATCH 0948/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 1 + library/think/Container.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 7f6ff030..60614550 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -24,6 +24,7 @@ return [ 'dispatch type not support' => '不支持的调度类型', 'method param miss' => '方法参数错误', 'method not exists' => '方法不存在', + 'function not exists' => '函数不存在', 'module not exists' => '模块不存在', 'controller not exists' => '控制器不存在', 'class not exists' => '类不存在', diff --git a/library/think/Container.php b/library/think/Container.php index f07bcf5e..72e3a6ba 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -240,7 +240,7 @@ class Container return $reflect->invokeArgs($args); } catch (ReflectionException $e) { - throw new Exception('method not exists:' . $function); + throw new Exception('function not exists: ' . $function . '()'); } } @@ -266,7 +266,7 @@ class Container return $reflect->invokeArgs(isset($class) ? $class : null, $args); } catch (ReflectionException $e) { - throw new Exception('method not exists:' . $function); + throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()'); } } @@ -304,7 +304,7 @@ class Container return $reflect->newInstanceArgs($args); } catch (ReflectionException $e) { - throw new ClassNotFoundException('class not exists:' . $class, $class); + throw new ClassNotFoundException('class not exists: ' . $class, $class); } } -- Gitee From b0c0b26f67e4a38542dbdc0d522ba113b21e7759 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Feb 2018 23:13:09 +0800 Subject: [PATCH 0949/1384] =?UTF-8?q?Domain=E5=AF=B9=E8=B1=A1=E6=94=AF?= =?UTF-8?q?=E6=8C=81bind=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Domain.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index d6e144ba..77fe1d3f 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -79,6 +79,19 @@ class Domain extends RuleGroup return parent::check($request, $url, $depr, $completeMatch); } + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @return $this + */ + public function bind($bind) + { + $this->router->bind($bind, $this->domain); + + return $this; + } + /** * 检测路由别名 * @access private -- Gitee From 2436825336445646906a2bfeaa4a0d7f21324f64 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Mon, 26 Feb 2018 15:23:42 +0800 Subject: [PATCH 0950/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Paginator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 3fccdf9e..85766f34 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -150,7 +150,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J $url = $path; if (!empty($parameters)) { - $url .= '?' . urldecode(http_build_query($parameters, null, '&')); + $url .= '?' . http_build_query($parameters, null, '&'); } return $url . $this->buildFragment(); -- Gitee From 760b11c3c9889e74329c6aa8bc8db73ea24c3a27 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Feb 2018 17:13:10 +0800 Subject: [PATCH 0951/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index 3403b845..489a2c97 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -255,7 +255,7 @@ class Route { $domain = is_null($domain) ? $this->domain : $domain; - $this->bind[$this->domain] = $bind; + $this->bind[$domain] = $bind; return $this; } -- Gitee From 80f3198fbf6cadefb6e393ac36c894182a0a9a4c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Feb 2018 17:40:04 +0800 Subject: [PATCH 0952/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=B0Response=E5=AF=B9=E8=B1=A1=E7=9A=84=E4=B8=80=E5=A4=84B?= =?UTF-8?q?UG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 9ec6697d..d39cfe51 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -218,7 +218,7 @@ class RuleGroup extends Rule */ public function lazy($lazy = true) { - if (!$lazy) { + if (!$lazy && !is_object($this->rule)) { $this->parseGroupRule(); $this->rule = null; } -- Gitee From 2c6c040a794165c3e9cec0843e41a3f88152b621 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Feb 2018 18:15:52 +0800 Subject: [PATCH 0953/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=9A=84=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/AliasRule.php | 26 ++++++----------- library/think/route/Domain.php | 12 ++++++++ library/think/route/RuleGroup.php | 46 ++++++++++++++++--------------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index bfb63761..aec1266c 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -11,7 +11,6 @@ namespace think\route; -use think\Container; use think\Route; class AliasRule extends Domain @@ -23,8 +22,8 @@ class AliasRule extends Domain * @access public * @param Route $router 路由实例 * @param RuleGroup $parent 上级对象 - * @param string|array $rule 路由规则 - * @param string|\Closure $route 路由地址 + * @param string $name 路由别名 + * @param string $route 路由绑定 * @param array $option 路由参数 */ public function __construct(Route $router, RuleGroup $parent, $name, $route, $option = []) @@ -70,34 +69,25 @@ class AliasRule extends Domain $this->option['method'] = $this->option['method'][$action]; } - // 指定Response响应数据 - if (!empty($this->option['response'])) { - Container::get('hook')->add('response_send', $this->option['response']); - } - - // 开启请求缓存 - if (isset($this->option['cache']) && $request->isGet()) { - $this->parseRequestCache($request, $this->option['cache']); - } + // 匹配后执行的行为 + $this->matchGroupRequestCheck($request); if ($this->parent) { // 合并分组参数 $this->mergeGroupOptions(); } - if (!empty($this->option['append'])) { - $request->route($this->option['append']); - } + $this->parseBindAppendParam($this->route); if (0 === strpos($this->route, '\\')) { // 路由到类 - return $this->bindToClass($bind, substr($this->route, 1), $depr); + return $this->bindToClass($bind, substr($this->route, 1)); } elseif (0 === strpos($this->route, '@')) { // 路由到控制器类 - return $this->bindToController($bind, substr($this->route, 1), $depr); + return $this->bindToController($bind, substr($this->route, 1)); } else { // 路由到模块/控制器 - return $this->bindToModule($bind, $this->route, $depr); + return $this->bindToModule($bind, $this->route); } } diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 77fe1d3f..01eac272 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -121,6 +121,9 @@ class Domain extends RuleGroup $bind = $this->router->getBind($this->domain); if (!empty($bind)) { + + $this->parseBindAppendParam($bind); + // 记录绑定信息 Container::get('app')->log('[ BIND ] ' . var_export($bind, true)); @@ -142,6 +145,15 @@ class Domain extends RuleGroup return false; } + protected function parseBindAppendParam(&$bind) + { + if (false !== strpos($bind, '?')) { + list($bind, $query) = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + /** * 绑定到类 * @access protected diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index d39cfe51..70bf0604 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -151,16 +151,7 @@ class RuleGroup extends Rule } // 分组匹配后执行的行为 - - // 指定Response响应数据 - if (!empty($this->option['response'])) { - Container::get('hook')->add('response_send', $this->option['response']); - } - - // 开启请求缓存 - if (isset($this->option['cache']) && $request->isGet()) { - $this->parseRequestCache($request, $this->option['cache']); - } + $this->matchGroupRequestCheck($request); // 获取当前路由规则 $method = strtolower($request->method()); @@ -175,10 +166,6 @@ class RuleGroup extends Rule $completeMatch = $this->option['complete_match']; } - if (!empty($this->option['append'])) { - $request->route($this->option['append']); - } - if (!empty($this->option['merge_rule_regex'])) { // 合并路由正则规则进行路由匹配检查 $result = $this->checkMergeRuleRegex($request, $rules, $url, $depr, $completeMatch); @@ -210,6 +197,28 @@ class RuleGroup extends Rule return $result; } + /** + * 分组匹配后执行的行为 + * @access protected + * @param Request $request + * @return void + */ + protected function matchGroupRequestCheck($request) + { + if (!empty($this->option['response'])) { + Container::get('hook')->add('response_send', $this->option['response']); + } + + // 开启请求缓存 + if (isset($this->option['cache']) && $request->isGet()) { + $this->parseRequestCache($request, $this->option['cache']); + } + + if (!empty($this->option['append'])) { + $request->route($this->option['append']); + } + } + /** * 延迟解析分组的路由规则 * @access public @@ -240,14 +249,7 @@ class RuleGroup extends Rule Container::getInstance()->invokeFunction($this->rule); } elseif (is_array($this->rule)) { $this->addRules($this->rule); - } elseif ($this->rule) { - if (false !== strpos($this->rule, '?')) { - list($rule, $query) = explode('?', $this->rule); - parse_str($query, $vars); - $this->append($vars); - $this->rule = $rule; - } - + } elseif (is_string($this->rule) && $this->rule) { $this->router->bind($this->rule, $this->domain); } -- Gitee From 8cbfba98c8f76fd595022a5186ff8bcb2c4d78b9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Feb 2018 12:19:54 +0800 Subject: [PATCH 0954/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=AE=9A=E4=B9=89=20=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=A3=80=E6=B5=8B=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 12 +++-- library/think/route/AliasRule.php | 2 +- library/think/route/Domain.php | 13 ----- library/think/route/Rule.php | 53 +++++++++++-------- library/think/route/RuleGroup.php | 87 +++++++++++++++++++------------ library/think/route/RuleItem.php | 5 -- 6 files changed, 95 insertions(+), 77 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 489a2c97..281a24f2 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -214,10 +214,14 @@ class Route $domainName .= '.' . $this->request->rootDomain(); } - $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) - ->lazy($this->lazy); - - $this->domains[$domainName] = $domain; + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) + ->lazy($this->lazy); + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } if (is_array($name) && !empty($name)) { $root = $this->request->rootDomain(); diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index aec1266c..0658453e 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -70,7 +70,7 @@ class AliasRule extends Domain } // 匹配后执行的行为 - $this->matchGroupRequestCheck($request); + $this->afterMatchGroup($request); if ($this->parent) { // 合并分组参数 diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 01eac272..12ef39f3 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -13,12 +13,10 @@ namespace think\route; use think\Container; use think\Loader; -use think\Response; use think\Route; use think\route\dispatch\Callback as CallbackDispatch; use think\route\dispatch\Controller as ControllerDispatch; use think\route\dispatch\Module as ModuleDispatch; -use think\route\dispatch\Response as ResponseDispatch; class Domain extends RuleGroup { @@ -51,17 +49,6 @@ class Domain extends RuleGroup */ public function check($request, $url, $depr = '/', $completeMatch = false) { - if ($this->rule) { - // 解析域名路由 - if ($this->rule instanceof Response) { - return new ResponseDispatch($this->rule); - } - - $this->parseGroupRule(); - - $this->rule = null; - } - // 检测别名路由 $result = $this->checkRouteAlias($request, $url, $depr); diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index ec13a976..991a40c7 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -582,28 +582,7 @@ abstract class Rule } } - // 绑定模型数据 - if (isset($option['model'])) { - $this->createBindModel($option['model'], $matches); - } - - // 指定Header数据 - if (!empty($option['header'])) { - $header = $option['header']; - Container::get('hook')->add('response_send', function ($response) use ($header) { - $response->header($header); - }); - } - - // 指定Response响应数据 - if (!empty($option['response'])) { - Container::get('hook')->add('response_send', $option['response']); - } - - // 开启请求缓存 - if (isset($option['cache']) && $request->isGet()) { - $this->parseRequestCache($request, $option['cache']); - } + $this->afterMatchRule($request, $option, $matches); // 解析额外参数 $count = substr_count($rule, '/'); @@ -631,6 +610,36 @@ abstract class Rule return $this->dispatch($request, $route, $option); } + protected function afterMatchRule($request, $option = [], $matches = []) + { + // 绑定模型数据 + if (isset($option['model'])) { + $this->createBindModel($option['model'], $matches); + } + + // 指定Header数据 + if (!empty($option['header'])) { + $header = $option['header']; + Container::get('hook')->add('response_send', function ($response) use ($header) { + $response->header($header); + }); + } + + // 指定Response响应数据 + if (!empty($option['response'])) { + Container::get('hook')->add('response_send', $option['response']); + } + + // 开启请求缓存 + if (isset($option['cache']) && $request->isGet()) { + $this->parseRequestCache($request, $option['cache']); + } + + if (!empty($option['append'])) { + $request->route($option['append']); + } + } + /** * 验证数据 * @access protected diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 70bf0604..20bf5a95 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -117,45 +117,29 @@ class RuleGroup extends Rule public function check($request, $url, $depr = '/', $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { - // 允许跨域 + // 跨域OPTIONS请求 return $dispatch; } - // 检查参数有效性 - if (!$this->checkOption($this->option, $request)) { + // 检查分组有效性 + if (!$this->checkOption($this->option, $request) || !$this->checkUrl()) { return false; } - if ($this->fullName) { - // 分组URL匹配检查 - $pos = strpos($this->fullName, '<'); - - if (false !== $pos) { - $str = substr($this->fullName, 0, $pos); - } else { - $str = $this->fullName; - } - - if (0 !== stripos(str_replace('|', '/', $url), $str)) { - return false; - } - } - // 解析分组路由 if ($this->rule) { if ($this->rule instanceof Response) { return new ResponseDispatch($this->rule); } - $this->parseGroupRule(); + $this->parseGroupRule($this->rule); } // 分组匹配后执行的行为 - $this->matchGroupRequestCheck($request); + $this->afterMatchGroup($request); // 获取当前路由规则 - $method = strtolower($request->method()); - $rules = array_merge($this->rules['*'], $this->rules[$method]); + $rules = $this->getMethodRules($request); if ($this->parent) { // 合并分组参数 @@ -197,13 +181,50 @@ class RuleGroup extends Rule return $result; } + /** + * 获取当前请求的路由规则(包括子分组、资源路由) + * @access protected + * @param Request $request + * @return array + */ + protected function getMethodRules($request) + { + $method = strtolower($request->method()); + + return array_merge($this->rules['*'], $this->rules[$method]); + } + + /** + * 分组URL匹配检查 + * @access protected + * @return bool + */ + protected function checkUrl() + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if (0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + /** * 分组匹配后执行的行为 * @access protected * @param Request $request * @return void */ - protected function matchGroupRequestCheck($request) + protected function afterMatchGroup($request) { if (!empty($this->option['response'])) { Container::get('hook')->add('response_send', $this->option['response']); @@ -216,6 +237,7 @@ class RuleGroup extends Rule if (!empty($this->option['append'])) { $request->route($this->option['append']); + unset($this->option['append']); } } @@ -228,7 +250,7 @@ class RuleGroup extends Rule public function lazy($lazy = true) { if (!$lazy && !is_object($this->rule)) { - $this->parseGroupRule(); + $this->parseGroupRule($this->rule); $this->rule = null; } @@ -237,20 +259,21 @@ class RuleGroup extends Rule /** * 解析分组和域名的路由规则及绑定 - * @access protected + * @access public + * @param mixed $rule 路由规则 * @return void */ - protected function parseGroupRule() + public function parseGroupRule($rule) { $origin = $this->router->getGroup(); $this->router->setGroup($this); - if ($this->rule instanceof \Closure) { - Container::getInstance()->invokeFunction($this->rule); - } elseif (is_array($this->rule)) { - $this->addRules($this->rule); - } elseif (is_string($this->rule) && $this->rule) { - $this->router->bind($this->rule, $this->domain); + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_array($rule)) { + $this->addRules($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); } $this->router->setGroup($origin); diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index e9eff63b..4e4c4ab3 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -237,11 +237,6 @@ class RuleItem extends Rule } if (false !== $match = $this->match($url, $pattern, $option, $depr, $completeMatch)) { - // 匹配到路由规则 - if (!empty($option['append'])) { - $request->route($option['append']); - } - return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); } -- Gitee From 9e5e0146a92bd01200355c148ffd786112240705 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Feb 2018 10:27:18 +0800 Subject: [PATCH 0955/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 20bf5a95..60bb63da 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -122,7 +122,7 @@ class RuleGroup extends Rule } // 检查分组有效性 - if (!$this->checkOption($this->option, $request) || !$this->checkUrl()) { + if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) { return false; } @@ -197,9 +197,10 @@ class RuleGroup extends Rule /** * 分组URL匹配检查 * @access protected + * @param string $url * @return bool */ - protected function checkUrl() + protected function checkUrl($url) { if ($this->fullName) { $pos = strpos($this->fullName, '<'); -- Gitee From 84c52cf9e9ef9eaf3290e8c5ee8746a8c2ba36d2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Feb 2018 14:01:56 +0800 Subject: [PATCH 0956/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 60bb63da..5af04dd1 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -497,9 +497,9 @@ class RuleGroup extends Rule { if ('' === $method) { return $this->rules; - } else { - return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : []; } + + return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : []; } } -- Gitee From 17ffe32c2fa1bc9667f73fee3d3ffacc37e6be3d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Feb 2018 14:43:28 +0800 Subject: [PATCH 0957/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E7=9A=84=E8=BE=93=E5=87=BA=E8=A7=84=E5=88=99?= =?UTF-8?q?=20=E4=B8=8D=E5=8F=97URL=E5=BD=B1=E5=93=8D=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=AE=9E=E9=99=85=E6=93=8D=E4=BD=9C=E6=96=B9=E6=B3=95=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=90=8D=E7=A7=B0=E8=BD=AC=E6=8D=A2=E4=B8=BA=E5=B0=8F?= =?UTF-8?q?=E5=86=99+=E4=B8=8B=E5=88=92=E7=BA=BF=E7=9A=84=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 8 ++++++++ library/think/Controller.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/helper.php b/helper.php index 461460ea..3a931f12 100644 --- a/helper.php +++ b/helper.php @@ -29,6 +29,7 @@ use think\facade\Request; use think\facade\Route; use think\facade\Session; use think\facade\Url; +use think\Loader; use think\Response; use think\route\RuleItem; @@ -658,6 +659,13 @@ if (!function_exists('view')) { */ function view($template = '', $vars = [], $code = 200, $filter = null) { + if ('' === $template) { + $trace = debug_backtrace(false, 2); + $suffix = Container::get('config')->get('app.action_suffix'); + $action = $suffix ? substr($trace[1]['function'], 0, -strlen($suffix)) : $trace[1]['function']; + $template = Loader::parseName($action); + } + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); } } diff --git a/library/think/Controller.php b/library/think/Controller.php index ffa38186..4d51ce34 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -12,6 +12,7 @@ namespace think; use think\exception\ValidateException; +use think\Loader; use traits\controller\Jump; class Controller @@ -118,6 +119,13 @@ class Controller */ protected function fetch($template = '', $vars = [], $config = []) { + if ('' === $template) { + $trace = debug_backtrace(false, 2); + $suffix = $this->app['config']->get('app.action_suffix'); + $action = $suffix ? substr($trace[1]['function'], 0, -strlen($suffix)) : $trace[1]['function']; + $template = Loader::parseName($action); + } + return $this->view->fetch($template, $vars, $config); } -- Gitee From da748473f0dff28f95e2936ae1bbe6a5c34fe46c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Feb 2018 14:45:12 +0800 Subject: [PATCH 0958/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 1 - 1 file changed, 1 deletion(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index 4d51ce34..de02e370 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -12,7 +12,6 @@ namespace think; use think\exception\ValidateException; -use think\Loader; use traits\controller\Jump; class Controller -- Gitee From 4df7113d6b3b483966289fa4b9fdad8e7b605df8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Feb 2018 19:05:14 +0800 Subject: [PATCH 0959/1384] =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E7=BA=A7=E6=8E=A7=E5=88=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Build.php | 60 ++++++++++++++++++++++++------- library/think/route/AliasRule.php | 3 +- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/library/think/Build.php b/library/think/Build.php index ed3ffe43..ad050db3 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -187,7 +187,7 @@ class Build * @param string $layer 控制器层目录名 * @return string */ - public function buildRoute($alias = false, $layer = '') + public function buildRoute($suffix = false, $layer = '') { $namespace = $this->app->getNameSpace(); $modules = glob($this->basePath . '*', GLOB_ONLYDIR); @@ -204,11 +204,8 @@ class Build continue; } - $controllers = glob($this->basePath . $module . '/' . $layer . '/*.php'); - - foreach ($controllers as $controller) { - $content .= $this->getControllerRoute($namespace, $module, basename($controller, '.php'), $alias, $layer); - } + $path = $this->basePath . $module . '/' . $layer . '/'; + $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer); } $filename = $this->app->getRuntimePath() . 'build_route.php'; @@ -218,8 +215,9 @@ class Build } /** - * 生成控制器类的路由规则 + * 生成子目录控制器类的路由规则 * @access protected + * @param string $path 控制器目录 * @param string $namespace 应用命名空间 * @param string $module 模块 * @param string $controller 控制器名 @@ -227,16 +225,52 @@ class Build * @param string $layer 控制器层目录名 * @return string */ - protected function getControllerRoute($namespace, $module, $controller, $alias = false, $layer = '') + protected function buildDirRoute($path, $namespace, $module, $suffix, $layer) { - $class = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller); - $content = ''; - $comment = $class->getDocComment(); + $content = ''; + $controllers = glob($path . '*.php'); + + foreach ($controllers as $controller) { + $controller = basename($controller, '.php'); + + if ($suffix) { + // 控制器后缀 + $controller = substr($controller, 0, -10); + } + + $class = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller); + + if (strpos($layer, DIRECTORY_SEPARATOR)) { + // 多级控制器 + $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11)); + $controller = $level . '.' . $controller; + } + + $content .= $this->getControllerRoute($class, $module, $controller); + } - if ($alias) { - $controller = substr($controller, 0, -10); + $subDir = glob($path . '*', GLOB_ONLYDIR); + + foreach ($subDir as $dir) { + $content .= $this->buildDirRoute($dir . '/', $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 . '/' . $controller; diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index 0658453e..21704659 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -41,9 +41,10 @@ class AliasRule extends Domain * @param Request $request 请求对象 * @param string $url 访问地址 * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/') + public function check($request, $url, $depr = '/', $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 -- Gitee From efe19ae7e39fca76b4dea8456e38b8964c427f2c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Feb 2018 19:07:30 +0800 Subject: [PATCH 0960/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Build.php | 1 - 1 file changed, 1 deletion(-) diff --git a/library/think/Build.php b/library/think/Build.php index ad050db3..6c34d29c 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -220,7 +220,6 @@ class Build * @param string $path 控制器目录 * @param string $namespace 应用命名空间 * @param string $module 模块 - * @param string $controller 控制器名 * @param bool $suffix 类库后缀 * @param string $layer 控制器层目录名 * @return string -- Gitee From 4353e32dba387251314f41fede54506c2771e038 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Mar 2018 15:20:36 +0800 Subject: [PATCH 0961/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 4ff06e6e..fbced58f 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -57,10 +57,11 @@ class Resource extends RuleGroup /** * 解析资源路由规则 - * @access protected + * @access public + * @param mixed $rule 路由规则 * @return void */ - protected function parseGroupRule() + public function parseGroupRule($rule) { $origin = $this->router->getGroup(); $this->router->setGroup($this); -- Gitee From 0d8aab10c1f33b3f229071a71ee15402a2877394 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Mar 2018 15:23:16 +0800 Subject: [PATCH 0962/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index fbced58f..6873132e 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -51,6 +51,7 @@ class Resource extends RuleGroup $this->rest = $rest; if ($this->parent) { + $this->domain = $this->parent->getDomain(); $this->parent->addRuleItem($this); } } -- Gitee From a1ea28d5ff75e1aa48f46c54190e06889e958237 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Mar 2018 17:15:33 +0800 Subject: [PATCH 0963/1384] =?UTF-8?q?Query=E7=B1=BB=E5=A2=9E=E5=8A=A0getNu?= =?UTF-8?q?mRows=E6=96=B9=E6=B3=95=E8=8E=B7=E5=8F=96=E5=89=8D=E6=AC=A1?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=BD=B1=E5=93=8D=E7=9A=84=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 8c85d489..a30c81ec 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -356,6 +356,16 @@ class Query return $this->connection->getLastInsID($sequence); } + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->connection->getNumRows(); + } + /** * 获取最近一次查询的sql语句 * @access public -- Gitee From e9feca832b96b805dcc9f0f2ffa79184c67fa90e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Mar 2018 18:06:22 +0800 Subject: [PATCH 0964/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index a30c81ec..bfe7f18e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1172,7 +1172,7 @@ class Query */ public function whereNull($field, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'null', null); + return $this->parseWhereExp($logic, $field, 'null', null, [], true); } /** @@ -1184,7 +1184,7 @@ class Query */ public function whereNotNull($field, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'notnull', null); + return $this->parseWhereExp($logic, $field, 'notnull', null, [], true); } /** @@ -1223,7 +1223,7 @@ class Query */ public function whereIn($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'in', $condition); + return $this->parseWhereExp($logic, $field, 'in', $condition, [], true); } /** @@ -1236,7 +1236,7 @@ class Query */ public function whereNotIn($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'not in', $condition); + return $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); } /** @@ -1249,7 +1249,7 @@ class Query */ public function whereLike($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'like', $condition); + return $this->parseWhereExp($logic, $field, 'like', $condition, [], true); } /** @@ -1262,7 +1262,7 @@ class Query */ public function whereNotLike($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'not like', $condition); + return $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); } /** @@ -1275,7 +1275,7 @@ class Query */ public function whereBetween($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'between', $condition); + return $this->parseWhereExp($logic, $field, 'between', $condition, [], true); } /** @@ -1288,7 +1288,7 @@ class Query */ public function whereNotBetween($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'not between', $condition); + return $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); } /** @@ -1336,7 +1336,7 @@ class Query */ public function whereExp($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'exp', $condition); + return $this->parseWhereExp($logic, $field, 'exp', $condition, [], true); } /** @@ -1347,9 +1347,10 @@ class Query * @param mixed $op 查询表达式 * @param mixed $condition 查询条件 * @param array $param 查询参数 + * @param bool $strict 严格模式 * @return $this */ - protected function parseWhereExp($logic, $field, $op, $condition, $param = []) + protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) { if ($field instanceof $this) { $this->options['where'] = $field->getOptions('where'); @@ -1362,7 +1363,10 @@ class Query $field = $this->options['via'] . '.' . $field; } - if ($field instanceof \Closure) { + if ($strict) { + // 使用严格模式查询 + $where = [$field, $op, $condition]; + } elseif ($field instanceof \Closure) { $where = is_string($op) ? [$op, $field] : $field; $field = ''; } elseif (is_string($field) && preg_match('/[,=\<\'\"\(\s]/', $field)) { -- Gitee From fddb2500d2537f07432a4fc87fe0e9a3474c5fc2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Mar 2018 18:41:35 +0800 Subject: [PATCH 0965/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 122 ++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 42 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index bfe7f18e..6ff4324b 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1331,17 +1331,18 @@ class Query * @access public * @param mixed $field 查询字段 * @param mixed $condition 查询条件 + * @param array $bind 参数绑定 * @param string $logic 查询逻辑 and or xor * @return $this */ - public function whereExp($field, $condition, $logic = 'AND') + public function whereExp($field, $condition, $bind = [], $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'exp', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'exp', $condition, $bind, true); } /** * 分析查询表达式 - * @access public + * @access protected * @param string $logic 查询逻辑 and or xor * @param mixed $field 查询字段 * @param mixed $op 查询表达式 @@ -1366,9 +1367,47 @@ class Query if ($strict) { // 使用严格模式查询 $where = [$field, $op, $condition]; - } elseif ($field instanceof \Closure) { + if ('exp' == $op && is_array($param)) { + // 参数绑定 + $this->bind($param); + } + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } else { + // 解析条件单元 + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + if ($field instanceof \Closure) { + $field = ''; + } + + if (isset($this->options['where'][$logic][$field])) { + $this->options['where'][$logic][] = $where; + } else { + $this->options['where'][$logic][$field] = $where; + } + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return mixed + */ + protected function parseWhereItem($logic, $field, $op, $condition, $param = []) + { + if ($field instanceof \Closure) { $where = is_string($op) ? [$op, $field] : $field; - $field = ''; } elseif (is_string($field) && preg_match('/[,=\<\'\"\(\s]/', $field)) { $where = ['', 'exp', $field]; if (is_array($op)) { @@ -1376,31 +1415,10 @@ class Query $this->bind($op); } } elseif (is_null($op) && is_null($condition)) { - if (is_array($field)) { - if (key($field) !== 0) { - $where = []; - foreach ($field as $key => $val) { - if (is_null($val)) { - $where[$key] = [$key, 'null', '']; - } else { - $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; - } - } - } else { - // 数组批量查询 - $where = $field; - } - - if (!empty($where)) { - $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; - } - - return $this; - } elseif ($field && is_string($field)) { - // 字符串查询 - $where = [$field, 'null', '']; - } + // 字符串查询 + $where = [$field, 'null', '']; } elseif (is_array($op)) { + // 同一字段多条件查询 array_unshift($param, $field); $where = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { @@ -1418,12 +1436,34 @@ class Query } } - if (!empty($where)) { - if (isset($this->options['where'][$logic][$field])) { - $this->options['where'][$logic][] = $where; - } else { - $this->options['where'][$logic][$field] = $where; + return $where; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems($field, $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if (is_null($val)) { + $where[$key] = [$key, 'null', '']; + } else { + $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; + } } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; } return $this; @@ -2003,9 +2043,10 @@ class Query * @param string $field 日期字段名 * @param string|array $op 比较运算符或者表达式 * @param string|array $range 比较范围 + * @param string $logic AND OR * @return $this */ - public function whereTime($field, $op, $range = null) + public function whereTime($field, $op, $range = null, $logic = 'AND') { if (is_null($range)) { if (is_array($op)) { @@ -2025,9 +2066,7 @@ class Query $op = is_array($range) ? 'between' : '>='; } - $this->where($field, strtolower($op) . ' time', $range); - - return $this; + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); } /** @@ -2036,18 +2075,17 @@ class Query * @param string $field 日期字段名 * @param string $startTime 开始时间 * @param string $endTime 结束时间 + * @param string $logic AND OR * @return $this */ - public function whereBetweenTime($field, $startTime, $endTime = null) + public function whereBetweenTime($field, $startTime, $endTime = null, $logic = 'AND') { if (is_null($endTime)) { $time = is_string($startTime) ? strtotime($startTime) : $startTime; $endTime = strtotime('+1 day', $time); } - $this->where($field, 'between time', [$startTime, $endTime]); - - return $this; + return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true); } /** -- Gitee From 90db910e3b91c8c1d4f3ff6c52b098187e00fba4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 1 Mar 2018 19:01:05 +0800 Subject: [PATCH 0966/1384] =?UTF-8?q?Collection=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- library/think/model/Collection.php | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 6ff4324b..3701abee 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1367,7 +1367,7 @@ class Query if ($strict) { // 使用严格模式查询 $where = [$field, $op, $condition]; - if ('exp' == $op && is_array($param)) { + if ('exp' == strtolower($op) && is_array($param)) { // 参数绑定 $this->bind($param); } diff --git a/library/think/model/Collection.php b/library/think/model/Collection.php index 3a9b60f5..1b6061a2 100644 --- a/library/think/model/Collection.php +++ b/library/think/model/Collection.php @@ -16,18 +16,6 @@ use think\Model; class Collection extends BaseCollection { - /** - * 返回数组中指定的一列 - * @access public - * @param string $column_key - * @param string|null $index_key - * @return array - */ - public function column($column_key, $index_key = null) - { - return array_column($this->toArray(), $column_key, $index_key); - } - /** * 延迟预载入关联查询 * @access public -- Gitee From 74c608061c4a4595b32c0c669295307fdbe04e0a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 10:48:37 +0800 Subject: [PATCH 0967/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=B1=BBreadTransform=E6=96=B9=E6=B3=95=E5=AF=B9=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/Attribute.php | 61 +++++++++++++++-------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index ceeae508..c91cd835 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -453,36 +453,51 @@ trait Attribute $value = $this->formatDateTime($value, $this->dateFormat); } } elseif ($notFound) { - $relation = $this->isRelationAttr($name); + $value = $this->getRelationAttribute($name, $item); + } - if ($relation) { - $modelRelation = $this->$relation(); - if ($modelRelation instanceof Relation) { - $value = $this->getRelationData($modelRelation); + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $name 属性名 + * @param array $item 数据 + * @return mixed + */ + protected function getRelationAttribute($name, &$item) + { + $relation = $this->isRelationAttr($name); + + if ($relation) { + $modelRelation = $this->$relation(); + if ($modelRelation instanceof Relation) { + $value = $this->getRelationData($modelRelation); - if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; - if (isset($item[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $item[$key] = $value ? $value->getAttr($attr) : null; - } + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; } - return false; } - // 保存关联对象值 - $this->relation[$name] = $value; - - return $value; + return false; } + + // 保存关联对象值 + $this->relation[$name] = $value; + + return $value; } - throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); } - return $value; + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); } /** @@ -540,7 +555,11 @@ trait Attribute $value = empty($value) ? new \stdClass() : json_decode($value); break; case 'serialize': - $value = unserialize($value); + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } break; default: if (false !== strpos($type, '\\')) { -- Gitee From 26d1a20844435b1e14c3ec1afad7ff01fad39be9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 10:48:58 +0800 Subject: [PATCH 0968/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Btrace=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/debug/Html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/debug/Html.php b/library/think/debug/Html.php index ed4c6268..85f354af 100644 --- a/library/think/debug/Html.php +++ b/library/think/debug/Html.php @@ -49,7 +49,7 @@ class Html return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 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() - Container::get('app')->getBeginMem()) / 1024, 2); -- Gitee From 9fe927673a731f45a2d59976a51e0c1d28aa5385 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 12:06:22 +0800 Subject: [PATCH 0969/1384] =?UTF-8?q?Log=E7=B1=BB=E5=A2=9E=E5=8A=A0close?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8F=AF=E4=BB=A5=E4=B8=B4=E6=97=B6=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=BD=93=E5=89=8D=E8=AF=B7=E6=B1=82=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=86=99=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Log.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/library/think/Log.php b/library/think/Log.php index d04f8c34..0e8248df 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -49,6 +49,12 @@ class Log implements LoggerInterface */ protected $key; + /** + * 是否允许日志写入 + * @var bool + */ + protected $allowWrite = true; + /** * 应用对象 * @var App @@ -108,6 +114,10 @@ class Log implements LoggerInterface */ public function record($msg, $type = 'info', array $context = []) { + if (!$this->allowWrite) { + return; + } + if (is_string($msg)) { $replace = []; foreach ($context as $key => $val) { @@ -167,6 +177,19 @@ class Log implements LoggerInterface return true; } + /** + * 关闭本次请求日志写入 + * @access public + * @return $this + */ + public function close() + { + $this->allowWrite = false; + $this->log = []; + + return $this; + } + /** * 保存调试信息 * @access public @@ -174,7 +197,7 @@ class Log implements LoggerInterface */ public function save() { - if (empty($this->log)) { + if (empty($this->log) || !$this->allowWrite) { return true; } -- Gitee From ffed6393afe1a7248e95dc3dbead49aa42b603a0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 14:08:48 +0800 Subject: [PATCH 0970/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E4=B8=BA=E7=A9=BA=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3701abee..89ec5a60 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1374,7 +1374,7 @@ class Query } elseif (is_array($field)) { // 解析数组批量查询 return $this->parseArrayWhereItems($field, $logic); - } else { + } elseif (!empty($field)) { // 解析条件单元 $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); } -- Gitee From 2a2ef580f16eddfc1a77eaeb7a1b19553a28fe2b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 14:41:58 +0800 Subject: [PATCH 0971/1384] =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E5=A2=9E=E5=8A=A0max=5Ffiles=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 3ec4cd1c..b3f7f405 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -24,6 +24,7 @@ class File 'file_size' => 2097152, 'path' => '', 'apart_level' => [], + 'max_files' => 0, ]; protected $writed = []; @@ -49,11 +50,23 @@ class File public function save(array $log = []) { if ($this->config['single']) { - $name = is_string($single) ? $single : 'single'; + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; $destination = $this->config['path'] . $name . '.log'; } else { - $cli = PHP_SAPI == 'cli' ? '_cli' : ''; - $destination = $this->config['path'] . date('Ym') . '/' . date('d') . $cli . '.log'; + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + $files = glob($this->config['path'] . '*.log'); + + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } else { + $filename = date('Ym') . '/' . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; } $path = dirname($destination); -- Gitee From 8a34e172ca7b240d8c340470ea92d80aff2e2882 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 15:30:30 +0800 Subject: [PATCH 0972/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index b3f7f405..a3abfc47 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -86,6 +86,8 @@ class File // 独立记录的日志级别 if ($this->config['single']) { $filename = $path . '/' . $name . '_' . $type . '.log'; + } elseif ($this->config['max_files']) { + $filename = $path . '/' . date('Ymd') . '_' . $type . $cli . '.log'; } else { $filename = $path . '/' . date('d') . '_' . $type . $cli . '.log'; } -- Gitee From b0a96511eb46e5066862178bbfc2b0bb787af330 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 2 Mar 2018 15:47:21 +0800 Subject: [PATCH 0973/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=80=E5=AF=B9?= =?UTF-8?q?=E4=B8=80=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsTo.php | 1 + library/think/model/relation/HasOne.php | 1 + 2 files changed, 2 insertions(+) diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 7fd92a1d..2fe77945 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -52,6 +52,7 @@ class BelongsTo extends OneToOne $foreignKey = $this->foreignKey; $relationModel = $this->query + ->removeWhereField($this->localKey) ->where($this->localKey, $this->parent->$foreignKey) ->relation($subRelation) ->find(); diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 4744d505..4a70cb81 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -52,6 +52,7 @@ class HasOne extends OneToOne // 判断关联类型执行查询 $relationModel = $this->query + ->removeWhereField($this->foreignKey) ->where($this->foreignKey, $this->parent->$localKey) ->relation($subRelation) ->find(); -- Gitee From d1301c6eb7c0c89ec1204af28966fd932fdd9025 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 5 Mar 2018 18:14:18 +0800 Subject: [PATCH 0974/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BMISS=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=92=8CAUTO=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 4 +- library/think/route/RuleGroup.php | 70 +++++++++++++++++++++---------- library/think/route/RuleItem.php | 32 +++++--------- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 281a24f2..ce7be4f5 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -700,7 +700,7 @@ class Route */ public function miss($route, $method = '*', array $option = []) { - return $this->rule('', $route, $method, $option)->isMiss(); + return $this->group->addMissRule($route, $method, $option); } /** @@ -711,7 +711,7 @@ class Route */ public function auto($route) { - return $this->rule('', $route)->isAuto(); + return $this->group->addAutoRule($route); } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 5af04dd1..21cb4015 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -139,7 +139,8 @@ class RuleGroup extends Rule $this->afterMatchGroup($request); // 获取当前路由规则 - $rules = $this->getMethodRules($request); + $method = strtolower($request->method()); + $rules = $this->getMethodRules($request, $method); if ($this->parent) { // 合并分组参数 @@ -168,10 +169,10 @@ class RuleGroup extends Rule } } - if (isset($this->auto)) { + if ($this->auto) { // 自动解析URL地址 - $result = new UrlDispatch($this->auto->getRoute() . '/' . $url, ['depr' => $depr, 'auto_search' => false]); - } elseif (isset($this->miss)) { + $result = new UrlDispatch($this->auto . '/' . $url, ['depr' => $depr, 'auto_search' => false]); + } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { // 未匹配所有路由的路由规则处理 $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); } else { @@ -185,12 +186,11 @@ class RuleGroup extends Rule * 获取当前请求的路由规则(包括子分组、资源路由) * @access protected * @param Request $request + * @param string $method * @return array */ - protected function getMethodRules($request) + protected function getMethodRules($request, $method) { - $method = strtolower($request->method()); - return array_merge($this->rules['*'], $this->rules[$method]); } @@ -355,34 +355,62 @@ class RuleGroup extends Rule } /** - * 设置自动路由 + * 获取分组的MISS路由 * @access public - * @param RuleItem $rule 路由规则 - * @return $this + * @return RuleItem|null */ - public function setAutoRule(RuleItem $rule) + public function getMissRule() { - $this->auto = $rule; - return $this; + return $this->miss; } /** - * 设置为MISS路由 + * 获取分组的自动路由 * @access public - * @param RuleItem $rule 路由规则 - * @return $this + * @return string */ - public function setMissRule(RuleItem $rule) + public function getAutoRule() { - $this->miss = $rule; - return $this; + return $this->auto; + } + + /** + * 注册自动路由 + * @access public + * @param string $route 路由规则 + * @return void + */ + public function addAutoRule($route) + { + $this->auto = $route; + } + + /** + * 注册MISS路由 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem + */ + public function addMissRule($route, $method = '*', $option = []) + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option); + + $this->miss = $ruleItem; + + return $ruleItem; } /** * 添加分组下的路由规则或者子分组 * @access public - * @param Rule $rule 路由规则 - * @param string $method 请求类型 + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 * @return $this */ public function addRule($rule, $route, $method = '*', $option = [], $pattern = []) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 4e4c4ab3..5ae766ff 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -104,20 +104,6 @@ class RuleItem extends Rule return $this->rule; } - /** - * 检查后缀 - * @access public - * @param string $ext - * @return $this - */ - public function ext($ext = '') - { - $this->option('ext', $ext); - $this->setRuleName(true); - - return $this; - } - /** * 获取当前路由地址 * @access public @@ -129,24 +115,26 @@ class RuleItem extends Rule } /** - * 设置为自动路由 + * 获取当前路由的请求类型 * @access public - * @return $this + * @return string */ - public function isAuto() + public function getMethod() { - $this->parent->setAutoRule($this); - return $this; + return strtolower($this->method); } /** - * 设置为MISS路由 + * 检查后缀 * @access public + * @param string $ext * @return $this */ - public function isMiss() + public function ext($ext = '') { - $this->parent->setMissRule($this); + $this->option('ext', $ext); + $this->setRuleName(true); + return $this; } -- Gitee From 61355c015dcd5a05675f4656d63353917d34e74a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 5 Mar 2018 18:38:00 +0800 Subject: [PATCH 0975/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 21cb4015..ccb81ec5 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -140,7 +140,7 @@ class RuleGroup extends Rule // 获取当前路由规则 $method = strtolower($request->method()); - $rules = $this->getMethodRules($request, $method); + $rules = $this->getMethodRules($method); if ($this->parent) { // 合并分组参数 @@ -185,11 +185,10 @@ class RuleGroup extends Rule /** * 获取当前请求的路由规则(包括子分组、资源路由) * @access protected - * @param Request $request * @param string $method * @return array */ - protected function getMethodRules($request, $method) + protected function getMethodRules($method) { return array_merge($this->rules['*'], $this->rules[$method]); } -- Gitee From bff1c8ff5a9716eb93a1cc9e0b4fd331a1adafc7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 11:06:16 +0800 Subject: [PATCH 0976/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Query=E7=B1=BB?= =?UTF-8?q?=E7=9A=84getPk=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 89ec5a60..d64d546c 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2099,7 +2099,7 @@ class Query if (!empty($this->pk)) { $pk = $this->pk; } else { - $pk = $this->connection->getPk(is_array($options) ? $options['table'] : $this->getTable()); + $pk = $this->connection->getPk(is_array($options) && isset($options['table']) ? $options['table'] : $this->getTable()); } return $pk; -- Gitee From bbb121fe99c6da6eece3112148f934bccc347c5b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 11:18:50 +0800 Subject: [PATCH 0977/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=9A=84=E5=B8=83=E5=B1=80=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Template.php b/library/think/Template.php index 4061507f..ef4a197f 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -204,7 +204,7 @@ class Template $template = $this->parseTemplateFile($template); if ($template) { - $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); if (!$this->checkCache($cacheFile)) { // 缓存无效 重新模板编译 -- Gitee From 2432823a5af8b8fae95357970f728b85a1d9e1b4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 12:36:08 +0800 Subject: [PATCH 0978/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 46 ++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index d64d546c..c0a25ec3 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1351,7 +1351,7 @@ class Query * @param bool $strict 严格模式 * @return $this */ - protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) + protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false) { if ($field instanceof $this) { $this->options['where'] = $field->getOptions('where'); @@ -1367,23 +1367,22 @@ class Query if ($strict) { // 使用严格模式查询 $where = [$field, $op, $condition]; - if ('exp' == strtolower($op) && is_array($param)) { + if ('exp' == strtolower($op) && !empty($param)) { // 参数绑定 $this->bind($param); } } elseif (is_array($field)) { // 解析数组批量查询 return $this->parseArrayWhereItems($field, $logic); - } elseif (!empty($field)) { + } elseif ($field instanceof \Closure) { + $where = is_string($op) ? [$op, $field] : $field; + $field = ''; + } elseif (is_string($field)) { // 解析条件单元 $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); } if (!empty($where)) { - if ($field instanceof \Closure) { - $field = ''; - } - if (isset($this->options['where'][$logic][$field])) { $this->options['where'][$logic][] = $where; } else { @@ -1406,34 +1405,33 @@ class Query */ protected function parseWhereItem($logic, $field, $op, $condition, $param = []) { - if ($field instanceof \Closure) { - $where = is_string($op) ? [$op, $field] : $field; - } elseif (is_string($field) && preg_match('/[,=\<\'\"\(\s]/', $field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { $where = ['', 'exp', $field]; if (is_array($op)) { // 参数绑定 $this->bind($op); } - } elseif (is_null($op) && is_null($condition)) { - // 字符串查询 - $where = [$field, 'null', '']; } elseif (is_array($op)) { // 同一字段多条件查询 array_unshift($param, $field); $where = $param; - } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { - // null查询 - $where = [$field, $op, '']; - } elseif (is_null($condition)) { - // 字段相等查询 - $where = [$field, '=', $op]; - } else { - $where = [$field, $op, $condition, isset($param[2]) ? $param[2] : null]; - - if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { + } elseif ($field && is_null($condition)) { + if (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { + // null查询 + $where = [$field, $op, '']; + } else { + // 字段相等查询 + $where = is_null($op) ? [$field, 'null', ''] : [$field, '=', $op]; + } + } elseif (strtolower($op) == 'exp') { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; + $where = [$field, 'exp', $condition, $bind]; + if ($bind) { // 参数绑定 - $this->bind($param[2]); + $this->bind($bind); } + } else { + $where = $field ? [$field, $op, $condition] : null; } return $where; -- Gitee From e133a33e74dd54fde3cbfca41653c5bc1a407e3c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 12:48:26 +0800 Subject: [PATCH 0979/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c0a25ec3..203b205a 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1375,7 +1375,7 @@ class Query // 解析数组批量查询 return $this->parseArrayWhereItems($field, $logic); } elseif ($field instanceof \Closure) { - $where = is_string($op) ? [$op, $field] : $field; + $where = $field; $field = ''; } elseif (is_string($field)) { // 解析条件单元 -- Gitee From 1d488363b3b8c48b7e8443725122fa895155fe82 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 14:51:47 +0800 Subject: [PATCH 0980/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3select=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=9A=84=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 524875d9..45db0cff 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -922,9 +922,9 @@ abstract class Connection } } - if (isset($cache) && false !== $resultSet) { + if (!empty($options['cache']) && false !== $resultSet) { // 缓存数据集 - $this->cacheData($key, $resultSet, $cache); + $this->cacheData($key, $resultSet, $options['cache']); } return $resultSet; -- Gitee From 1893eb351b3a66c7bcbd17675bbaf9a3e53d2082 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 15:05:24 +0800 Subject: [PATCH 0981/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Binput=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper.php b/helper.php index 3a931f12..b9a5dfa9 100644 --- a/helper.php +++ b/helper.php @@ -371,7 +371,7 @@ if (!function_exists('input')) { * @param string $filter 过滤方法 * @return mixed */ - function input($key = '', $default = null, $filter = null) + function input($key = '', $default = null, $filter = '') { if (0 === strpos($key, '?')) { $key = substr($key, 1); -- Gitee From 51877dafae4d9873c9e80be166620be5f3694217 Mon Sep 17 00:00:00 2001 From: g1471 <1060853566@qq.com> Date: Wed, 7 Mar 2018 07:46:01 +0000 Subject: [PATCH 0982/1384] Create helper.php --- helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper.php b/helper.php index b9a5dfa9..ea975c3d 100644 --- a/helper.php +++ b/helper.php @@ -654,7 +654,7 @@ if (!function_exists('view')) { * @param string $template 模板文件 * @param array $vars 模板变量 * @param integer $code 状态码 - * @param callable $filer 内容过滤 + * @param callable $filter 内容过滤 * @return \think\response\View */ function view($template = '', $vars = [], $code = 200, $filter = null) -- Gitee From c525c1ed1d5daf5e34b41c04a441df47e53b0a89 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 17:22:29 +0800 Subject: [PATCH 0983/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=96=AD=E7=BA=BF?= =?UTF-8?q?=E9=87=8D=E8=BF=9E=E7=9A=84=E4=BF=A1=E6=81=AF=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 45db0cff..80b10e0d 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1790,6 +1790,7 @@ abstract class Connection 'SSL connection has been closed unexpectedly', 'Error writing data to the connection', 'Resource deadlock avoided', + 'failed with errno', ]; $error = $e->getMessage(); -- Gitee From 3b9a0b35a2f30bcfc6ec1ac2063e09f8d440dc1e Mon Sep 17 00:00:00 2001 From: g1471 <1060853566@qq.com> Date: Wed, 7 Mar 2018 09:37:05 +0000 Subject: [PATCH 0984/1384] Create Model.php --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index a24722aa..433e6d09 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -389,7 +389,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 检查数据是否允许写入 * @access protected - * @param array $autoFields 自动完成的字段列表 + * @param array $append 自动完成的字段列表 * @return array */ protected function checkAllowFields(array $append = []) -- Gitee From 8f2b8671ba78329279f9b325e5463da81f539c58 Mon Sep 17 00:00:00 2001 From: g1471 <1060853566@qq.com> Date: Wed, 7 Mar 2018 09:58:52 +0000 Subject: [PATCH 0985/1384] Create App.php --- library/think/App.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 9b3f7290..6e634b4f 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -409,9 +409,9 @@ class App implements \ArrayAccess * @param string $type 信息类型 * @return void */ - public function log($log, $type = 'info') + public function log($msg, $type = 'info') { - $this->debug && $this->log->record($log, $type); + $this->debug && $this->log->record($msg, $type); } /** -- Gitee From 5c6094c0cd3e991758a32cae37510e55b2eec9ab Mon Sep 17 00:00:00 2001 From: g1471 <1060853566@qq.com> Date: Wed, 7 Mar 2018 09:39:26 +0000 Subject: [PATCH 0986/1384] Create Paginator.php --- library/think/Paginator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Paginator.php b/library/think/Paginator.php index 85766f34..e2f9b898 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -104,8 +104,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J * @param $items * @param $listRows * @param null $currentPage - * @param bool $simple * @param null $total + * @param bool $simple * @param array $options * @return Paginator */ -- Gitee From 123c43c7bc137dc5ecf67ed63246e5c38a80b796 Mon Sep 17 00:00:00 2001 From: g1471 <1060853566@qq.com> Date: Wed, 7 Mar 2018 09:49:11 +0000 Subject: [PATCH 0987/1384] Create Route.php --- library/think/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Route.php b/library/think/Route.php index ce7be4f5..8d8915ba 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -582,7 +582,7 @@ class Route * 注册重定向路由 * @access public * @param string|array $rule 路由规则 - * @param string $template 路由模板地址 + * @param string $route 路由地址 * @param array $status 状态码 * @param array $option 路由参数 * @param array $pattern 变量规则 -- Gitee From fd52ac71b3e952d9f7dd15363532458e0bb5d0fc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 7 Mar 2018 18:34:59 +0800 Subject: [PATCH 0988/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 984332f5..90c5d833 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -1322,7 +1322,7 @@ class Validate $rule = '/^' . $rule . '$/'; } - return 1 === preg_match($rule, (string) $value); + return is_scalar($value) && 1 === preg_match($rule, (string) $value); } /** -- Gitee From 0291209823dfc95cb0a3e768e9474b20a7a9e74f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 8 Mar 2018 17:49:04 +0800 Subject: [PATCH 0989/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=8C=85=E7=9A=84=E5=8A=A0=E8=BD=BD=E9=A1=BA=E5=BA=8F=20?= =?UTF-8?q?=E6=94=BE=E5=88=B0app=5Finit=E4=B9=8B=E5=89=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 6e634b4f..38e502ac 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -228,6 +228,9 @@ class App implements \ArrayAccess // 设置系统时区 date_default_timezone_set($this->config('app.default_timezone')); + // 读取语言包 + $this->loadLangPack(); + // 监听app_init $this->hook->listen('app_init'); } @@ -298,10 +301,10 @@ class App implements \ArrayAccess */ public function run() { - // 初始化应用 - $this->initialize(); - try { + // 初始化应用 + $this->initialize(); + if ($this->bind) { // 模块/控制器绑定 $this->route->bind($this->bind); @@ -313,21 +316,6 @@ class App implements \ArrayAccess } } - // 读取默认语言 - $this->lang->range($this->config('app.default_lang')); - if ($this->config('app.lang_switch_on')) { - // 开启多语言机制 检测当前语言 - $this->lang->detect(); - } - - $this->request->langset($this->lang->range()); - - // 加载系统语言包 - $this->lang->load([ - $this->thinkPath . 'lang/' . $this->request->langset() . '.php', - $this->appPath . 'lang/' . $this->request->langset() . '.php', - ]); - // 监听app_dispatch $this->hook->listen('app_dispatch'); @@ -390,6 +378,24 @@ class App implements \ArrayAccess return $response; } + protected function loadLangPack() + { + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->langset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang/' . $this->request->langset() . '.php', + $this->appPath . 'lang/' . $this->request->langset() . '.php', + ]); + } + /** * 设置当前请求的调度信息 * @access public -- Gitee From 91dcaca6927a9c0b434027474cc43d7273ca2cee Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 9 Mar 2018 10:58:14 +0800 Subject: [PATCH 0990/1384] =?UTF-8?q?controller=E7=B1=BBfetch=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=94=B9=E4=B8=BAfinal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index de02e370..98e46179 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -116,7 +116,7 @@ class Controller * @param array $config 模板参数 * @return mixed */ - protected function fetch($template = '', $vars = [], $config = []) + final protected function fetch($template = '', $vars = [], $config = []) { if ('' === $template) { $trace = debug_backtrace(false, 2); -- Gitee From 5a4c7599734d8d1390d8a9116479de2eb13f8683 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 9 Mar 2018 16:13:48 +0800 Subject: [PATCH 0991/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=86=E7=BB=84?= =?UTF-8?q?=E6=88=96=E8=80=85=E8=B5=84=E6=BA=90=E8=B7=AF=E7=94=B1=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E5=BC=80=E5=A4=B4=E6=97=A0=E6=B3=95=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index ccb81ec5..4ce37ca7 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -210,7 +210,7 @@ class RuleGroup extends Rule $str = $this->fullName; } - if (0 !== stripos(str_replace('|', '/', $url), $str)) { + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { return false; } } -- Gitee From e40b72e3d7c53182d07b0801bd90ce4fbf8539ea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 9 Mar 2018 18:03:50 +0800 Subject: [PATCH 0992/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=8F=98=E9=87=8F=E6=94=AF=E6=8C=81=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 991a40c7..2501f8ce 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -576,7 +576,9 @@ abstract class Rule // 替换路由地址中的变量 if (is_string($route) && !empty($matches)) { foreach ($matches as $key => $val) { - if (false !== strpos($route, ':' . $key)) { + if (false !== strpos($route, '<' . $key . '>')) { + $route = str_replace('<' . $key . '>', $val, $route); + } elseif (false !== strpos($route, ':' . $key)) { $route = str_replace(':' . $key, $val, $route); } } -- Gitee From 5eb8c3abb87ef2af06a169c7f6da7ec9cdb73546 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 9 Mar 2018 23:22:11 +0800 Subject: [PATCH 0993/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A0=B9=E8=B7=AF?= =?UTF-8?q?=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 6 +++++- library/think/route/RuleItem.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 4ce37ca7..fa3a0f53 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -296,11 +296,15 @@ class RuleGroup extends Rule foreach ($rules as $key => $item) { if ($item instanceof RuleItem) { $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; if (false === strpos($rule, '<')) { - if (($complete && 0 === strcasecmp($rule, $url)) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { return $item->checkHasMatchRule($request, $url); } diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 5ae766ff..dae90d43 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -299,8 +299,12 @@ class RuleItem extends Rule $url = $depr . str_replace('|', $depr, $url); $rule = $depr . str_replace('/', $depr, $this->rule); + if ($depr == $rule && $depr != $url) { + return false; + } + if (false === strpos($rule, '<')) { - if (($completeMatch && 0 === strcasecmp($rule, $url)) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { return $var; } return false; -- Gitee From a803c8641e4ea18bdf5aefc1bf2e52b67e92bc34 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 12 Mar 2018 15:20:01 +0800 Subject: [PATCH 0994/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BXMLResponse=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BC=A0=E5=85=A5=E7=BC=96=E7=A0=81=E8=BF=87?= =?UTF-8?q?=E7=9A=84xml=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/Xml.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/think/response/Xml.php b/library/think/response/Xml.php index f92b99c7..9c1681a4 100644 --- a/library/think/response/Xml.php +++ b/library/think/response/Xml.php @@ -41,6 +41,15 @@ class Xml extends Response */ protected function output($data) { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $data; + } + // XML数据转换 return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); } -- Gitee From 6a7e1e0ed25db98f44533e787b543e1d9dd361d3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 15 Mar 2018 14:00:21 +0800 Subject: [PATCH 0995/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Query=E7=B1=BBview?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=9A=84=E6=95=B0=E7=BB=84=E8=A1=A8=E5=90=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 203b205a..636ae505 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -854,6 +854,7 @@ class Query { if (is_array($join)) { $table = $join; + $alias = array_shift($join); } else { $join = trim($join); @@ -1057,9 +1058,9 @@ class Query { $this->options['view'] = true; - if (is_array($join) && key($join) !== 0) { + if (is_array($join) && key($join) === 0) { foreach ($join as $key => $val) { - $this->view($key, $val[0], isset($val[1]) ? $val[1] : null, isset($val[2]) ? $val[2] : 'INNER'); + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); } } else { $fields = []; -- Gitee From 0efb6c512bc1d0c3dc61366a0d06c39673dab75a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 15 Mar 2018 15:25:02 +0800 Subject: [PATCH 0996/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=9A=84=E6=A8=A1=E5=9E=8B=E9=97=AD=E5=8C=85=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 2501f8ce..d2e1d606 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -220,7 +220,9 @@ abstract class Rule */ public function model($var, $model = null, $exception = true) { - if (is_array($var)) { + if ($var instanceof \Closure) { + $this->option['model'][] = $var; + } elseif (is_array($var)) { $this->option['model'] = $var; } elseif (is_null($model)) { $this->option['model']['id'] = [$var, true]; -- Gitee From 7b149a1c8015eafc64832e1dc4c760af6762459e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 15 Mar 2018 16:01:13 +0800 Subject: [PATCH 0997/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E7=BB=84?= =?UTF-8?q?=E7=9A=84=E5=8F=98=E9=87=8F=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index fa3a0f53..2a592146 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -145,6 +145,8 @@ class RuleGroup extends Rule if ($this->parent) { // 合并分组参数 $this->mergeGroupOptions(); + // 合并分组变量规则 + $this->pattern = array_merge($this->parent->getPattern(), $this->pattern); } if (isset($this->option['complete_match'])) { -- Gitee From 55b463ffdb22262aa8dbcf7bd96c4fbf8305a51b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 15 Mar 2018 16:56:42 +0800 Subject: [PATCH 0998/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDb=E7=B1=BB?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 4 +++- library/think/db/Query.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index 21770e08..6ce22f39 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -14,6 +14,7 @@ namespace think; /** * Class Db * @package think + * @method \think\db\Query connect(array $config =[], mixed $name = false) static 连接/切换数据库连接 * @method \think\db\Query table(string $table) static 指定数据表(含前缀) * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 @@ -42,7 +43,8 @@ namespace think; * @method void commit() static 用于非自动提交状态下面的查询提交 * @method void rollback() static 事务回滚 * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 - * @method string getLastInsID($sequence = null) static 获取最近插入的ID + * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID + * @method mixed getConfig(string $name = '') static 获取数据库的配置参数 */ class Db { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 636ae505..9cc7011a 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -435,7 +435,7 @@ class Query * 获取数据库的配置参数 * @access public * @param string $name 参数名称 - * @return boolean + * @return mixed */ public function getConfig($name = '') { -- Gitee From 29486b5d1547ae4f74b53c6fec49935b58a7ca6a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 17 Mar 2018 09:46:50 +0800 Subject: [PATCH 0999/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcli-server=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8B=E7=9A=84composer=E8=87=AA=E5=8A=A8=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index ce334dc3..e004a18c 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -60,10 +60,10 @@ class Loader $path = dirname($_SERVER['SCRIPT_FILENAME']); - if (is_file('./think')) { - $rootPath = realpath($path) . '/'; - } else { + if ('cli-server' == PHP_SAPI || !is_file('./think')) { $rootPath = realpath($path . '/../') . '/'; + } else { + $rootPath = realpath($path) . '/'; } self::$composerPath = $rootPath . 'vendor/composer/'; -- Gitee From 7eda2b28c524b7970007dab0ec5ef325fb231b40 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 17 Mar 2018 11:31:46 +0800 Subject: [PATCH 1000/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=BC=82=E5=B8=B8=E6=8D=95=E8=8E=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 1 + library/think/route/RuleItem.php | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 60614550..a4319827 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -67,6 +67,7 @@ return [ 'relation data not exists' => '关联数据不存在', 'relation not support' => '关联不支持', 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', // 上传错误信息 'unknown upload error' => '未知上传错误!', diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index dae90d43..ee3c4c94 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -12,6 +12,7 @@ namespace think\route; use think\Container; +use think\Exception; use think\Route; class RuleItem extends Rule @@ -321,8 +322,12 @@ class RuleItem extends Rule if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); - if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { - return false; + try { + if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); } foreach ($match as $key => $val) { -- Gitee From b6dbf999ed8fd91d2d5a3b8f60ef1a1ece7fbb7e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 17 Mar 2018 11:32:16 +0800 Subject: [PATCH 1001/1384] =?UTF-8?q?=E5=88=86=E7=BB=84=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=9A=84=E8=B7=AF=E7=94=B1=E5=8F=98=E9=87=8F=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=8D=95=E8=8E=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 36 +++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 2a592146..bdb44bc1 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -334,29 +334,33 @@ class RuleGroup extends Rule } } - if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/', $url, $match)) { - $var = []; - foreach ($match as $key => $val) { - if (is_string($key) && '' !== $val) { - list($name, $pos) = explode('_THINK_', $key); - - $var[$name] = $val; + try { + if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/', $url, $match)) { + $var = []; + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + list($name, $pos) = explode('_THINK_', $key); + + $var[$name] = $val; + } } - } - if (!isset($pos)) { - foreach ($regex as $key => $item) { - if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { - $pos = $key; - break; + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { + $pos = $key; + break; + } } } + + return $items[$pos]->checkHasMatchRule($request, $url, $var); } - return $items[$pos]->checkHasMatchRule($request, $url, $var); + return false; + } catch (\Exception $e) { + throw new Exception('route pattern error'); } - - return false; } /** -- Gitee From 0a42f6f831fb4d026cfc1217ed0bf6060a5b658b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Mar 2018 13:10:13 +0800 Subject: [PATCH 1002/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 4 +- library/think/route/RuleItem.php | 103 +++++++++++++----------------- 2 files changed, 46 insertions(+), 61 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index bdb44bc1..78ae7e20 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -307,7 +307,7 @@ class RuleGroup extends Rule if (false === strpos($rule, '<')) { if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { - return $item->checkHasMatchRule($request, $url); + return $item->checkMatchRule($request, $url); } unset($rules[$key]); @@ -354,7 +354,7 @@ class RuleGroup extends Rule } } - return $items[$pos]->checkHasMatchRule($request, $url, $var); + return $items[$pos]->checkMatchRule($request, $url, $var); } return false; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index ee3c4c94..77c7cb67 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -167,14 +167,15 @@ class RuleItem extends Rule /** * 检测路由 - * @access public + * @access private * @param Request $request 请求对象 * @param string $url 访问地址 + * @param array $match 匹配路由变量 * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/', $completeMatch = false) + private function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 @@ -186,20 +187,6 @@ class RuleItem extends Rule return false; } - return $this->checkRule($request, $url, $depr, $completeMatch); - } - - /** - * 检测路由规则 - * @access private - * @param Request $request 请求对象 - * @param string $url URL地址 - * @param string $depr URL分隔符(全局) - * @param bool $completeMatch 路由是否完全匹配 - * @return array|false - */ - private function checkRule($request, $url, $depr, $completeMatch = false) - { // 合并分组参数 $option = $this->mergeGroupOptions(); @@ -208,24 +195,13 @@ class RuleItem extends Rule return false; } - $pattern = array_merge($this->parent->getPattern(), $this->pattern); + $url = $this->urlSuffixCheck($request, $url, $option); - // 是否区分 / 地址访问 - if (!empty($option['remove_slash']) && '/' != $this->rule) { - $this->rule = rtrim($this->rule, '/'); - $url = rtrim($url, '|'); + if (is_null($match)) { + $match = $this->match($url, $option, $depr, $completeMatch); } - if (isset($option['ext'])) { - // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); - } - - if (isset($option['complete_match'])) { - $completeMatch = $option['complete_match']; - } - - if (false !== $match = $this->match($url, $pattern, $option, $depr, $completeMatch)) { + if (false !== $match) { return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); } @@ -233,37 +209,42 @@ class RuleItem extends Rule } /** - * 检测已经匹配的路由 + * 检测路由(含路由匹配) * @access public * @param Request $request 请求对象 * @param string $url 访问地址 - * @param array $var 路由变量 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function checkHasMatchRule($request, $url, $var = []) + public function check($request, $url, $depr = '/', $completeMatch = false) { - if ($dispatch = $this->checkCrossDomain($request)) { - // 允许跨域 - return $dispatch; - } - - // 检查参数有效性 - if (!$this->checkOption($this->option, $request)) { - return false; - } - - // 合并分组参数 - $option = $this->mergeGroupOptions(); - - // 检查前置行为 - if (isset($option['before']) && false === $this->checkBefore($option['before'])) { - return false; - } + return $this->checkRule($request, $url, null, $depr, $completeMatch); + } - if (!empty($option['append'])) { - $request->route($option['append']); - } + /** + * 检测已经匹配的路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $match 匹配路由变量 + * @return Dispatch|false + */ + public function checkMatchRule($request, $url, $match = []) + { + return $this->checkRule($request, $url, $match); + } + /** + * 检测已经匹配的路由 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck($request, $url, $option = []) + { // 是否区分 / 地址访问 if (!empty($option['remove_slash']) && '/' != $this->rule) { $this->rule = rtrim($this->rule, '/'); @@ -275,24 +256,28 @@ class RuleItem extends Rule $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); } - // 匹配到路由规则 - return $this->parseRule($request, $this->rule, $this->route, $url, $option, $var); + return $url; } /** * 检测URL和规则路由是否匹配 * @access private * @param string $url URL地址 - * @param array $pattern 变量规则 * @param array $option 路由参数 * @param string $depr URL分隔符(全局) * @param bool $completeMatch 路由是否完全匹配 * @return array|false */ - private function match($url, $pattern, $option, $depr, $completeMatch) + private function match($url, $option, $depr, $completeMatch) { + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + $pattern = array_merge($this->parent->getPattern(), $this->pattern); + // 检查完整规则定义 - if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { return false; } -- Gitee From 08eabc21aaf7af54d3618ee5f2fb961892dd0e99 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Mar 2018 21:31:57 +0800 Subject: [PATCH 1003/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E6=94=AF=E6=8C=81=20=E8=B7=AF=E7=94=B1=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=A2=9E=E5=8A=A0middleware=E6=96=B9=E6=B3=95=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0make:middleware=20=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Console.php | 1 + .../think/console/command/make/Middleware.php | 36 ++++++++++++++++++ .../command/make/stubs/middleware.stub | 10 +++++ library/think/http/middleware/Dispatcher.php | 37 +++++++++++++------ library/think/route/Rule.php | 28 ++++++++++++++ library/think/route/RuleGroup.php | 7 ++++ 6 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 library/think/console/command/make/Middleware.php create mode 100644 library/think/console/command/make/stubs/middleware.stub diff --git a/library/think/Console.php b/library/think/Console.php index 5cb1ccde..90acd8ff 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -41,6 +41,7 @@ class Console "think\\console\\command\\Clear", "think\\console\\command\\make\\Controller", "think\\console\\command\\make\\Model", + "think\\console\\command\\make\\Middleware", "think\\console\\command\\optimize\\Autoload", "think\\console\\command\\optimize\\Config", "think\\console\\command\\optimize\\Schema", diff --git a/library/think/console/command/make/Middleware.php b/library/think/console/command/make/Middleware.php new file mode 100644 index 00000000..ec7575b2 --- /dev/null +++ b/library/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub() + { + return __DIR__ . '/stubs/middleware.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, 'http') . '\middleware'; + } +} diff --git a/library/think/console/command/make/stubs/middleware.stub b/library/think/console/command/make/stubs/middleware.stub new file mode 100644 index 00000000..333832bb --- /dev/null +++ b/library/think/console/command/make/stubs/middleware.stub @@ -0,0 +1,10 @@ +queue = (array) $middlewares; + $this->queue = $middlewares; } /** @@ -28,7 +29,8 @@ class Dispatcher implements DispatcherInterface */ public function add($middleware) { - $this->assertValid($middleware); + $middleware = $this->makeInstance($middleware); + $this->queue[] = $middleware; } @@ -37,7 +39,8 @@ class Dispatcher implements DispatcherInterface */ public function insert($middleware) { - $this->assertValid($middleware); + $middleware = $this->makeInstance($middleware); + array_unshift($this->queue, $middleware); } @@ -54,8 +57,20 @@ class Dispatcher implements DispatcherInterface */ public function dispatch(Request $request) { - $requestHandler = $this->resolve(); - return call_user_func($requestHandler, $request); + return call_user_func($this->resolve(), $request); + } + + protected function makeInstance($middleware) + { + if ($middleware instanceof \Closure) { + return $middleware; + } + + if (!is_string($middleware)) { + throw new \InvalidArgumentException('The middleware is invalid'); + } + + return false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware; } protected function resolve() @@ -64,6 +79,10 @@ class Dispatcher implements DispatcherInterface $middleware = array_shift($this->queue); if (null !== $middleware) { + if (is_string($middleware)) { + $middleware = [Container::get($middleware), 'handle']; + } + $response = call_user_func($middleware, $request, $this->resolve()); if (!$response instanceof Response) { @@ -77,10 +96,4 @@ class Dispatcher implements DispatcherInterface }; } - protected function assertValid($middleware) - { - if (!is_callable($middleware)) { - throw new \InvalidArgumentException('The middleware is invalid'); - } - } } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index d2e1d606..4631c499 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -304,6 +304,27 @@ abstract class Rule return $this->option('param_depr', $depr); } + /** + * 制定路由中间件 + * @access public + * @param string|\Closure $middleware + * @return $this + */ + public function middleware($middleware) + { + if (empty($this->option['middleware'])) { + $this->option['middleware'] = []; + } + + if (is_array($middleware)) { + $this->option['middleware'] = array_merge($this->option['middleware'], $middleware); + } else { + $this->option['middleware'][] = $middleware; + } + + return $this; + } + /** * 是否合并额外参数 * @access public @@ -616,6 +637,13 @@ abstract class Rule protected function afterMatchRule($request, $option = [], $matches = []) { + // 添加中间件 + if (!empty($option['middleware'])) { + foreach ($option['middleware'] as $middleware) { + Container::get('middlewareDispatcher')->add($middleware); + } + } + // 绑定模型数据 if (isset($option['model'])) { $this->createBindModel($option['model'], $matches); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 78ae7e20..5d4acf76 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -241,6 +241,13 @@ class RuleGroup extends Rule $request->route($this->option['append']); unset($this->option['append']); } + + if (!empty($this->option['middleware'])) { + foreach ($this->option['middleware'] as $middleware) { + Container::get('middlewareDispatcher')->add($middleware); + } + unset($this->option['middleware']); + } } /** -- Gitee From 9060c396b28b6dc58ededa446b7196a0576f2d72 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Mar 2018 22:14:19 +0800 Subject: [PATCH 1004/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/http/middleware/Dispatcher.php | 8 +++----- library/think/route/RuleGroup.php | 14 +++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php index f6466f57..bb551d1a 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/http/middleware/Dispatcher.php @@ -70,7 +70,9 @@ class Dispatcher implements DispatcherInterface throw new \InvalidArgumentException('The middleware is invalid'); } - return false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware; + $class = false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware; + + return [Container::get($class), 'handle']; } protected function resolve() @@ -79,10 +81,6 @@ class Dispatcher implements DispatcherInterface $middleware = array_shift($this->queue); if (null !== $middleware) { - if (is_string($middleware)) { - $middleware = [Container::get($middleware), 'handle']; - } - $response = call_user_func($middleware, $request, $this->resolve()); if (!$response instanceof Response) { diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 5d4acf76..baf0a036 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -228,6 +228,13 @@ class RuleGroup extends Rule */ protected function afterMatchGroup($request) { + if (!empty($this->option['middleware'])) { + foreach ($this->option['middleware'] as $middleware) { + Container::get('middlewareDispatcher')->add($middleware); + } + unset($this->option['middleware']); + } + if (!empty($this->option['response'])) { Container::get('hook')->add('response_send', $this->option['response']); } @@ -241,13 +248,6 @@ class RuleGroup extends Rule $request->route($this->option['append']); unset($this->option['append']); } - - if (!empty($this->option['middleware'])) { - foreach ($this->option['middleware'] as $middleware) { - Container::get('middlewareDispatcher')->add($middleware); - } - unset($this->option['middleware']); - } } /** -- Gitee From d3131a5fede531a9fbeb6f5404bdc296b912b361 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Mar 2018 22:21:45 +0800 Subject: [PATCH 1005/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1middleware=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=94=AF=E6=8C=81=E7=AC=AC=E4=BA=8C=E4=B8=AA=E5=8F=82?= =?UTF-8?q?=E6=95=B0=20=E6=8C=87=E5=AE=9A=E6=98=AF=E5=90=A6=E4=BC=98?= =?UTF-8?q?=E5=85=88=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 4631c499..b9256582 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -305,12 +305,13 @@ abstract class Rule } /** - * 制定路由中间件 + * 指定路由中间件 * @access public * @param string|\Closure $middleware + * @param bool $first * @return $this */ - public function middleware($middleware) + public function middleware($middleware, $first = false) { if (empty($this->option['middleware'])) { $this->option['middleware'] = []; @@ -318,6 +319,8 @@ abstract class Rule if (is_array($middleware)) { $this->option['middleware'] = array_merge($this->option['middleware'], $middleware); + } elseif ($first) { + array_unshift($this->option['middleware'], $middleware); } else { $this->option['middleware'][] = $middleware; } -- Gitee From ea3592dd178d0a0cd98632c5814a7edadc3f2674 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 18 Mar 2018 23:21:26 +0800 Subject: [PATCH 1006/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 38e502ac..5b943eb0 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -347,14 +347,22 @@ class App implements \ArrayAccess $this->config('app.request_cache_except') ); - // 执行调度 - $data = $dispatch->run(); - + $data = null; } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); + $dispatch = null; + $data = $exception->getResponse(); } - $this->middlewareDispatcher->add(function (Request $request, $next) use ($data) { + $this->middlewareDispatcher->add(function (Request $request, $next) use ($dispatch, $data) { + if (is_null($data)) { + try { + // 执行调度 + $data = $dispatch->run(); + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + } + // 输出数据到客户端 if ($data instanceof Response) { $response = $data; -- Gitee From 86cd3698782cd5e349540b99ebb9864675f8d4bd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 19 Mar 2018 14:08:01 +0800 Subject: [PATCH 1007/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E5=AE=9A=E4=B9=89=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 4 ++++ library/think/console/command/make/stubs/middleware.stub | 2 +- library/think/http/middleware/Dispatcher.php | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 5b943eb0..f36e55c4 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -266,6 +266,10 @@ class App implements \ArrayAccess if ('' == $module) { // 加载系统助手函数 include $this->thinkPath . 'helper.php'; + // 加载全局中间件 + if (is_file($path . 'middleware.php')) { + $this->middlewareDispatcher->import(include $path . 'middleware.php'); + } } // 注册服务的容器对象实例 diff --git a/library/think/console/command/make/stubs/middleware.stub b/library/think/console/command/make/stubs/middleware.stub index 333832bb..8b5f12e8 100644 --- a/library/think/console/command/make/stubs/middleware.stub +++ b/library/think/console/command/make/stubs/middleware.stub @@ -4,7 +4,7 @@ namespace {%namespace%}; class {%className%} { - public function handle($request, $next) + public function handle($request, \Closure $next) { } } diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php index bb551d1a..e5b55aa1 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/http/middleware/Dispatcher.php @@ -17,13 +17,18 @@ use think\Response; class Dispatcher implements DispatcherInterface { - protected $queue; + protected $queue = []; public function __construct(array $middlewares = []) { $this->queue = $middlewares; } + public function import(array $middlewares = []) + { + $this->queue = array_merge($this->queue, $middlewares); + } + /** * {@inheritdoc} */ -- Gitee From 4ba7eb02d1843ab8bf815454d15f3bb31a9585ef Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 19 Mar 2018 14:14:16 +0800 Subject: [PATCH 1008/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Boptimize:config?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E5=AF=B9=E5=85=A8=E5=B1=80=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 6 +++--- library/think/console/command/optimize/Config.php | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index f36e55c4..d0a3cd60 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -255,7 +255,7 @@ class App implements \ArrayAccess } else { // 加载行为扩展文件 if (is_file($path . 'tags.php')) { - $this->hook->import(include $path . 'tags.php'); + $this->hook->import(include $path . 'tags.php' ?: []); } // 加载公共文件 @@ -268,13 +268,13 @@ class App implements \ArrayAccess include $this->thinkPath . 'helper.php'; // 加载全局中间件 if (is_file($path . 'middleware.php')) { - $this->middlewareDispatcher->import(include $path . 'middleware.php'); + $this->middlewareDispatcher->import(include $path . 'middleware.php' ?: []); } } // 注册服务的容器对象实例 if (is_file($path . 'provider.php')) { - $this->container->bind(include $path . 'provider.php'); + $this->container->bind(include $path . 'provider.php' ?: []); } // 自动读取配置文件 diff --git a/library/think/console/command/optimize/Config.php b/library/think/console/command/optimize/Config.php index da45dc97..958c00c2 100644 --- a/library/think/console/command/optimize/Config.php +++ b/library/think/console/command/optimize/Config.php @@ -84,6 +84,10 @@ class Config extends Command if ('' == $module) { $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; + + if (is_file($path . 'middleware.php')) { + $content .= PHP_EOL . '\think\Container::get("middlewareDispatcher")->import(' . var_export(include $path . 'middleware.php' ?: [], true) . ');' . PHP_EOL; + } } if (is_file($path . 'provider.php')) { -- Gitee From 9529128350092aa572f32f9121306f62761d6458 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 19 Mar 2018 15:02:52 +0800 Subject: [PATCH 1009/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bconfig=E7=B1=BBhas?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Config.php b/library/think/Config.php index 23df8495..0d56d1ab 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -116,7 +116,7 @@ class Config implements \ArrayAccess $name = $this->prefix . '.' . $name; } - return $this->get($name) ? true : false; + return !is_null($this->get($name)) ? true : false; } /** -- Gitee From c85bedd05ee96562ddae82663b3935af4546693a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 19 Mar 2018 19:22:40 +0800 Subject: [PATCH 1010/1384] =?UTF-8?q?=E4=B8=A5=E6=A0=BC=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 20 +++++++++---------- library/think/Build.php | 6 +++--- library/think/Config.php | 4 ++-- library/think/File.php | 6 +++--- library/think/Loader.php | 16 +++++++-------- library/think/cache/driver/File.php | 4 ++-- library/think/console/command/Clear.php | 2 +- library/think/console/command/RunServer.php | 2 +- .../think/console/command/make/Controller.php | 8 +++++--- .../think/console/command/make/Middleware.php | 2 +- library/think/console/command/make/Model.php | 2 +- .../think/console/command/optimize/Schema.php | 2 +- library/think/db/Connection.php | 2 +- library/think/log/driver/File.php | 14 +++++++------ 14 files changed, 47 insertions(+), 43 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index d0a3cd60..2d0311d9 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -126,7 +126,7 @@ class App implements \ArrayAccess public function __construct($appPath = '') { - $this->appPath = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/'; + $this->appPath = $appPath ?: realpath(dirname(dirname($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR . 'application') . DIRECTORY_SEPARATOR; $this->container = Container::getInstance(); } @@ -163,11 +163,11 @@ class App implements \ArrayAccess { $this->beginTime = microtime(true); $this->beginMem = memory_get_usage(); - $this->thinkPath = dirname(dirname(__DIR__)) . '/'; - $this->rootPath = dirname(realpath($this->appPath)) . '/'; - $this->runtimePath = $this->rootPath . 'runtime/'; - $this->routePath = $this->rootPath . 'route/'; - $this->configPath = $this->rootPath . 'config/'; + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->rootPath = dirname(realpath($this->appPath)) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; // 设置路径环境变量 $this->env->set([ @@ -177,8 +177,8 @@ class App implements \ArrayAccess 'config_path' => $this->configPath, 'route_path' => $this->routePath, 'runtime_path' => $this->runtimePath, - 'extend_path' => $this->rootPath . 'extend/', - 'vendor_path' => $this->rootPath . 'vendor/', + 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, + 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, ]); // 加载环境变量配置文件 @@ -403,8 +403,8 @@ class App implements \ArrayAccess // 加载系统语言包 $this->lang->load([ - $this->thinkPath . 'lang/' . $this->request->langset() . '.php', - $this->appPath . 'lang/' . $this->request->langset() . '.php', + $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', ]); } diff --git a/library/think/Build.php b/library/think/Build.php index 6c34d29c..6de01402 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -135,7 +135,7 @@ class Build // 创建子目录和文件 foreach ($list as $path => $file) { - $modulePath = $this->basePath . $module . '/'; + $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR; if ('__dir__' == $path) { // 生成子目录 foreach ($file as $dir) { @@ -204,7 +204,7 @@ class Build continue; } - $path = $this->basePath . $module . '/' . $layer . '/'; + $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR; $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer); } @@ -251,7 +251,7 @@ class Build $subDir = glob($path . '*', GLOB_ONLYDIR); foreach ($subDir as $dir) { - $content .= $this->buildDirRoute($dir . '/', $namespace, $module, $suffix, $layer . '\\' . basename($dir)); + $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir)); } return $content; diff --git a/library/think/Config.php b/library/think/Config.php index 0d56d1ab..603c655f 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -89,12 +89,12 @@ class Config implements \ArrayAccess { // 如果尚未载入 则动态加载配置文件 $module = Container::get('request')->module(); - $module = $module ? $module . '/' : ''; + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; $app = Container::get('app'); $path = $app->getAppPath() . $module; if (is_dir($path . 'config')) { - $file = $path . 'config/' . $name . $app->getConfigExt(); + $file = $path . 'config' . DIRECTORY_SEPARATOR . $name . $app->getConfigExt(); } elseif (is_dir($app->getConfigPath() . $module)) { $file = $app->getConfigPath() . $module . $name . $app->getConfigExt(); } diff --git a/library/think/File.php b/library/think/File.php index 79b6eeff..e9985690 100644 --- a/library/think/File.php +++ b/library/think/File.php @@ -422,16 +422,16 @@ class File extends SplFileObject } else { switch ($this->rule) { case 'date': - $savename = date('Ymd') . '/' . md5(microtime(true)); + $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) . '/' . substr($hash, 2); + $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') . '/' . md5(microtime(true)); + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); } } } diff --git a/library/think/Loader.php b/library/think/Loader.php index e004a18c..168ed1f8 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -58,15 +58,15 @@ class Loader // 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - $path = dirname($_SERVER['SCRIPT_FILENAME']); + $path = realpath(dirname($_SERVER['SCRIPT_FILENAME'])); if ('cli-server' == PHP_SAPI || !is_file('./think')) { - $rootPath = realpath($path . '/../') . '/'; + $rootPath = dirname($path) . DIRECTORY_SEPARATOR; } else { - $rootPath = realpath($path) . '/'; + $rootPath = $path . DIRECTORY_SEPARATOR; } - self::$composerPath = $rootPath . 'vendor/composer/'; + self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; // Composer自动加载支持 if (is_dir(self::$composerPath)) { @@ -89,13 +89,13 @@ class Loader // 注册命名空间定义 self::addNamespace([ - 'think' => __DIR__ . '/', - 'traits' => __DIR__ . '/../traits/', + 'think' => __DIR__, + 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', ]); // 加载类库映射文件 - if (is_file($rootPath . 'runtime/classmap.php')) { - self::addClassMap(__include_file($rootPath . 'runtime/classmap.php')); + if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); } // 自动加载extend目录 diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index d4c52033..3dff7d6c 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -43,7 +43,7 @@ class File extends Driver } if (empty($this->options['path'])) { - $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache/'; + $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR; } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { $this->options['path'] .= DIRECTORY_SEPARATOR; } @@ -269,7 +269,7 @@ class File extends Driver 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); } diff --git a/library/think/console/command/Clear.php b/library/think/console/command/Clear.php index 4ca0c4ff..9896aea5 100644 --- a/library/think/console/command/Clear.php +++ b/library/think/console/command/Clear.php @@ -34,7 +34,7 @@ class Clear extends Command if ($files) { foreach ($files as $file) { if ('.' != $file && '..' != $file && is_dir($path . $file)) { - array_map('unlink', glob($path . $file . '/*.*')); + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); } elseif ('.gitignore' != $file && is_file($path . $file)) { unlink($path . $file); } diff --git a/library/think/console/command/RunServer.php b/library/think/console/command/RunServer.php index 60edfd98..4dd610b6 100644 --- a/library/think/console/command/RunServer.php +++ b/library/think/console/command/RunServer.php @@ -42,7 +42,7 @@ class RunServer extends Command $host, $port, escapeshellarg($root), - escapeshellarg($root . '/router.php') + escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php') ); $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port)); diff --git a/library/think/console/command/make/Controller.php b/library/think/console/command/make/Controller.php index 511958e3..e4cc5bb5 100644 --- a/library/think/console/command/make/Controller.php +++ b/library/think/console/command/make/Controller.php @@ -31,15 +31,17 @@ class Controller extends Make protected function getStub() { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + if ($this->input->getOption('api')) { - return __DIR__ . '/stubs/controller.api.stub'; + return $stubPath . 'controller.api.stub'; } if ($this->input->getOption('plain')) { - return __DIR__ . '/stubs/controller.plain.stub'; + return $stubPath . 'controller.plain.stub'; } - return __DIR__ . '/stubs/controller.stub'; + return $stubPath . 'controller.stub'; } protected function getClassName($name) diff --git a/library/think/console/command/make/Middleware.php b/library/think/console/command/make/Middleware.php index ec7575b2..bfe821b0 100644 --- a/library/think/console/command/make/Middleware.php +++ b/library/think/console/command/make/Middleware.php @@ -26,7 +26,7 @@ class Middleware extends Make protected function getStub() { - return __DIR__ . '/stubs/middleware.stub'; + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; } protected function getNamespace($appNamespace, $module) diff --git a/library/think/console/command/make/Model.php b/library/think/console/command/make/Model.php index d4e9b5dd..03e6b3fc 100644 --- a/library/think/console/command/make/Model.php +++ b/library/think/console/command/make/Model.php @@ -26,7 +26,7 @@ class Model extends Make protected function getStub() { - return __DIR__ . '/stubs/model.stub'; + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; } protected function getNamespace($appNamespace, $module) diff --git a/library/think/console/command/optimize/Schema.php b/library/think/console/command/optimize/Schema.php index 17997013..61620bad 100644 --- a/library/think/console/command/optimize/Schema.php +++ b/library/think/console/command/optimize/Schema.php @@ -99,7 +99,7 @@ class Schema extends Command $info = $class::getConnection()->getFields($table); $content .= var_export($info, true) . ';'; - file_put_contents(App::getRuntimePath() . 'schema/' . $dbName . '.' . $table . '.php', $content); + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . '.' . $table . '.php', $content); } } diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 80b10e0d..69c64300 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -351,7 +351,7 @@ abstract class Connection if (!isset(self::$info[$schema])) { // 读取缓存 - $cacheFile = Container::get('app')->getRuntimePath() . 'schema/' . $schema . '.php'; + $cacheFile = Container::get('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $schema . '.php'; if (!$this->config['debug'] && is_file($cacheFile)) { $info = include $cacheFile; diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index a3abfc47..f7150922 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -37,7 +37,9 @@ class File } if (empty($this->config['path'])) { - $this->config['path'] = Container::get('app')->getRuntimePath() . 'log/'; + $this->config['path'] = Container::get('app')->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; } } @@ -63,7 +65,7 @@ class File unlink($files[0]); } } else { - $filename = date('Ym') . '/' . date('d') . $cli . '.log'; + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; } $destination = $this->config['path'] . $filename; @@ -85,11 +87,11 @@ class File if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 if ($this->config['single']) { - $filename = $path . '/' . $name . '_' . $type . '.log'; + $filename = $path . DIRECTORY_SEPARATOR . $name . '_' . $type . '.log'; } elseif ($this->config['max_files']) { - $filename = $path . '/' . date('Ymd') . '_' . $type . $cli . '.log'; + $filename = $path . DIRECTORY_SEPARATOR . date('Ymd') . '_' . $type . $cli . '.log'; } else { - $filename = $path . '/' . date('d') . '_' . $type . $cli . '.log'; + $filename = $path . DIRECTORY_SEPARATOR . date('d') . '_' . $type . $cli . '.log'; } $this->write($level, $filename, true); @@ -118,7 +120,7 @@ class File // 检测日志文件大小,超过配置大小则备份日志文件重新生成 if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { try { - rename($destination, dirname($destination) . '/' . time() . '-' . basename($destination)); + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); } catch (\Exception $e) { } -- Gitee From e5d8449bf056599d98e0d4e60a01115d31065042 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 20 Mar 2018 15:11:37 +0800 Subject: [PATCH 1011/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 3d8e5071..ce3b104e 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -387,7 +387,7 @@ abstract class Builder $exp = $this->exp[$exp]; } - $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field); + $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); if (preg_match('/\W/', $bindName)) { // 处理带非单词字符的字段名 -- Gitee From 2f243ba18159f62edb7fa481a7455d6d982d3051 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Mar 2018 12:50:44 +0800 Subject: [PATCH 1012/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E7=BB=84?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=90=8E=E7=9A=84=E8=A1=8C=E4=B8=BA=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 91 ++++++++++++++----------------- library/think/route/RuleGroup.php | 33 ----------- 2 files changed, 42 insertions(+), 82 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index b9256582..44721bb4 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -34,7 +34,7 @@ abstract class Rule // 路由变量规则 protected $pattern = []; // 需要合并的路由参数 - protected $mergeOptions = ['after', 'before', 'model']; + protected $mergeOptions = ['after', 'before', 'model', 'header', 'response', 'append', 'middleware']; abstract public function check($request, $url, $depr = '/'); @@ -127,23 +127,6 @@ abstract class Rule return isset($this->option[$name]) ? $this->option[$name] : null; } - /** - * 附加路由隐式参数 - * @access public - * @param array $append - * @return $this - */ - public function append(array $append = []) - { - if (isset($this->option['append'])) { - $this->option['append'] = array_merge($this->option['append'], $append); - } else { - $this->option['append'] = $append; - } - - return $this; - } - /** * 设置路由请求类型 * @access public @@ -233,6 +216,23 @@ abstract class Rule return $this; } + /** + * 附加路由隐式参数 + * @access public + * @param array $append + * @return $this + */ + public function append(array $append = []) + { + if (isset($this->option['append'])) { + $this->option['append'] = array_merge($this->option['append'], $append); + } else { + $this->option['append'] = $append; + } + + return $this; + } + /** * 绑定验证 * @access public @@ -257,7 +257,8 @@ abstract class Rule */ public function response($response) { - return $this->option('response', $response); + $this->option['response'][] = $response; + return $this; } /** @@ -269,12 +270,8 @@ abstract class Rule */ public function header($header, $value = null) { - if (empty($this->option['header'])) { - $this->option['header'] = []; - } - if (is_array($header)) { - $this->option['header'] = array_merge($this->option['header'], $header); + $this->option['header'] = $header; } else { $this->option['header'][$header] = $value; } @@ -282,6 +279,26 @@ abstract class Rule return $this; } + /** + * 指定路由中间件 + * @access public + * @param string|\Closure $middleware + * @param bool $first + * @return $this + */ + public function middleware($middleware, $first = false) + { + if (is_array($middleware)) { + $this->option['middleware'] = $middleware; + } elseif ($first && isset($this->option['middleware'])) { + array_unshift($this->option['middleware'], $middleware); + } else { + $this->option['middleware'][] = $middleware; + } + + return $this; + } + /** * 设置路由缓存 * @access public @@ -304,30 +321,6 @@ abstract class Rule return $this->option('param_depr', $depr); } - /** - * 指定路由中间件 - * @access public - * @param string|\Closure $middleware - * @param bool $first - * @return $this - */ - public function middleware($middleware, $first = false) - { - if (empty($this->option['middleware'])) { - $this->option['middleware'] = []; - } - - if (is_array($middleware)) { - $this->option['middleware'] = array_merge($this->option['middleware'], $middleware); - } elseif ($first) { - array_unshift($this->option['middleware'], $middleware); - } else { - $this->option['middleware'][] = $middleware; - } - - return $this; - } - /** * 是否合并额外参数 * @access public @@ -648,7 +641,7 @@ abstract class Rule } // 绑定模型数据 - if (isset($option['model'])) { + if (!empty($option['model'])) { $this->createBindModel($option['model'], $matches); } diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index baf0a036..d8f6dfdc 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -135,9 +135,6 @@ class RuleGroup extends Rule $this->parseGroupRule($this->rule); } - // 分组匹配后执行的行为 - $this->afterMatchGroup($request); - // 获取当前路由规则 $method = strtolower($request->method()); $rules = $this->getMethodRules($method); @@ -220,36 +217,6 @@ class RuleGroup extends Rule return true; } - /** - * 分组匹配后执行的行为 - * @access protected - * @param Request $request - * @return void - */ - protected function afterMatchGroup($request) - { - if (!empty($this->option['middleware'])) { - foreach ($this->option['middleware'] as $middleware) { - Container::get('middlewareDispatcher')->add($middleware); - } - unset($this->option['middleware']); - } - - if (!empty($this->option['response'])) { - Container::get('hook')->add('response_send', $this->option['response']); - } - - // 开启请求缓存 - if (isset($this->option['cache']) && $request->isGet()) { - $this->parseRequestCache($request, $this->option['cache']); - } - - if (!empty($this->option['append'])) { - $request->route($this->option['append']); - unset($this->option['append']); - } - } - /** * 延迟解析分组的路由规则 * @access public -- Gitee From 3dbe9e6dc113633ec3ea25e981d6624d7ba41b76 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Mar 2018 13:44:02 +0800 Subject: [PATCH 1013/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AD=A6=E5=91=8A=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E6=9B=BF=E4=BB=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 1 + library/think/route/Rule.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index a4319827..6e72a6a5 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -68,6 +68,7 @@ return [ 'relation not support' => '关联不支持', 'chunk not support order' => 'Chunk不支持调用order方法', 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', // 上传错误信息 'unknown upload error' => '未知上传错误!', diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 44721bb4..8c88ee64 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -615,6 +615,7 @@ abstract class Rule // 检测路由after行为 if (!empty($option['after'])) { + trigger_error('route behavior will not support'); $dispatch = $this->checkAfter($option['after']); if (false !== $dispatch) { @@ -714,6 +715,7 @@ abstract class Rule */ protected function checkBefore($before) { + trigger_error('route behavior will not support'); $hook = Container::get('hook'); foreach ((array) $before as $behavior) { -- Gitee From c2ad07af40a5392309aa0229e246141996770994 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Mar 2018 15:19:42 +0800 Subject: [PATCH 1014/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E9=A2=9D=E5=A4=96=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/http/middleware/Dispatcher.php | 22 ++++++++++++++------ library/think/route/Rule.php | 14 ++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php index e5b55aa1..c6cb820d 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/http/middleware/Dispatcher.php @@ -34,7 +34,7 @@ class Dispatcher implements DispatcherInterface */ public function add($middleware) { - $middleware = $this->makeInstance($middleware); + $middleware = $this->buildMiddleware($middleware); $this->queue[] = $middleware; } @@ -44,7 +44,7 @@ class Dispatcher implements DispatcherInterface */ public function insert($middleware) { - $middleware = $this->makeInstance($middleware); + $middleware = $this->buildMiddleware($middleware); array_unshift($this->queue, $middleware); } @@ -65,10 +65,14 @@ class Dispatcher implements DispatcherInterface return call_user_func($this->resolve(), $request); } - protected function makeInstance($middleware) + protected function buildMiddleware($middleware) { + if (is_array($middleware)) { + list($middleware, $param) = $middleware; + } + if ($middleware instanceof \Closure) { - return $middleware; + return [$middleware, null]; } if (!is_string($middleware)) { @@ -77,7 +81,11 @@ class Dispatcher implements DispatcherInterface $class = false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware; - return [Container::get($class), 'handle']; + if (strpos($class, ':')) { + list($class, $param) = explode(':', $class, 2); + } + + return [[Container::get($class), 'handle'], isset($param) ? $param : null]; } protected function resolve() @@ -86,7 +94,9 @@ class Dispatcher implements DispatcherInterface $middleware = array_shift($this->queue); if (null !== $middleware) { - $response = call_user_func($middleware, $request, $this->resolve()); + list($call, $param) = $middleware; + + $response = call_user_func_array($call, [$request, $this->resolve(), $param]); if (!$response instanceof Response) { throw new \LogicException('The middleware must return Response instance'); diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 8c88ee64..e8622062 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -282,18 +282,18 @@ abstract class Rule /** * 指定路由中间件 * @access public - * @param string|\Closure $middleware - * @param bool $first + * @param string|array|\Closure $middleware + * @param mixed $param * @return $this */ - public function middleware($middleware, $first = false) + public function middleware($middleware, $param = null) { - if (is_array($middleware)) { + if (is_null($param) && is_array($middleware)) { $this->option['middleware'] = $middleware; - } elseif ($first && isset($this->option['middleware'])) { - array_unshift($this->option['middleware'], $middleware); } else { - $this->option['middleware'][] = $middleware; + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $param]; + } } return $this; -- Gitee From d89cce8a81040e32574c5983873b6d4110bd0728 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Mar 2018 15:51:46 +0800 Subject: [PATCH 1015/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E7=9A=84=E5=AE=9A=E4=B9=89=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BC=A0=E5=85=A5=E9=A2=9D=E5=A4=96=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/http/middleware/Dispatcher.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/http/middleware/Dispatcher.php index c6cb820d..516bc60e 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/http/middleware/Dispatcher.php @@ -19,14 +19,11 @@ class Dispatcher implements DispatcherInterface { protected $queue = []; - public function __construct(array $middlewares = []) - { - $this->queue = $middlewares; - } - public function import(array $middlewares = []) { - $this->queue = array_merge($this->queue, $middlewares); + foreach ($middlewares as $middleware) { + $this->add($middleware); + } } /** -- Gitee From c9b5134ac8bf2123fc9dc932e1d7b20c5d333d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E6=96=87=E5=81=A5?= Date: Wed, 21 Mar 2018 16:44:16 +0800 Subject: [PATCH 1016/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正变量判断错误,该错误会导致count函数的参数为null,然后报错 --- library/think/process/pipes/Windows.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/process/pipes/Windows.php b/library/think/process/pipes/Windows.php index bba7e9b8..1b8b0d4f 100644 --- a/library/think/process/pipes/Windows.php +++ b/library/think/process/pipes/Windows.php @@ -196,7 +196,7 @@ class Windows extends Pipes return; } - if (null !== $w && 0 < count($r)) { + if (null !== $r && 0 < count($r)) { $data = ''; while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { $data .= $dataread; -- Gitee From 3838d853570d5d411a3f99a2e035c676e35b5b55 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Mar 2018 18:58:31 +0800 Subject: [PATCH 1017/1384] =?UTF-8?q?Middleware=E7=B1=BB=E8=B0=83=E6=95=B4?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=20\think\facade\Middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base.php | 37 +++++----- lang/zh-cn.php | 2 + library/think/App.php | 6 +- .../Dispatcher.php => Middleware.php} | 20 +++--- .../Middleware.php} | 20 ++++-- .../http/middleware/DispatcherInterface.php | 45 ------------ .../http/tests/middleware/DispatcherTest.php | 69 ------------------- library/think/route/Rule.php | 2 +- 8 files changed, 53 insertions(+), 148 deletions(-) rename library/think/{http/middleware/Dispatcher.php => Middleware.php} (89%) rename library/think/{http/middleware/MissingResponseException.php => facade/Middleware.php} (50%) delete mode 100644 library/think/http/middleware/DispatcherInterface.php delete mode 100644 library/think/http/tests/middleware/DispatcherTest.php diff --git a/base.php b/base.php index deb5840b..aad321d6 100644 --- a/base.php +++ b/base.php @@ -40,6 +40,7 @@ Container::getInstance()->bind([ 'hook' => Hook::class, 'lang' => Lang::class, 'log' => Log::class, + 'middleware' => Middleware::class, 'request' => Request::class, 'response' => Response::class, 'route' => Route::class, @@ -48,30 +49,30 @@ Container::getInstance()->bind([ 'validate' => Validate::class, 'view' => View::class, 'rule_name' => route\RuleName::class, - 'middlewareDispatcher' => http\middleware\Dispatcher::class, // 接口依赖注入 'think\LoggerInterface' => Log::class, ]); // 注册核心类的静态代理 Facade::bind([ - facade\App::class => App::class, - facade\Build::class => Build::class, - facade\Cache::class => Cache::class, - facade\Config::class => Config::class, - facade\Cookie::class => Cookie::class, - facade\Debug::class => Debug::class, - facade\Env::class => Env::class, - facade\Hook::class => Hook::class, - facade\Lang::class => Lang::class, - facade\Log::class => Log::class, - facade\Request::class => Request::class, - facade\Response::class => Response::class, - facade\Route::class => Route::class, - facade\Session::class => Session::class, - facade\Url::class => Url::class, - facade\Validate::class => Validate::class, - facade\View::class => View::class, + facade\App::class => App::class, + facade\Build::class => Build::class, + facade\Cache::class => Cache::class, + facade\Config::class => Config::class, + facade\Cookie::class => Cookie::class, + facade\Debug::class => Debug::class, + facade\Env::class => Env::class, + facade\Hook::class => Hook::class, + facade\Lang::class => Lang::class, + facade\Log::class => Log::class, + facade\Middleware::class => Middleware::class, + facade\Request::class => Request::class, + facade\Response::class => Response::class, + facade\Route::class => Route::class, + facade\Session::class => Session::class, + facade\Url::class => Url::class, + facade\Validate::class => Validate::class, + facade\View::class => View::class, ]); // 注册类库别名 diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 6e72a6a5..16b1bb7c 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -86,6 +86,8 @@ 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必须', diff --git a/library/think/App.php b/library/think/App.php index 2d0311d9..ee216aca 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -268,7 +268,7 @@ class App implements \ArrayAccess include $this->thinkPath . 'helper.php'; // 加载全局中间件 if (is_file($path . 'middleware.php')) { - $this->middlewareDispatcher->import(include $path . 'middleware.php' ?: []); + $this->middleware->import(include $path . 'middleware.php' ?: []); } } @@ -357,7 +357,7 @@ class App implements \ArrayAccess $data = $exception->getResponse(); } - $this->middlewareDispatcher->add(function (Request $request, $next) use ($dispatch, $data) { + $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { if (is_null($data)) { try { // 执行调度 @@ -382,7 +382,7 @@ class App implements \ArrayAccess return $response; }); - $response = $this->middlewareDispatcher->dispatch($this->request); + $response = $this->middleware->dispatch($this->request); // 监听app_end $this->hook->listen('app_end', $response); diff --git a/library/think/http/middleware/Dispatcher.php b/library/think/Middleware.php similarity index 89% rename from library/think/http/middleware/Dispatcher.php rename to library/think/Middleware.php index 516bc60e..970330c7 100644 --- a/library/think/http/middleware/Dispatcher.php +++ b/library/think/Middleware.php @@ -9,13 +9,9 @@ // | Author: Slince // +---------------------------------------------------------------------- -namespace think\http\middleware; +namespace think; -use think\Container; -use think\Request; -use think\Response; - -class Dispatcher implements DispatcherInterface +class middleware { protected $queue = []; @@ -31,6 +27,10 @@ class Dispatcher implements DispatcherInterface */ public function add($middleware) { + if (is_null($middleware)) { + return; + } + $middleware = $this->buildMiddleware($middleware); $this->queue[] = $middleware; @@ -39,8 +39,12 @@ class Dispatcher implements DispatcherInterface /** * {@inheritdoc} */ - public function insert($middleware) + public function unshift($middleware) { + if (is_null($middleware)) { + return; + } + $middleware = $this->buildMiddleware($middleware); array_unshift($this->queue, $middleware); @@ -101,7 +105,7 @@ class Dispatcher implements DispatcherInterface return $response; } else { - throw new MissingResponseException('The queue was exhausted, with no response returned'); + throw new \InvalidArgumentException('The queue was exhausted, with no response returned'); } }; } diff --git a/library/think/http/middleware/MissingResponseException.php b/library/think/facade/Middleware.php similarity index 50% rename from library/think/http/middleware/MissingResponseException.php rename to library/think/facade/Middleware.php index 7cdcb33d..46827964 100644 --- a/library/think/http/middleware/MissingResponseException.php +++ b/library/think/facade/Middleware.php @@ -6,10 +6,22 @@ // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- -// | Author: Slince +// | Author: liu21st // +---------------------------------------------------------------------- -namespace think\http\middleware; +namespace think\facade; -class MissingResponseException extends \InvalidArgumentException -{} +use think\Facade; + +/** + * @see \think\Middleware + * @mixin \think\Middleware + * @method void import(array $middlewares = []) static 批量设置中间件 + * @method void add(mixed $middleware) static 添加中间件到队列 + * @method void unshift(mixed $middleware) static 添加中间件到队列开头 + * @method array all() static 获取中间件队列 + * @method \think\Response dispatch(\think\Request $request) static 执行中间件调度 + */ +class Middleware extends Facade +{ +} diff --git a/library/think/http/middleware/DispatcherInterface.php b/library/think/http/middleware/DispatcherInterface.php deleted file mode 100644 index 1693f609..00000000 --- a/library/think/http/middleware/DispatcherInterface.php +++ /dev/null @@ -1,45 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\http\middleware; - -use think\Request; -use think\Response; - -interface DispatcherInterface -{ - /** - * 在队尾添加 middleware - * @param callable $middleware - * @return DispatcherInterface - */ - public function add($middleware); - - /** - * 在队前插入 middleware - * @param callable $middleware - * @return DispatcherInterface - */ - public function insert($middleware); - - /** - * 获取所有的middleware - * @return array - */ - public function all(); - - /** - * 处理 request 并返回 response - * @param Request $request - * @return Response - */ - public function dispatch(Request $request); -} diff --git a/library/think/http/tests/middleware/DispatcherTest.php b/library/think/http/tests/middleware/DispatcherTest.php deleted file mode 100644 index 298cbccc..00000000 --- a/library/think/http/tests/middleware/DispatcherTest.php +++ /dev/null @@ -1,69 +0,0 @@ -add(function () { - }); - $this->assertCount(1, $dispatcher->all()); - $this->expectException(\InvalidArgumentException::class); - $dispatcher->add('foo middleware'); - } - - public function testAddAndInsert() - { - $middleware1 = function () {}; - $middleware2 = function () {}; - $dispatcher = new Dispatcher(); - $dispatcher->add($middleware1); - $dispatcher->insert($middleware2); - $this->assertSame([$middleware2, $middleware1], $dispatcher->all()); - } - - public function testDispatch() - { - $middleware1 = function ($request, $next) { - return $next($request); - }; - $middleware2 = function ($request) { - return Response::create('hello world'); - }; - $dispatcher = new Dispatcher([$middleware1, $middleware2]); - $response = $dispatcher->dispatch(new Request()); - $this->assertInstanceOf(Response::class, $response); - $this->assertEquals('hello world', $response->getContent()); - } - - public function testDispatchWithoutResponse() - { - $middleware1 = function ($request, $next) { - return $next($request); - }; - $middleware2 = function ($request, $next) { - return $next($request); - }; - $dispatcher = new Dispatcher([$middleware1, $middleware2]); - $this->expectException(MissingResponseException::class); - $dispatcher->dispatch(new Request()); - } - - public function testDispatchWithBadResponse() - { - $middleware = function ($request, $next) { - return 'invalid response'; - }; - $dispatcher = new Dispatcher($middleware); - $this->expectException(\LogicException::class); - $dispatcher->dispatch(new Request()); - } -} diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index e8622062..36cc13a4 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -637,7 +637,7 @@ abstract class Rule // 添加中间件 if (!empty($option['middleware'])) { foreach ($option['middleware'] as $middleware) { - Container::get('middlewareDispatcher')->add($middleware); + Container::get('middleware')->add($middleware); } } -- Gitee From 6c01cc5e63eafc4e26c08955475cecd41f2a6e24 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 21 Mar 2018 21:50:55 +0800 Subject: [PATCH 1018/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 970330c7..b59b7237 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -11,7 +11,7 @@ namespace think; -class middleware +class Middleware { protected $queue = []; -- Gitee From dbda9d8b3dda81800aec452424eb51bc0c1f9125 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 11:07:44 +0800 Subject: [PATCH 1019/1384] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=A2=9E=E5=8A=A0break=5Fmatch=5Fstr=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E8=BF=BD=E5=8A=A0=E9=A2=9D=E5=A4=96=E7=9A=84=E7=9F=AD?= =?UTF-8?q?=E7=BA=BF=E6=A0=87=E8=AF=86=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 37 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 69c64300..142e8044 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -106,6 +106,8 @@ abstract class Connection 'query' => '\\think\\db\\Query', // 是否需要断线重连 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], ]; // PDO连接参数 @@ -117,6 +119,21 @@ abstract class Connection PDO::ATTR_EMULATE_PREPARES => false, ]; + // 服务器断线标识字符 + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + // 绑定参数 protected $bind = []; @@ -494,6 +511,10 @@ abstract class Connection // 记录当前字段属性大小写设置 $this->attrCase = $params[PDO::ATTR_CASE]; + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + try { if (empty($config['dsn'])) { $config['dsn'] = $this->parseDsn($config); @@ -1779,23 +1800,9 @@ abstract class Connection return false; } - $info = [ - 'server has gone away', - 'no connection to the server', - 'Lost connection', - 'is dead or not enabled', - 'Error while sending', - 'decryption failed or bad record mac', - 'server closed the connection unexpectedly', - 'SSL connection has been closed unexpectedly', - 'Error writing data to the connection', - 'Resource deadlock avoided', - 'failed with errno', - ]; - $error = $e->getMessage(); - foreach ($info as $msg) { + foreach ($this->breakMatchStr as $msg) { if (false !== stripos($error, $msg)) { return true; } -- Gitee From 15ba365cfccc9eb3d9bb700687124a125496390f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 11:12:13 +0800 Subject: [PATCH 1020/1384] =?UTF-8?q?=E6=9B=B4=E6=96=B0redis=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/Redis.php | 37 +++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index ad4de7af..3ba61fdb 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -74,7 +74,7 @@ class Redis extends Driver */ public function has($name) { - return $this->handler->get($this->getCacheKey($name)) ? true : false; + return $this->handler->exists($this->getCacheKey($name)); } /** @@ -203,4 +203,39 @@ class Redis extends Driver return $this->handler->flushDB(); } +/** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + // 没有过期参数时,使用setnx + if (!$expire) { + $key = $this->getCacheKey($name); + $val = $this->serialize($value); + $res = $this->handler->setnx($key, $val); + if ($res) { + $this->writeTimes++; + return $value; + } else { + return $this->get($name); + } + } + + if ($this->has($name)) { + return $this->get($name); + } else { + $this->set($name, $value, $expire); + } + + return $value; + } } -- Gitee From 891fbec69b312cdb3fa572bf5ab8ac6cc2255692 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 11:45:11 +0800 Subject: [PATCH 1021/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BFacade=E7=B1=BB?= =?UTF-8?q?=E7=9A=84instance=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Facade.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/Facade.php b/library/think/Facade.php index 2cda381c..c455f662 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -88,6 +88,10 @@ class Facade */ public static function instance(...$args) { + if (__CLASS__ != static::class) { + return self::__callStatic('instance', $args); + } + return self::createFacade('', $args); } -- Gitee From 9d490e9328e98296946d936f01e31cfb6aa9e434 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 12:37:50 +0800 Subject: [PATCH 1022/1384] =?UTF-8?q?Query=E7=B1=BBgroup=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BC=A0=E5=85=A5=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 14 +++++++++++++- library/think/db/Query.php | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ce3b104e..ec3020f7 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -845,7 +845,19 @@ abstract class Builder */ protected function parseGroup(Query $query, $group) { - return !empty($group) ? ' GROUP BY ' . $this->parseKey($query, $group) : ''; + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); } /** diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 9cc7011a..68b03a13 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1789,7 +1789,7 @@ class Query /** * 指定group查询 * @access public - * @param string $group GROUP + * @param string|array $group GROUP * @return $this */ public function group($group) -- Gitee From 7b33d45a38d9c56179bd30ea407cbf3a85778f64 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 14:17:26 +0800 Subject: [PATCH 1023/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/dispatch/Module.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 20bb812d..c0062133 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -54,7 +54,7 @@ class Module extends Dispatch $this->app->init($module); // 加载当前模块语言包 - $this->app['lang']->load($this->app->getAppPath() . $module . '/lang/' . $this->app['request']->langset() . '.php'); + $this->app['lang']->load($this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->app['request']->langset() . '.php'); // 模块请求缓存检查 $this->app['request']->cache( @@ -72,7 +72,7 @@ class Module extends Dispatch } // 当前模块路径 - $this->app->setModulePath($this->app->getAppPath() . ($module ? $module . '/' : '')); + $this->app->setModulePath($this->app->getAppPath() . ($module ? $module . DIRECTORY_SEPARATOR : '')); // 是否自动转换控制器和操作名 $convert = is_bool($this->convert) ? $this->convert : $this->app->config('app.url_convert'); -- Gitee From c416686bc80bab486640812f27fa31defb055d9f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 14:19:58 +0800 Subject: [PATCH 1024/1384] =?UTF-8?q?Controller=E7=B1=BBfetch=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=8F=96=E6=B6=88final?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index 98e46179..de02e370 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -116,7 +116,7 @@ class Controller * @param array $config 模板参数 * @return mixed */ - final protected function fetch($template = '', $vars = [], $config = []) + protected function fetch($template = '', $vars = [], $config = []) { if ('' === $template) { $trace = debug_backtrace(false, 2); -- Gitee From e30ec381152969417c5a126153bba4fe3b5299f2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 15:51:02 +0800 Subject: [PATCH 1025/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0route=5Frule=5Fmerg?= =?UTF-8?q?e=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0=20=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=85=A8=E5=B1=80=E8=B7=AF=E7=94=B1=E8=A7=84?= =?UTF-8?q?=E5=88=99=E7=9A=84=E5=90=88=E5=B9=B6=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/App.php | 8 +++-- library/think/Route.php | 52 +++++++++++++++++++++++++++---- library/think/route/RuleGroup.php | 1 + 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/convention.php b/convention.php index 01046c4f..cf8edc9a 100644 --- a/convention.php +++ b/convention.php @@ -89,6 +89,8 @@ return [ 'url_lazy_route' => false, // 是否强制使用路由 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, // 路由是否完全匹配 'route_complete_match' => false, // 使用注解路由 diff --git a/library/think/App.php b/library/think/App.php index ee216aca..a7c54d43 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -326,8 +326,12 @@ class App implements \ArrayAccess // 获取应用调度信息 $dispatch = $this->dispatch; if (empty($dispatch)) { - // 进行URL路由检测 - $this->route->lazy($this->config('app.url_lazy_route')); + // 路由检测 + $this->route + ->lazy($this->config('app.url_lazy_route')) + ->autoSearchController($this->config('app.controller_auto_search')) + ->mergeRuleRegex($this->config('app.route_rule_merge')); + $dispatch = $this->routeCheck(); } diff --git a/library/think/Route.php b/library/think/Route.php index 8d8915ba..81f914b9 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -107,9 +107,20 @@ class Route */ protected $lazy = true; - public function __construct(Request $request, Config $config) + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = true; + + /** + * 路由解析自动搜索多级控制器 + * @var bool + */ + protected $autoSearchController = true; + + public function __construct(Request $request) { - $this->config = $config; $this->request = $request; $this->host = $this->request->host(); @@ -128,6 +139,32 @@ class Route return $this; } + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 设置路由自动解析是否搜索多级控制器 + * @access public + * @param bool $auto 是否自动搜索多级控制器 + * @return $this + */ + public function autoSearchController($auto = true) + { + $this->autoSearchController = $auto; + return $this; + } + /** * 初始化默认域名 * @access protected @@ -216,7 +253,9 @@ class Route if (!isset($this->domains[$domainName])) { $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) - ->lazy($this->lazy); + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); + $this->domains[$domainName] = $domain; } else { $domain = $this->domains[$domainName]; @@ -401,7 +440,7 @@ class Route public function setCrossDomainRule($rule, $method = '*') { if (!isset($this->cross)) { - $this->cross = new RuleGroup($this); + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); } $this->cross->addRuleItem($rule, $method); @@ -440,7 +479,8 @@ class Route } return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern)) - ->lazy($this->lazy); + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); } /** @@ -746,7 +786,7 @@ class Route } // 默认路由解析 - return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]); + return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->autoSearchController]); } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index d8f6dfdc..20d6a1b7 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -12,6 +12,7 @@ namespace think\route; use think\Container; +use think\Exception; use think\Request; use think\Response; use think\Route; -- Gitee From 1bd805afca86f47190bef9c106a3342331cdf227 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 16:01:05 +0800 Subject: [PATCH 1026/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84=E7=9A=84=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 20d6a1b7..5b00d05a 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -140,6 +140,10 @@ class RuleGroup extends Rule $method = strtolower($request->method()); $rules = $this->getMethodRules($method); + if (count($rules) == 0) { + return false; + } + if ($this->parent) { // 合并分组参数 $this->mergeGroupOptions(); -- Gitee From ede7434298da65d02e6ecf3f1f30c2b964040c37 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 16:12:54 +0800 Subject: [PATCH 1027/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=9A=84has=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/RelationShip.php | 5 +++-- library/think/model/relation/BelongsTo.php | 3 ++- library/think/model/relation/HasOne.php | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/library/think/model/concern/RelationShip.php b/library/think/model/concern/RelationShip.php index a406a44d..2c993bd2 100644 --- a/library/think/model/concern/RelationShip.php +++ b/library/think/model/concern/RelationShip.php @@ -141,9 +141,10 @@ trait RelationShip * @param mixed $operator 比较操作符 * @param integer $count 个数 * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ - public static function has($relation, $operator = '>=', $count = 1, $id = '*') + public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { $relation = (new static())->$relation(); @@ -151,7 +152,7 @@ trait RelationShip return $relation->hasWhere($operator); } - return $relation->has($operator, $count, $id); + return $relation->has($operator, $count, $id, $joinType); } /** diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 2fe77945..bf2dbe53 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -70,9 +70,10 @@ class BelongsTo extends OneToOne * @param string $operator 比较操作符 * @param integer $count 个数 * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ - public function has($operator = '>=', $count = 1, $id = '*') + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { return $this->parent; } diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 4a70cb81..02c743c5 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -67,9 +67,13 @@ class HasOne extends OneToOne /** * 根据关联条件查询当前模型 * @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))); -- Gitee From bacc11079c6bab04f49052ec9d6f371342a315b5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 17:32:26 +0800 Subject: [PATCH 1028/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 +- library/think/route/RuleGroup.php | 4 ++-- library/think/route/RuleItem.php | 21 ++++----------------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/convention.php b/convention.php index cf8edc9a..f3c0f94c 100644 --- a/convention.php +++ b/convention.php @@ -30,7 +30,7 @@ return [ // 默认JSONP处理方法 'var_jsonp_handler' => 'callback', // 默认时区 - 'default_timezone' => 'PRC', + 'default_timezone' => 'Asia/Shanghai', // 是否开启多语言 'lang_switch_on' => false, // 默认全局过滤方法 用逗号分隔多个 diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 5b00d05a..333c4997 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -286,7 +286,7 @@ class RuleGroup extends Rule if (false === strpos($rule, '<')) { if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { - return $item->checkMatchRule($request, $url); + return $item->checkRule($request, $url, []); } unset($rules[$key]); @@ -333,7 +333,7 @@ class RuleGroup extends Rule } } - return $items[$pos]->checkMatchRule($request, $url, $var); + return $items[$pos]->checkRule($request, $url, $var); } return false; diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 77c7cb67..e2856e35 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -166,8 +166,8 @@ class RuleItem extends Rule } /** - * 检测路由 - * @access private + * 检测匹配到的路由 + * @access protected * @param Request $request 请求对象 * @param string $url 访问地址 * @param array $match 匹配路由变量 @@ -175,7 +175,7 @@ class RuleItem extends Rule * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - private function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false) + protected function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 @@ -223,20 +223,7 @@ class RuleItem extends Rule } /** - * 检测已经匹配的路由 - * @access public - * @param Request $request 请求对象 - * @param string $url 访问地址 - * @param array $match 匹配路由变量 - * @return Dispatch|false - */ - public function checkMatchRule($request, $url, $match = []) - { - return $this->checkRule($request, $url, $match); - } - - /** - * 检测已经匹配的路由 + * URL后缀及Slash检查 * @access protected * @param Request $request 请求对象 * @param string $url 访问地址 -- Gitee From ba837221904ac03e24ce15f7899681675f6395c7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 17:34:35 +0800 Subject: [PATCH 1029/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index e2856e35..b7873b0c 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -166,8 +166,8 @@ class RuleItem extends Rule } /** - * 检测匹配到的路由 - * @access protected + * 检测路由 + * @access public * @param Request $request 请求对象 * @param string $url 访问地址 * @param array $match 匹配路由变量 @@ -175,7 +175,7 @@ class RuleItem extends Rule * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - protected function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false) + public function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 -- Gitee From be85e5579a2b73129129a6546a055c0aa4a5d736 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 18:15:53 +0800 Subject: [PATCH 1030/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BApp=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 15 ++++++++++++--- library/think/console/command/optimize/Config.php | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index a7c54d43..8736f5cc 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -255,7 +255,10 @@ class App implements \ArrayAccess } else { // 加载行为扩展文件 if (is_file($path . 'tags.php')) { - $this->hook->import(include $path . 'tags.php' ?: []); + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $this->hook->import($tags); + } } // 加载公共文件 @@ -268,13 +271,19 @@ class App implements \ArrayAccess include $this->thinkPath . 'helper.php'; // 加载全局中间件 if (is_file($path . 'middleware.php')) { - $this->middleware->import(include $path . 'middleware.php' ?: []); + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); + } } } // 注册服务的容器对象实例 if (is_file($path . 'provider.php')) { - $this->container->bind(include $path . 'provider.php' ?: []); + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->container->bind($provider); + } } // 自动读取配置文件 diff --git a/library/think/console/command/optimize/Config.php b/library/think/console/command/optimize/Config.php index 958c00c2..9b692b33 100644 --- a/library/think/console/command/optimize/Config.php +++ b/library/think/console/command/optimize/Config.php @@ -71,7 +71,10 @@ class Config extends Command // 加载行为扩展文件 if (is_file($path . 'tags.php')) { - $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export(include $path . 'tags.php' ?: [], true)) . ');' . PHP_EOL; + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export($tags, true)) . ');' . PHP_EOL; + } } // 加载公共文件 @@ -86,12 +89,18 @@ class Config extends Command $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; if (is_file($path . 'middleware.php')) { - $content .= PHP_EOL . '\think\Container::get("middlewareDispatcher")->import(' . var_export(include $path . 'middleware.php' ?: [], true) . ');' . PHP_EOL; + $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')) { - $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export(include $path . 'provider.php' ?: [], true) . ');' . PHP_EOL; + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export($provider, true) . ');' . PHP_EOL; + } } $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL; -- Gitee From c92d47dc95af1ce401756696ddc1689927812eb6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 20:18:33 +0800 Subject: [PATCH 1031/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E8=AE=B0=E5=BD=95NOTICE=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 36cc13a4..aecf0bd0 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -615,7 +615,6 @@ abstract class Rule // 检测路由after行为 if (!empty($option['after'])) { - trigger_error('route behavior will not support'); $dispatch = $this->checkAfter($option['after']); if (false !== $dispatch) { @@ -715,7 +714,8 @@ abstract class Rule */ protected function checkBefore($before) { - trigger_error('route behavior will not support'); + Container::get('log')->notice('路由行为建议使用中间件替代!'); + $hook = Container::get('hook'); foreach ((array) $before as $behavior) { @@ -735,6 +735,8 @@ abstract class Rule */ protected function checkAfter($after) { + Container::get('log')->notice('路由行为建议使用中间件替代!'); + $hook = Container::get('hook'); $result = null; -- Gitee From 555b7e5c2911823a94588b330df66b36b2c5b229 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 22 Mar 2018 20:23:48 +0800 Subject: [PATCH 1032/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index aecf0bd0..5ef9c833 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -714,8 +714,6 @@ abstract class Rule */ protected function checkBefore($before) { - Container::get('log')->notice('路由行为建议使用中间件替代!'); - $hook = Container::get('hook'); foreach ((array) $before as $behavior) { @@ -735,7 +733,7 @@ abstract class Rule */ protected function checkAfter($after) { - Container::get('log')->notice('路由行为建议使用中间件替代!'); + Container::get('log')->notice('路由后置行为建议使用中间件替代!'); $hook = Container::get('hook'); -- Gitee From 68489b81058743a59657941076a2ff8844339d87 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Mar 2018 10:41:30 +0800 Subject: [PATCH 1033/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bconsole=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=94=A8=E6=88=B7=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 6 ++++++ library/think/Console.php | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/convention.php b/convention.php index f3c0f94c..4cae2c34 100644 --- a/convention.php +++ b/convention.php @@ -291,4 +291,10 @@ return [ 'list_rows' => 15, ], + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + ], ]; diff --git a/library/think/Console.php b/library/think/Console.php index 90acd8ff..8782aa92 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -49,11 +49,22 @@ class Console "think\\console\\command\\RunServer", ]; - public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) { $this->name = $name; $this->version = $version; + if ($user) { + $this->setUser($user); + } + $this->defaultCommand = 'list'; $this->definition = $this->getDefaultInputDefinition(); @@ -62,13 +73,33 @@ class Console } } + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ public static function init($run = true) { static $console; if (!$console) { - // 实例化console - $console = new self('Think Console', '0.1'); + $config = Container::get('config')->pull('console'); + // 实例化 console + $console = new self($config['name'], $config['version'], $config['user']); // 读取指令集 $file = Container::get('env')->get('app_path') . 'command.php'; -- Gitee From 46abd2e4b7ddf1df37fb409aad1e6f74c05acd3e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 24 Mar 2018 20:30:16 +0800 Subject: [PATCH 1034/1384] =?UTF-8?q?Request=E5=AF=B9=E8=B1=A1=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AF=B9=E8=B1=A1=E6=96=B9=E5=BC=8F=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 84 ++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 6ac15d78..b35b9f70 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1099,37 +1099,12 @@ class Request $files = $this->file; if (!empty($files)) { // 处理上传文件 - $array = []; - foreach ($files as $key => $file) { - if (is_array($file['name'])) { - $item = []; - $keys = array_keys($file); - $count = count($file['name']); - for ($i = 0; $i < $count; $i++) { - if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { - continue; - } - $temp['key'] = $key; - foreach ($keys as $_key) { - $temp[$_key] = $file[$_key][$i]; - } - $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); - } - $array[$key] = $item; - } else { - if ($file instanceof File) { - $array[$key] = $file; - } else { - if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { - continue; - } - $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); - } - } - } + $array = $this->dealUploadFile($files); + if (strpos($name, '.')) { list($name, $sub) = explode('.', $name); } + if ('' === $name) { // 获取全部文件 return $array; @@ -1143,6 +1118,46 @@ class Request return; } + protected function dealUploadFile($files) + { + $array = []; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + + for ($i = 0; $i < $count; $i++) { + if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { + continue; + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { + continue; + } + + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + } + + return $array; + } + /** * 获取环境变量 * @access public @@ -1232,6 +1247,7 @@ class Request } else { $type = 's'; } + // 按.拆分成多维数组进行判断 foreach (explode('.', $name) as $val) { if (isset($data[$val])) { @@ -1241,6 +1257,7 @@ class Request return $default; } } + if (is_object($data)) { return $data; } @@ -1891,4 +1908,15 @@ class Request return $this->cache; } + /** + * 获取请求数据的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->param($name); + } + } -- Gitee From 576f67a26c91885e6b4f022293e4a37f626c638c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Mar 2018 15:19:15 +0800 Subject: [PATCH 1035/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=A7=84=E5=88=99=E7=9A=84=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 5ef9c833..92f1ce9d 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -1019,7 +1019,14 @@ abstract class Rule $name = substr($name, 1, -1); } - $nameRule = isset($pattern[$name]) ? $pattern[$name] : '\w+'; + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = '\w+'; + } return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; } -- Gitee From 6121521b487d7aee383c9b92b61c6eaf155ea9d8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 25 Mar 2018 22:24:40 +0800 Subject: [PATCH 1036/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=B8=B2=E6=9F=93=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 5 +---- library/think/Container.php | 15 +++++++++++++++ library/think/Controller.php | 5 +---- library/think/Request.php | 5 +++-- library/think/route/dispatch/Module.php | 20 +++++++++++++++----- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/helper.php b/helper.php index ea975c3d..18ade67d 100644 --- a/helper.php +++ b/helper.php @@ -660,10 +660,7 @@ if (!function_exists('view')) { function view($template = '', $vars = [], $code = 200, $filter = null) { if ('' === $template) { - $trace = debug_backtrace(false, 2); - $suffix = Container::get('config')->get('app.action_suffix'); - $action = $suffix ? substr($trace[1]['function'], 0, -strlen($suffix)) : $trace[1]['function']; - $template = Loader::parseName($action); + $template = Loader::parseName(request()->action(true)); } return Response::create($template, 'view', $code)->assign($vars)->filter($filter); diff --git a/library/think/Container.php b/library/think/Container.php index 72e3a6ba..b138e600 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -270,6 +270,21 @@ class Container } } + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, $vars = []) + { + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($instance, $args); + } + /** * 调用反射执行callable 支持参数绑定 * @access public diff --git a/library/think/Controller.php b/library/think/Controller.php index de02e370..28909c89 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -119,10 +119,7 @@ class Controller protected function fetch($template = '', $vars = [], $config = []) { if ('' === $template) { - $trace = debug_backtrace(false, 2); - $suffix = $this->app['config']->get('app.action_suffix'); - $action = $suffix ? substr($trace[1]['function'], 0, -strlen($suffix)) : $trace[1]['function']; - $template = Loader::parseName($action); + $template = Loader::parseName($this->request->action(true)); } return $this->view->fetch($template, $vars, $config); diff --git a/library/think/Request.php b/library/think/Request.php index b35b9f70..93ad29ea 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1750,12 +1750,13 @@ class Request */ public function action($action = null) { - if (!is_null($action)) { + if (!is_null($action) && !is_bool($action)) { $this->action = $action; return $this; } - return $this->action ?: ''; + $name = $this->action ?: ''; + return true === $action ? $name : strtolower($name); } /** diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index c0062133..93d5bf10 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -11,6 +11,7 @@ namespace think\route\dispatch; +use ReflectionMethod; use think\Container; use think\exception\ClassNotFoundException; use think\exception\HttpException; @@ -82,10 +83,9 @@ class Module extends Dispatch // 获取操作名 $actionName = strip_tags($result[2] ?: $this->app->config('app.default_action')); - $actionName = $convert ? strtolower($actionName) : $actionName; // 设置当前请求的控制器、操作 - $this->app['request']->controller(Loader::parseName($controller, 1))->action($actionName); + $this->app['request']->controller(Loader::parseName($controller, 1)); // 监听module_init $this->app['hook']->listen('module_init'); @@ -106,14 +106,24 @@ class Module extends Dispatch if (is_callable([$instance, $action])) { // 执行操作方法 $call = [$instance, $action]; + + // 严格获取当前操作方法名 + $reflect = new ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $this->app->config('app.action_suffix'); + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $this->app['request']->action($actionName); + // 自动获取请求变量 $vars = $this->app->config('app.url_param_type') ? $this->app['request']->route() : $this->app['request']->param(); } elseif (is_callable([$instance, '_empty'])) { // 空操作 - $call = [$instance, '_empty']; - $vars = [$actionName]; + $this->app['request']->action($actionName); + $call = [$instance, '_empty']; + $vars = [$actionName]; + $reflect = new ReflectionMethod($instance, '_empty'); } else { // 操作不存在 throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); @@ -121,6 +131,6 @@ class Module extends Dispatch $this->app['hook']->listen('action_begin', $call); - return Container::getInstance()->invokeMethod($call, $vars); + return Container::getInstance()->invokeReflectMethod($instance, $reflect, $vars); } } -- Gitee From 35fff18bcd1fa0580d71623499cf14fe214882e9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 11:35:20 +0800 Subject: [PATCH 1037/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=9A=84=E5=B9=B6=E5=8F=91=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 3dff7d6c..3ff1a012 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -242,7 +242,10 @@ class File extends Driver { $this->writeTimes++; - return $this->unlink($this->getCacheKey($name)); + try { + return $this->unlink($this->getCacheKey($name)); + } catch (\Exception $e) { + } } /** -- Gitee From 65ccc1c109faaaf80a7168f412ec0861d02a0467 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 14:24:13 +0800 Subject: [PATCH 1038/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Binc/dec/exp?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=9A=84=E5=AE=89=E5=85=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 20 ++++++++++++++++++++ library/think/db/Builder.php | 12 +++++++++--- library/think/db/Query.php | 21 +++++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 93ad29ea..6227082c 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -251,6 +251,12 @@ class Request */ protected $isCheckCache; + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + /** * 架构函数 * @access public @@ -1710,6 +1716,20 @@ class Request return $this->dispatch; } + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey() + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid(); + } + + return $this->secureKey; + } + /** * 设置或者获取当前的模块名 * @access public diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ec3020f7..239a0213 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -135,13 +135,19 @@ abstract class Builder } elseif (is_array($val) && !empty($val)) { switch ($val[0]) { case 'exp': - $result[$item] = $val[1]; + if (isset($val[2]) && $query->getSecureKey() == $val[2]) { + $result[$item] = $val[1]; + } break; case 'inc': - $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]); + if ($key == $val[1]) { + $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]); + } break; case 'dec': - $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]); + if ($key == $val[1]) { + $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]); + } break; } } elseif (is_scalar($val)) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 68b03a13..1d96724f 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -58,6 +58,12 @@ class Query */ protected $pk; + /** + * 查询安全Key + * @var string + */ + protected $secureKey; + /** * 当前数据表前缀 * @var string @@ -121,7 +127,8 @@ class Query $this->connection = $connection; } - $this->prefix = $this->connection->getConfig('prefix'); + $this->prefix = $this->connection->getConfig('prefix'); + $this->secureKey = Container::get('request')->secureKey(); } /** @@ -264,6 +271,16 @@ class Query return $this->name ?: $this->model->getName(); } + /** + * 获取查询安全Key + * @access public + * @return string + */ + public function getSecureKey() + { + return $this->secureKey; + } + /** * 得到当前或者指定名称的数据表 * @access public @@ -1041,7 +1058,7 @@ class Query */ public function exp($field, $value) { - $this->data($field, ['exp', $value]); + $this->data($field, ['exp', $value, $this->secureKey]); return $this; } -- Gitee From 5e382b52325f8a48781ae9609c034704c55340a0 Mon Sep 17 00:00:00 2001 From: ThinkPHP Date: Mon, 26 Mar 2018 06:24:35 +0000 Subject: [PATCH 1039/1384] Apply fixes from StyleCI --- library/think/cache/driver/Redis.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 3ba61fdb..2f019608 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -203,14 +203,14 @@ class Redis extends Driver return $this->handler->flushDB(); } -/** - * 如果不存在则写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return mixed - */ + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ public function remember($name, $value, $expire = null) { if (is_null($expire)) { -- Gitee From f59cf597eec140b1c0aa1e5ad3df54c4339367df Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 14:52:28 +0800 Subject: [PATCH 1040/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 6227082c..66b88ce9 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1724,7 +1724,7 @@ class Request public function secureKey() { if (is_null($this->secureKey)) { - $this->secureKey = uniqid(); + $this->secureKey = uniqid('', true); } return $this->secureKey; -- Gitee From 98aaf52dd364af7fdecc7214224678d180676b4f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 15:10:00 +0800 Subject: [PATCH 1041/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 8736f5cc..ae87e597 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.5'; + const VERSION = '5.1.6'; /** * 当前模块路径 -- Gitee From d6ec5a3ed12a839c79dd441c631b1eacd6759378 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 16:42:08 +0800 Subject: [PATCH 1042/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/console/command/optimize/Route.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/console/command/optimize/Route.php b/library/think/console/command/optimize/Route.php index d5f06d00..b6ad5b52 100644 --- a/library/think/console/command/optimize/Route.php +++ b/library/think/console/command/optimize/Route.php @@ -29,7 +29,9 @@ class Route extends Command protected function execute(Input $input, Output $output) { $filename = Container::get('app')->getRuntimePath() . 'route.php'; - unlink($filename); + if (is_file($filename)) { + unlink($filename); + } file_put_contents($filename, $this->buildRouteCache()); $output->writeln('Succeed!'); } -- Gitee From 904f54b05e22d811bdf5635587294492a93cfdff Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 16:54:03 +0800 Subject: [PATCH 1043/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Baction=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/dispatch/Module.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 93d5bf10..ed8e21ec 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -85,7 +85,7 @@ class Module extends Dispatch $actionName = strip_tags($result[2] ?: $this->app->config('app.default_action')); // 设置当前请求的控制器、操作 - $this->app['request']->controller(Loader::parseName($controller, 1)); + $this->app['request']->controller(Loader::parseName($controller, 1))->action($actionName); // 监听module_init $this->app['hook']->listen('module_init'); @@ -120,7 +120,6 @@ class Module extends Dispatch : $this->app['request']->param(); } elseif (is_callable([$instance, '_empty'])) { // 空操作 - $this->app['request']->action($actionName); $call = [$instance, '_empty']; $vars = [$actionName]; $reflect = new ReflectionMethod($instance, '_empty'); -- Gitee From f737a05ec7c21eb77a624478d97a172dade8fa67 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 18:43:20 +0800 Subject: [PATCH 1044/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84append=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Domain.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 12ef39f3..08581908 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -59,6 +59,11 @@ class Domain extends RuleGroup // 检测URL绑定 $result = $this->checkUrlBind($url, $depr); + if (!empty($this->option['append'])) { + $request->route($this->option['append']); + unset($this->option['append']); + } + if (false !== $result) { return $result; } -- Gitee From 2de73229e3312ebe735367437f737207c8054810 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 22:33:55 +0800 Subject: [PATCH 1045/1384] =?UTF-8?q?.gitignore=E7=A7=BB=E9=99=A4composer.?= =?UTF-8?q?lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7e31ef51..4037b829 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/composer.lock /vendor .idea .DS_Store -- Gitee From c131db3a9cb12ddbcd8a99e7a9ffa7a981bd656a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 22:47:33 +0800 Subject: [PATCH 1046/1384] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4037b829..7e31ef51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/composer.lock /vendor .idea .DS_Store -- Gitee From 24e32d94619c5591c41377abf392c83ecc4e3e79 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 26 Mar 2018 22:54:40 +0800 Subject: [PATCH 1047/1384] =?UTF-8?q?.gitignore=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7e31ef51..f7775ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -/composer.lock /vendor -.idea +composer.phar +composer.lock .DS_Store +Thumbs.db +/phpunit.xml +/.idea +/.vscode \ No newline at end of file -- Gitee From 22231687c6e9210b1d0167f04e182a1c0dc8cf52 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 12:07:35 +0800 Subject: [PATCH 1048/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomposer=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index 168ed1f8..d8065f23 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -17,7 +17,7 @@ class Loader * 类名映射信息 * @var array */ - protected static $map = []; + protected static $classMap = []; /** * 类库别名 @@ -76,12 +76,11 @@ class Loader $declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass); - self::$prefixLengthsPsr4 = $composerClass::$prefixLengthsPsr4; - - self::$prefixDirsPsr4 = property_exists($composerClass, 'prefixDirsPsr4') ? $composerClass::$prefixDirsPsr4 : []; - - self::$prefixesPsr0 = property_exists($composerClass, 'prefixesPsr0') ? $composerClass::$prefixesPsr0 : []; - self::$map = property_exists($composerClass, 'classMap') ? $composerClass::$classMap : []; + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'prefixesPsr0', 'classMap'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } } else { self::registerComposerLoader(self::$composerPath); } @@ -129,9 +128,9 @@ class Loader */ private static function findFile($class) { - if (!empty(self::$map[$class])) { + if (!empty(self::$classMap[$class])) { // 类库映射 - return self::$map[$class]; + return self::$classMap[$class]; } // 查找 PSR-4 @@ -186,16 +185,16 @@ class Loader } } - return self::$map[$class] = false; + return self::$classMap[$class] = false; } // 注册classmap public static function addClassMap($class, $map = '') { if (is_array($class)) { - self::$map = array_merge(self::$map, $class); + self::$classMap = array_merge(self::$classMap, $class); } else { - self::$map[$class] = $map; + self::$classMap[$class] = $map; } } -- Gitee From c303aab1b3350d9826d76fad02d0da7a97e95bdb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 12:22:01 +0800 Subject: [PATCH 1049/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Binc/dec=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=94=AF=E6=8C=81=E6=89=B9=E9=87=8F=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20inc(['a'=3D>2,'b'=3D>3])=20=E6=88=96?= =?UTF-8?q?=E8=80=85=20inc(['a','b'],2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1d96724f..1039a7b4 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1020,12 +1020,18 @@ class Query * @param integer $step 增长值 * @return $this */ - public function inc($field, $step = 1) + public function inc($field, $step = 1, $op = 'inc') { $fields = is_string($field) ? explode(',', $field) : $field; - foreach ($fields as $field) { - $this->data($field, ['inc', $field, $step]); + foreach ($fields as $field => $val) { + if (is_numeric($field)) { + $field = $val; + } else { + $step = $val; + } + + $this->data($field, [$op, $field, $step]); } return $this; @@ -1040,13 +1046,7 @@ class Query */ public function dec($field, $step = 1) { - $fields = is_string($field) ? explode(',', $field) : $field; - - foreach ($fields as $field) { - $this->data($field, ['dec', $field, $step]); - } - - return $this; + return $this->inc($field, $step, 'dec'); } /** -- Gitee From 4ef6a5f8c95e4959ef0ec2f058c4617255f6694d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 13:31:20 +0800 Subject: [PATCH 1050/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BResponse=E7=B1=BBcr?= =?UTF-8?q?eate=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Response.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/Response.php b/library/think/Response.php index 071d075c..59660a9d 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -97,9 +97,7 @@ class Response */ public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) { - $type = empty($type) ? 'null' : strtolower($type); - - $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst($type); + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); if (class_exists($class)) { return new $class($data, $code, $header, $options); -- Gitee From da0fafe0784de99148df0afd8d64551156d44509 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 15:01:34 +0800 Subject: [PATCH 1051/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 333c4997..dc2c8cd8 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -194,7 +194,7 @@ class RuleGroup extends Rule */ protected function getMethodRules($method) { - return array_merge($this->rules['*'], $this->rules[$method]); + return $this->rules[$method] + $this->rules['*']; } /** -- Gitee From b52c251efa94732c2cd6e1f0a457c6122b6f9a62 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 15:52:01 +0800 Subject: [PATCH 1052/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Binc/dec=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 8 ++------ library/think/db/Query.php | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 239a0213..7701a6dd 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -140,14 +140,10 @@ abstract class Builder } break; case 'inc': - if ($key == $val[1]) { - $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]); - } + $result[$item] = $item . ' + ' . floatval($val[1]); break; case 'dec': - if ($key == $val[1]) { - $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]); - } + $result[$item] = $item . ' - ' . floatval($val[1]); break; } } elseif (is_scalar($val)) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1039a7b4..e4a19ece 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1031,7 +1031,7 @@ class Query $step = $val; } - $this->data($field, [$op, $field, $step]); + $this->data($field, [$op, $step]); } return $this; -- Gitee From 5fbf129d9881d40ab4a360740a8ca0a60510a361 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 18:27:39 +0800 Subject: [PATCH 1053/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Expression=E7=B1=BB?= =?UTF-8?q?=20field=20ordery=20=E5=92=8C=20where=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E9=83=BD=E6=94=AF=E6=8C=81=E4=BD=BF=E7=94=A8raw=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 38 +++++---- library/think/db/Expression.php | 48 ++++++++++++ library/think/db/Query.php | 134 ++++++++++++++++++++++++++------ 3 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 library/think/db/Expression.php diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 7701a6dd..669b23f2 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -12,6 +12,7 @@ namespace think\db; use PDO; +use think\db\Expression; use think\Exception; abstract class Builder @@ -115,7 +116,10 @@ abstract class Builder foreach ($data as $key => $val) { $item = $this->parseKey($query, $key); - if (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { $val = json_encode($val); } elseif (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 @@ -134,15 +138,10 @@ abstract class Builder $result[$item] = 'NULL'; } elseif (is_array($val) && !empty($val)) { switch ($val[0]) { - case 'exp': - if (isset($val[2]) && $query->getSecureKey() == $val[2]) { - $result[$item] = $val[1]; - } - break; - case 'inc': + case 'INC': $result[$item] = $item . ' + ' . floatval($val[1]); break; - case 'dec': + case 'DEC': $result[$item] = $item . ' - ' . floatval($val[1]); break; } @@ -205,7 +204,9 @@ abstract class Builder $array = []; foreach ($fields as $key => $field) { - if (!is_numeric($key)) { + if ($field instanceof Expression) { + $array[] = $field->getValue(); + } elseif (!is_numeric($key)) { $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field); } else { $array[] = $this->parseKey($query, $field); @@ -292,6 +293,11 @@ abstract class Builder $str = []; foreach ($val as $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $logic . ' ( ' . $value->getValue() . ' )'; + continue; + } + if (is_array($value)) { if (key($value) !== 0) { throw new Exception('where express error:' . var_export($value, true)); @@ -359,7 +365,7 @@ abstract class Builder // 查询规则和条件 if (!is_array($val)) { - $val = is_null($val) ? ['null', ''] : ['=', $val]; + $val = is_null($val) ? ['NULL', ''] : ['=', $val]; } list($exp, $value) = $val; @@ -396,7 +402,9 @@ abstract class Builder $bindName = md5($bindName); } - if (is_object($value) && method_exists($value, '__toString')) { + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { // 对象数据写入 $value = $value->__toString(); } @@ -474,10 +482,10 @@ abstract class Builder * @param integer $bindType * @return string */ - protected function parseExp(Query $query, $key, $exp, $value, $field, $bindName, $bindType) + protected function parseExp(Query $query, $key, $exp, Expression $value, $field, $bindName, $bindType) { // 表达式查询 - return '( ' . $key . ' ' . $value . ' )'; + return '( ' . $key . ' ' . $value->getValue() . ' )'; } /** @@ -802,7 +810,9 @@ abstract class Builder $array = []; foreach ($order as $key => $val) { - if (is_array($val)) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_array($val)) { if (isset($val['sort'])) { $sort = ' ' . $val['sort']; unset($val['sort']); diff --git a/library/think/db/Expression.php b/library/think/db/Expression.php new file mode 100644 index 00000000..f1b92abd --- /dev/null +++ b/library/think/db/Expression.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/library/think/db/Query.php b/library/think/db/Query.php index e4a19ece..1446b4b0 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -948,6 +948,9 @@ class Query { if (empty($field)) { return $this; + } elseif ($field instanceof Expression) { + $this->options['field'][] = $field; + return $this; } if (is_string($field)) { @@ -984,6 +987,23 @@ class Query return $this; } + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field + * @return $this + */ + public function fieldRaw($field, array $bind = []) + { + $this->options['field'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + /** * 设置数据排除字段 * @access public @@ -1020,7 +1040,7 @@ class Query * @param integer $step 增长值 * @return $this */ - public function inc($field, $step = 1, $op = 'inc') + public function inc($field, $step = 1, $op = 'INC') { $fields = is_string($field) ? explode(',', $field) : $field; @@ -1046,7 +1066,7 @@ class Query */ public function dec($field, $step = 1) { - return $this->inc($field, $step, 'dec'); + return $this->inc($field, $step, 'DEC'); } /** @@ -1058,10 +1078,21 @@ class Query */ public function exp($field, $value) { - $this->data($field, ['exp', $value, $this->secureKey]); + $this->data($field, $this->raw($value)); return $this; } + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + /** * 指定JOIN查询字段 * @access public @@ -1190,7 +1221,7 @@ class Query */ public function whereNull($field, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'null', null, [], true); + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); } /** @@ -1202,7 +1233,7 @@ class Query */ public function whereNotNull($field, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'notnull', null, [], true); + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); } /** @@ -1214,7 +1245,7 @@ class Query */ public function whereExists($condition, $logic = 'AND') { - $this->options['where'][strtoupper($logic)][] = ['', 'exists', $condition]; + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; return $this; } @@ -1227,7 +1258,7 @@ class Query */ public function whereNotExists($condition, $logic = 'AND') { - $this->options['where'][strtoupper($logic)][] = ['', 'not exists', $condition]; + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; return $this; } @@ -1241,7 +1272,7 @@ class Query */ public function whereIn($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'in', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); } /** @@ -1254,7 +1285,7 @@ class Query */ public function whereNotIn($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); } /** @@ -1267,7 +1298,7 @@ class Query */ public function whereLike($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'like', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); } /** @@ -1280,7 +1311,7 @@ class Query */ public function whereNotLike($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); } /** @@ -1293,7 +1324,7 @@ class Query */ public function whereBetween($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'between', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); } /** @@ -1306,7 +1337,7 @@ class Query */ public function whereNotBetween($field, $condition, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); } /** @@ -1325,7 +1356,7 @@ class Query $operator = '='; } - return $this->whereExp($field1, $operator . ' ' . $field2, $logic); + return $this->whereExp($field1, $operator . ' ' . $field2, [], $logic); } /** @@ -1348,14 +1379,45 @@ class Query * 指定Exp查询条件 * @access public * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 + * @param string $condition 查询条件 * @param array $bind 参数绑定 * @param string $logic 查询逻辑 and or xor * @return $this */ public function whereExp($field, $condition, $bind = [], $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'exp', $condition, $bind, true); + return $this->parseWhereExp($logic, $field, 'EXP', $condition, $bind, true); + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, array $bind = [], $logic = 'AND') + { + $this->options['where'][$logic][] = $this->raw($where); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, array $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); } /** @@ -1377,15 +1439,22 @@ class Query } $logic = strtoupper($logic); + $op = strtoupper($op); if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { $field = $this->options['via'] . '.' . $field; } - if ($strict) { + if ($field instanceof Expression) { + $where = $field; + if (!empty($param)) { + // 参数绑定 + $this->bind($param); + } + } elseif ($strict) { // 使用严格模式查询 - $where = [$field, $op, $condition]; - if ('exp' == strtolower($op) && !empty($param)) { + $where = [$field, $op, 'EXP' == $op ? $this->raw($condition) : $condition]; + if ('EXP' == $op && !empty($param)) { // 参数绑定 $this->bind($param); } @@ -1424,7 +1493,7 @@ class Query protected function parseWhereItem($logic, $field, $op, $condition, $param = []) { if (preg_match('/[,=\<\'\"\(\s]/', $field)) { - $where = ['', 'exp', $field]; + $where = ['', 'EXP', $this->raw($field)]; if (is_array($op)) { // 参数绑定 $this->bind($op); @@ -1434,16 +1503,16 @@ class Query array_unshift($param, $field); $where = $param; } elseif ($field && is_null($condition)) { - if (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { + if (in_array($op, ['NULL', 'NOTNULL', 'NOT NULL'])) { // null查询 $where = [$field, $op, '']; } else { // 字段相等查询 - $where = is_null($op) ? [$field, 'null', ''] : [$field, '=', $op]; + $where = is_null($op) ? [$field, 'NULL', ''] : [$field, '=', $op]; } - } elseif (strtolower($op) == 'exp') { + } elseif ('EXP' == $op) { $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; - $where = [$field, 'exp', $condition, $bind]; + $where = [$field, 'EXP', $this->raw($condition), $bind]; if ($bind) { // 参数绑定 $this->bind($bind); @@ -1468,7 +1537,7 @@ class Query $where = []; foreach ($field as $key => $val) { if (is_null($val)) { - $where[$key] = [$key, 'null', '']; + $where[$key] = [$key, 'NULL', '']; } else { $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; } @@ -1721,6 +1790,9 @@ class Query { if (empty($field)) { return $this; + } elseif ($field instanceof Expression) { + $this->options['order'][] = $field; + return $this; } if (is_string($field)) { @@ -1753,6 +1825,18 @@ class Query return $this; } + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @return $this + */ + public function orderRaw($field) + { + $this->options['order'][] = $this->raw($field); + return $this; + } + /** * 指定Field排序 order('id',[1,2,3],'desc') * @access public -- Gitee From 0df406ffbc132306c284d17cd8f794fcf1729864 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 18:31:02 +0800 Subject: [PATCH 1054/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1446b4b0..9bc0c1a2 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -58,12 +58,6 @@ class Query */ protected $pk; - /** - * 查询安全Key - * @var string - */ - protected $secureKey; - /** * 当前数据表前缀 * @var string @@ -127,8 +121,7 @@ class Query $this->connection = $connection; } - $this->prefix = $this->connection->getConfig('prefix'); - $this->secureKey = Container::get('request')->secureKey(); + $this->prefix = $this->connection->getConfig('prefix'); } /** @@ -271,16 +264,6 @@ class Query return $this->name ?: $this->model->getName(); } - /** - * 获取查询安全Key - * @access public - * @return string - */ - public function getSecureKey() - { - return $this->secureKey; - } - /** * 得到当前或者指定名称的数据表 * @access public -- Gitee From 3e25f7cb9bbc0fa0dbd16c98bbcfe0aa6d3b2ead Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 27 Mar 2018 18:46:09 +0800 Subject: [PATCH 1055/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bfield=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E8=87=AA=E5=8A=A8=E8=AF=86=E5=88=ABfieldRaw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 9bc0c1a2..5a7e702b 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -937,6 +937,10 @@ class Query } if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + $field = array_map('trim', explode(',', $field)); } @@ -973,7 +977,8 @@ class Query /** * 表达式方式指定查询字段 * @access public - * @param string $field + * @param string $field 字段名 + * @param array $bind 参数绑定 * @return $this */ public function fieldRaw($field, array $bind = []) @@ -1812,11 +1817,17 @@ class Query * 表达式方式指定Field排序 * @access public * @param string $field 排序字段 + * @param array $bind 参数绑定 * @return $this */ - public function orderRaw($field) + public function orderRaw($field, array $bind = []) { $this->options['order'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + return $this; } -- Gitee From b738ebeec4b3b53f2d3c997af597e1a0b4a196ad Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 11:36:46 +0800 Subject: [PATCH 1056/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BBuilder=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 18 +++++++++++++++++- library/think/db/builder/Mysql.php | 8 +++----- library/think/route/dispatch/View.php | 6 ++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 669b23f2..078dffd4 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -12,7 +12,6 @@ namespace think\db; use PDO; -use think\db\Expression; use think\Exception; abstract class Builder @@ -144,6 +143,11 @@ abstract class Builder case 'DEC': $result[$item] = $item . ' - ' . floatval($val[1]); break; + default: + $value = $this->parseArrayData($query, $val); + if ($value) { + $result[$item] = $value; + } } } elseif (is_scalar($val)) { // 过滤非标量数据 @@ -154,6 +158,18 @@ abstract class Builder return $result; } + /** + * 数组数据解析 + * @access protected + * @param Query $query 查询对象 + * @param array $data + * @return mixed + */ + protected function parseArrayData(Query $query, $data) + { + return false; + } + /** * 数据绑定处理 * @access protected diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index d13ac016..f5cd5f40 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -176,17 +176,15 @@ class Mysql extends Builder /** * 数组数据解析 * @access protected - * @param array $data + * @param Query $query 查询对象 + * @param array $data * @return mixed */ - protected function parseArrayData($data) + protected function parseArrayData(Query $query, $data) { list($type, $value) = $data; switch (strtolower($type)) { - case 'exp': - $result = $value; - break; case 'point': $fun = isset($data[2]) ? $data[2] : 'GeomFromText'; $point = isset($data[3]) ? $data[3] : 'POINT'; diff --git a/library/think/route/dispatch/View.php b/library/think/route/dispatch/View.php index 85ef9d28..852c8b9a 100644 --- a/library/think/route/dispatch/View.php +++ b/library/think/route/dispatch/View.php @@ -11,7 +11,7 @@ namespace think\route\dispatch; -use think\Container; +use think\Response; use think\route\Dispatch; class View extends Dispatch @@ -21,8 +21,6 @@ class View extends Dispatch // 渲染模板输出 $vars = array_merge($this->app['request']->param(), $this->param); - return Container::get('view') - ->init(Container::get('config')->pull('template')) - ->fetch($this->dispatch, $vars); + return Response::create($this->dispatch, 'view')->assign($vars); } } -- Gitee From 28d031d7417276d260332ed562fd2c3fa9d100f4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 12:41:44 +0800 Subject: [PATCH 1057/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 3 +++ library/think/route/Resource.php | 22 +++++----------------- library/think/route/RuleGroup.php | 4 +++- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 078dffd4..d6a5df4e 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -186,9 +186,12 @@ abstract class Builder if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) { return $data; } + $key = str_replace(['.', '->'], '_', $key); $name = 'data__' . $key . $suffix; + $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + return ':' . $name; } diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 6873132e..d280cb0b 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -56,23 +56,6 @@ class Resource extends RuleGroup } } - /** - * 解析资源路由规则 - * @access public - * @param mixed $rule 路由规则 - * @return void - */ - public function parseGroupRule($rule) - { - $origin = $this->router->getGroup(); - $this->router->setGroup($this); - - // 生成资源路由的路由规则 - $this->buildResourceRule($this->resource, $this->option); - - $this->router->setGroup($origin); - } - /** * 生成资源路由规则 * @access protected @@ -82,6 +65,9 @@ class Resource extends RuleGroup */ protected function buildResourceRule($rule, $option = []) { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + if (strpos($rule, '.')) { // 注册嵌套资源路由 $array = explode('.', $rule); @@ -112,6 +98,8 @@ class Resource extends RuleGroup $this->addRule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); } + + $this->router->setGroup($origin); } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index dc2c8cd8..9312b7f1 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -128,7 +128,9 @@ class RuleGroup extends Rule } // 解析分组路由 - if ($this->rule) { + if ($this instanceof Resource) { + $this->buildResourceRule($this->resource, $this->option); + } elseif ($this->rule) { if ($this->rule instanceof Response) { return new ResponseDispatch($this->rule); } -- Gitee From 4e72a4747bbaf5ff67a33a2a2e1cbb7e4fdeee7d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 12:44:11 +0800 Subject: [PATCH 1058/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index ae87e597..d928969b 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.6'; + const VERSION = '5.1.7'; /** * 当前模块路径 -- Gitee From fa9e67ff38efbf30a3f03d907825b0826b22dbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E6=96=87=E5=81=A5?= Date: Tue, 27 Mar 2018 22:59:53 +0800 Subject: [PATCH 1059/1384] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 90c5d833..4abfc6b2 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -198,6 +198,7 @@ class Validate * @param array $rules 验证规则 * @param array $message 验证提示信息 * @param array $field 验证字段描述信息 + * @return Validate */ public static function make(array $rules = [], array $message = [], array $field = []) { @@ -1437,7 +1438,7 @@ class Validate * 获取数据验证的场景 * @access protected * @param string $scene 验证场景 - * @return array + * @return void */ protected function getScene($scene = '') { -- Gitee From 2b41be5c9373c0177d5f6d9b85bb5740f83f78a5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 13:11:51 +0800 Subject: [PATCH 1060/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 1 - 1 file changed, 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 5a7e702b..0d88831c 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1427,7 +1427,6 @@ class Query } $logic = strtoupper($logic); - $op = strtoupper($op); if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { $field = $this->options['via'] . '.' . $field; -- Gitee From 54fbaac55d75979b4ed6b717706ac054bfaa49ab Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 13:45:08 +0800 Subject: [PATCH 1061/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bexp=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 0d88831c..1ddce113 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1497,7 +1497,7 @@ class Query // 字段相等查询 $where = is_null($op) ? [$field, 'NULL', ''] : [$field, '=', $op]; } - } elseif ('EXP' == $op) { + } elseif ('EXP' == strtoupper($op)) { $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; $where = [$field, 'EXP', $this->raw($condition), $bind]; if ($bind) { -- Gitee From 73f358f83359b54b2d6a8082b65423cb05c92c13 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 14:02:38 +0800 Subject: [PATCH 1062/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 9312b7f1..4f2f7d14 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -196,7 +196,7 @@ class RuleGroup extends Rule */ protected function getMethodRules($method) { - return $this->rules[$method] + $this->rules['*']; + return array_merge($this->rules[$method], $this->rules['*']); } /** -- Gitee From 99ca3b5fea470a7b1c33d62908c290c6f58703d3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 14:24:30 +0800 Subject: [PATCH 1063/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87m?= =?UTF-8?q?iddleware=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E9=A2=84=E5=85=88?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=B8=AD=E9=97=B4=E4=BB=B6=E5=88=AB=E5=90=8D?= =?UTF-8?q?=EF=BC=8C=E6=96=B9=E4=BE=BF=E5=9C=A8=E8=B7=AF=E7=94=B1=E4=B8=AD?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index b59b7237..296de275 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -80,7 +80,12 @@ class Middleware throw new \InvalidArgumentException('The middleware is invalid'); } - $class = false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware; + if (false === strpos($middleware, '\\')) { + $value = Container::get('config')->get('middleware.' . $middleware); + $class = $value ?: Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware; + } else { + $class = $middleware; + } if (strpos($class, ':')) { list($class, $param) = explode(':', $class, 2); -- Gitee From 6c76d8a2ce231aedece1a27b1e307f74977e9dd6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 15:14:45 +0800 Subject: [PATCH 1064/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 42 +++++++++++++++----------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1ddce113..24949738 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1374,7 +1374,12 @@ class Query */ public function whereExp($field, $condition, $bind = [], $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'EXP', $condition, $bind, true); + $this->options['where'][$logic][] = [$field, 'EXP', $this->raw($condition)]; + + if ($bind) { + $this->bind($bind); + } + return $this; } /** @@ -1433,18 +1438,10 @@ class Query } if ($field instanceof Expression) { - $where = $field; - if (!empty($param)) { - // 参数绑定 - $this->bind($param); - } + return $this->whereRaw($field, is_array($op) ? $op : []); } elseif ($strict) { // 使用严格模式查询 - $where = [$field, $op, 'EXP' == $op ? $this->raw($condition) : $condition]; - if ('EXP' == $op && !empty($param)) { - // 参数绑定 - $this->bind($param); - } + $where = [$field, $op, $condition]; } elseif (is_array($field)) { // 解析数组批量查询 return $this->parseArrayWhereItems($field, $logic); @@ -1452,7 +1449,13 @@ class Query $where = $field; $field = ''; } elseif (is_string($field)) { - // 解析条件单元 + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, $op); + } elseif (is_string($op) && strtolower($op) == 'exp') { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; + return $this->whereExp($field, $condition, $bind, $logic); + } + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); } @@ -1479,13 +1482,7 @@ class Query */ protected function parseWhereItem($logic, $field, $op, $condition, $param = []) { - if (preg_match('/[,=\<\'\"\(\s]/', $field)) { - $where = ['', 'EXP', $this->raw($field)]; - if (is_array($op)) { - // 参数绑定 - $this->bind($op); - } - } elseif (is_array($op)) { + if (is_array($op)) { // 同一字段多条件查询 array_unshift($param, $field); $where = $param; @@ -1497,13 +1494,6 @@ class Query // 字段相等查询 $where = is_null($op) ? [$field, 'NULL', ''] : [$field, '=', $op]; } - } elseif ('EXP' == strtoupper($op)) { - $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; - $where = [$field, 'EXP', $this->raw($condition), $bind]; - if ($bind) { - // 参数绑定 - $this->bind($bind); - } } else { $where = $field ? [$field, $op, $condition] : null; } -- Gitee From e88b9127547d2df8947ff0f5daa50406552bd57e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 15:49:02 +0800 Subject: [PATCH 1065/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 24949738..40d5d22d 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1487,7 +1487,7 @@ class Query array_unshift($param, $field); $where = $param; } elseif ($field && is_null($condition)) { - if (in_array($op, ['NULL', 'NOTNULL', 'NOT NULL'])) { + if (in_array($op, ['NULL', 'NOTNULL', 'NOT NULL'], true)) { // null查询 $where = [$field, $op, '']; } else { -- Gitee From fefc5a0701ae6e123383cbd19580fd88d8661e74 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 15:53:39 +0800 Subject: [PATCH 1066/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bnull=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 40d5d22d..88b19397 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1487,7 +1487,7 @@ class Query array_unshift($param, $field); $where = $param; } elseif ($field && is_null($condition)) { - if (in_array($op, ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { // null查询 $where = [$field, $op, '']; } else { -- Gitee From 5a84836e2813d85c1d128eca2257a5a63bfa659e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 16:20:26 +0800 Subject: [PATCH 1067/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=85=B3=E8=81=94c?= =?UTF-8?q?ount=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsToMany.php | 2 +- library/think/model/relation/HasMany.php | 2 +- library/think/model/relation/MorphMany.php | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index 203c198b..67424926 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -364,7 +364,7 @@ class BelongsToMany extends Relation { return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ [ - 'pivot.' . $this->localKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + 'pivot.' . $this->localKey, 'exp', $this->query->raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), ], ])->fetchSql()->$aggregate($field); } diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 3a882298..aecc2545 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -181,7 +181,7 @@ class HasMany extends Relation } return $this->query - ->where($this->foreignKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) ->fetchSql() ->$aggregate($field); } diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index 2b489113..a50ed8dc 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -224,10 +224,8 @@ class MorphMany extends Relation } return $this->query - ->where([ - [$this->morphKey, 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()], - [$this->morphType, '=', $this->type], - ]) + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) ->fetchSql() ->$aggregate($field); } -- Gitee From cc946e535f1a0336a83ca75d65c696d39567f861 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 18:06:49 +0800 Subject: [PATCH 1068/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3setinc=E5=92=8Csetd?= =?UTF-8?q?ec=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 88b19397..98efcfa6 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -708,7 +708,7 @@ class Query } } - return $this->setField($field, ['inc', $field, $step]); + return $this->setField($field, ['INC', $step]); } /** @@ -740,9 +740,9 @@ class Query return true; } - $value = ['inc', $field, $step]; + $value = ['INC', $step]; } else { - $value = ['dec', $field, $step]; + $value = ['DEC', $step]; } return $this->setField($field, $value); -- Gitee From 7bee866c9b2931ddf930a0ef4c5c2cb9bd1bd8fc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 28 Mar 2018 22:29:12 +0800 Subject: [PATCH 1069/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E6=9F=A5=E8=AF=A2=E8=AF=AD=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 98efcfa6..ddb2bbea 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1390,7 +1390,7 @@ class Query * @param string $logic 查询逻辑 and or xor * @return $this */ - public function whereRaw($where, array $bind = [], $logic = 'AND') + public function whereRaw($where, $bind = [], $logic = 'AND') { $this->options['where'][$logic][] = $this->raw($where); @@ -1408,7 +1408,7 @@ class Query * @param array $bind 参数绑定 * @return $this */ - public function whereOrRaw($where, array $bind = []) + public function whereOrRaw($where, $bind = []) { return $this->whereRaw($where, $bind, 'OR'); } -- Gitee From e7fda3b376787dd99969ba7c5f0b234e19398819 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 29 Mar 2018 12:16:18 +0800 Subject: [PATCH 1070/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E4=B8=8B=E7=9A=84=E6=89=A7=E8=A1=8C=20trace=E8=BE=93?= =?UTF-8?q?=E5=87=BAcli=E6=A8=A1=E5=BC=8F=E4=B8=8B=E9=9D=A2=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 8 +++++++- library/think/Response.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index d928969b..fb59cfd8 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -126,7 +126,7 @@ class App implements \ArrayAccess public function __construct($appPath = '') { - $this->appPath = $appPath ?: realpath(dirname(dirname($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR . 'application') . DIRECTORY_SEPARATOR; + $this->appPath = $appPath ?: $this->getAppPath(); $this->container = Container::getInstance(); } @@ -750,6 +750,12 @@ class App implements \ArrayAccess */ public function getAppPath() { + if (is_null($this->appPath)) { + $scriptName = 'cli' == PHP_SAPI ? getcwd() . DIRECTORY_SEPARATOR . $_SERVER['argv'][0] : $_SERVER['SCRIPT_FILENAME']; + + $this->appPath = realpath(dirname(dirname($scriptName)) . DIRECTORY_SEPARATOR . 'application') . DIRECTORY_SEPARATOR; + } + return $this->appPath; } diff --git a/library/think/Response.php b/library/think/Response.php index 59660a9d..de8feaac 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -121,7 +121,7 @@ class Response $data = $this->getContent(); // Trace调试注入 - if (Container::get('env')->get('app_trace', Container::get('app')->config('app.app_trace'))) { + if ('cli' != PHP_SAPI && Container::get('env')->get('app_trace', Container::get('app')->config('app.app_trace'))) { Container::get('debug')->inject($this, $data); } -- Gitee From 603197e3bc1972d1f4e6da115e339353b16261b0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 29 Mar 2018 23:10:56 +0800 Subject: [PATCH 1071/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0template.auto=5Frul?= =?UTF-8?q?e=20=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0=20=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=BB=98=E8=AE=A4=E6=A8=A1=E6=9D=BF=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E7=9A=84=E6=93=8D=E4=BD=9C=E5=90=8D=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E8=A7=84=E5=88=99=201=20=E5=B0=8F=E5=86=99+?= =?UTF-8?q?=E4=B8=8B=E5=88=92=E7=BA=BF=202=20=E5=85=A8=E5=B0=8F=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ helper.php | 5 ----- library/think/Controller.php | 4 ---- library/think/view/driver/Php.php | 2 +- library/think/view/driver/Think.php | 2 +- 5 files changed, 4 insertions(+), 11 deletions(-) diff --git a/convention.php b/convention.php index 4cae2c34..3e2d31ff 100644 --- a/convention.php +++ b/convention.php @@ -138,6 +138,8 @@ return [ // +---------------------------------------------------------------------- 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, // 模板引擎类型 支持 php think 支持扩展 'type' => 'Think', // 视图基础目录,配置目录为所有模块的视图起始目录 diff --git a/helper.php b/helper.php index 18ade67d..30670f9f 100644 --- a/helper.php +++ b/helper.php @@ -29,7 +29,6 @@ use think\facade\Request; use think\facade\Route; use think\facade\Session; use think\facade\Url; -use think\Loader; use think\Response; use think\route\RuleItem; @@ -659,10 +658,6 @@ if (!function_exists('view')) { */ function view($template = '', $vars = [], $code = 200, $filter = null) { - if ('' === $template) { - $template = Loader::parseName(request()->action(true)); - } - return Response::create($template, 'view', $code)->assign($vars)->filter($filter); } } diff --git a/library/think/Controller.php b/library/think/Controller.php index 28909c89..ffa38186 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -118,10 +118,6 @@ class Controller */ protected function fetch($template = '', $vars = [], $config = []) { - if ('' === $template) { - $template = Loader::parseName($this->request->action(true)); - } - return $this->view->fetch($template, $vars, $config); } diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index e2091aad..0eb124cb 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -139,7 +139,7 @@ class Php if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $request->action(); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); } elseif (false === strpos($template, $depr)) { $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 83daa1c5..7213002d 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -133,7 +133,7 @@ class Think if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $request->action(); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); } elseif (false === strpos($template, $depr)) { $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } -- Gitee From 7051e6b3586108cc73ed94fa6f4fc2f8fc721a88 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 30 Mar 2018 09:42:03 +0800 Subject: [PATCH 1072/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/view/driver/Php.php | 2 ++ library/think/view/driver/Think.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 0eb124cb..6e5db3bd 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -19,6 +19,8 @@ class Php { // 模板引擎参数 protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, // 视图基础目录(集中式) 'view_base' => '', // 模板起始路径 diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 7213002d..137acf85 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -22,6 +22,8 @@ class Think private $template; // 模板引擎参数 protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, // 视图基础目录(集中式) 'view_base' => '', // 模板起始路径 -- Gitee From 4026111fd5c2dbc34c705ef9e94f33099c9f9d6b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 30 Mar 2018 10:02:42 +0800 Subject: [PATCH 1073/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=A0=87=E8=AF=86=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 14 ++++++++++++++ library/think/route/RuleName.php | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index b7873b0c..27e5e754 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -139,6 +139,20 @@ class RuleItem extends Rule return $this; } + /** + * 设置别名 + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + $this->setRuleName(true); + + return $this; + } + /** * 设置路由标识 用于URL反解生成 * @access protected diff --git a/library/think/route/RuleName.php b/library/think/route/RuleName.php index 460ccab3..408a7c93 100644 --- a/library/think/route/RuleName.php +++ b/library/think/route/RuleName.php @@ -25,7 +25,7 @@ class RuleName */ public function set($name, $value, $first = false) { - if ($first) { + if ($first && isset($this->item[$name])) { array_unshift($this->item[$name], $value); } else { $this->item[$name][] = $value; -- Gitee From 25951147cb895ae936bab405bb3def97dbc85233 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 30 Mar 2018 15:45:58 +0800 Subject: [PATCH 1074/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E8=B7=AF=E7=94=B1=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 3 +-- library/think/route/Rule.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 81f914b9..be411148 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -596,8 +596,7 @@ class Route $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); foreach ($this->methodPrefix as $type => $val) { - $item = $this->$type(':action', $val . ':action'); - $group->addRuleItem($item, $type); + $group->addRule('', $val . '', $type); } return $group->prefix($route . '/'); diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 92f1ce9d..5790e4e7 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -97,6 +97,16 @@ abstract class Rule return $this->name; } + /** + * 获取Parent对象 + * @access public + * @return $this|null + */ + public function getParent() + { + return $this->parent; + } + /** * 获取变量规则定义 * @access public -- Gitee From 63b96c53af73a8cfe09135d2ea5d0469a6886a05 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 30 Mar 2018 16:04:59 +0800 Subject: [PATCH 1075/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBip?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E4=BB=A3=E7=90=86IP=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 66b88ce9..e3fbdfc4 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1557,7 +1557,11 @@ class Request return $ip[$type]; } - if ($adv) { + $httpAgentIp = $this->config->get('http_agent_ip'); + + if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { + $ip = $_SERVER[$httpAgentIp]; + } elseif ($adv) { if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); $pos = array_search('unknown', $arr); -- Gitee From 289b36dca7053d992aaec754cba1d493aadd9933 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 31 Mar 2018 09:20:26 +0800 Subject: [PATCH 1076/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3redis=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=E7=9A=84remember=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/Redis.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 2f019608..2aed5396 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -220,6 +220,10 @@ class Redis extends Driver // 没有过期参数时,使用setnx if (!$expire) { $key = $this->getCacheKey($name); + if ($value instanceof \Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } $val = $this->serialize($value); $res = $this->handler->setnx($key, $val); if ($res) { -- Gitee From e5161b995541feb32dd5395f5f8b1de02fddadcb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 31 Mar 2018 11:06:37 +0800 Subject: [PATCH 1077/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E6=94=AF=E6=8C=81=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 296de275..96df84d3 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -33,7 +33,9 @@ class Middleware $middleware = $this->buildMiddleware($middleware); - $this->queue[] = $middleware; + if ($middleware) { + $this->queue[] = $middleware; + } } /** @@ -47,7 +49,9 @@ class Middleware $middleware = $this->buildMiddleware($middleware); - array_unshift($this->queue, $middleware); + if ($middleware) { + array_unshift($this->queue, $middleware); + } } /** @@ -87,6 +91,10 @@ class Middleware $class = $middleware; } + if (is_array($class)) { + return $this->import($class); + } + if (strpos($class, ':')) { list($class, $param) = explode(':', $class, 2); } -- Gitee From b09f1e1e4078ff533f7e1deb3f265908a37eb664 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 31 Mar 2018 11:39:00 +0800 Subject: [PATCH 1078/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8Ccli=E6=89=A7=E8=A1=8C=E4=B8=8B=E7=9A=84composer?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index d8065f23..6354f9e4 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -58,7 +58,9 @@ class Loader // 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - $path = realpath(dirname($_SERVER['SCRIPT_FILENAME'])); + $scriptName = 'cli' == PHP_SAPI ? getcwd() . DIRECTORY_SEPARATOR . $_SERVER['argv'][0] : $_SERVER['SCRIPT_FILENAME']; + + $path = realpath(dirname($scriptName)); if ('cli-server' == PHP_SAPI || !is_file('./think')) { $rootPath = dirname($path) . DIRECTORY_SEPARATOR; -- Gitee From e9ae28e38e86c250f75a7e88dbc1993a1d3d5d4e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 31 Mar 2018 22:05:41 +0800 Subject: [PATCH 1079/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E7=9A=84=E4=B8=AD=E9=97=B4=E4=BB=B6=E6=B3=A8?= =?UTF-8?q?=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Domain.php | 6 ++++++ library/think/route/Rule.php | 11 ++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 08581908..cb75605b 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -68,6 +68,12 @@ class Domain extends RuleGroup return $result; } + // 添加域名中间件 + if (!empty($this->option['middleware'])) { + $this->registerMiddleware($this->option['middleware']); + unset($this->option['middleware']); + } + return parent::check($request, $url, $depr, $completeMatch); } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 5790e4e7..d1efae5a 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -641,13 +641,18 @@ abstract class Rule return $this->dispatch($request, $route, $option); } + protected function registerMiddleware(array $middlewares) + { + foreach ($middlewares as $middleware) { + Container::get('middleware')->add($middleware); + } + } + protected function afterMatchRule($request, $option = [], $matches = []) { // 添加中间件 if (!empty($option['middleware'])) { - foreach ($option['middleware'] as $middleware) { - Container::get('middleware')->add($middleware); - } + $this->registerMiddleware($option['middleware']); } // 绑定模型数据 -- Gitee From 77515ec5bb13286098f40462be77a016838a0354 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 31 Mar 2018 23:24:03 +0800 Subject: [PATCH 1080/1384] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E5=AE=9A=E4=B9=89=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 13 +++++++------ library/think/route/Dispatch.php | 4 ++++ library/think/route/Domain.php | 11 ++++++----- library/think/route/Rule.php | 9 +-------- library/think/route/dispatch/Module.php | 22 ++++++++++++++-------- library/think/route/dispatch/Url.php | 9 +++++++-- 6 files changed, 39 insertions(+), 29 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index fb59cfd8..cf070372 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -269,12 +269,13 @@ class App implements \ArrayAccess if ('' == $module) { // 加载系统助手函数 include $this->thinkPath . 'helper.php'; - // 加载全局中间件 - if (is_file($path . 'middleware.php')) { - $middleware = include $path . 'middleware.php'; - if (is_array($middleware)) { - $this->middleware->import($middleware); - } + } + + // 加载中间件 + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); } } diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index ca6b5d63..ec7e1549 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -32,8 +32,12 @@ abstract class Dispatch $this->dispatch = $dispatch; $this->param = $param; $this->code = $code; + $this->init(); } + protected function init() + {} + public function convert($convert) { $this->convert = $convert; diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index cb75605b..9043017c 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -20,6 +20,8 @@ use think\route\dispatch\Module as ModuleDispatch; class Domain extends RuleGroup { + protected $bind; + /** * 架构函数 * @access public @@ -70,7 +72,7 @@ class Domain extends RuleGroup // 添加域名中间件 if (!empty($this->option['middleware'])) { - $this->registerMiddleware($this->option['middleware']); + Container::get('middleware')->import($this->option['middleware']); unset($this->option['middleware']); } @@ -85,6 +87,7 @@ class Domain extends RuleGroup */ public function bind($bind) { + $this->bind = $bind; $this->router->bind($bind, $this->domain); return $this; @@ -116,10 +119,8 @@ class Domain extends RuleGroup */ private function checkUrlBind($url, $depr = '/') { - $bind = $this->router->getBind($this->domain); - - if (!empty($bind)) { - + if (!empty($this->bind)) { + $bind = $this->bind; $this->parseBindAppendParam($bind); // 记录绑定信息 diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index d1efae5a..8d53c437 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -641,18 +641,11 @@ abstract class Rule return $this->dispatch($request, $route, $option); } - protected function registerMiddleware(array $middlewares) - { - foreach ($middlewares as $middleware) { - Container::get('middleware')->add($middleware); - } - } - protected function afterMatchRule($request, $option = [], $matches = []) { // 添加中间件 if (!empty($option['middleware'])) { - $this->registerMiddleware($option['middleware']); + Container::get('middleware')->import($option['middleware']); } // 绑定模型数据 diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index ed8e21ec..8490bf42 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -20,7 +20,10 @@ use think\route\Dispatch; class Module extends Dispatch { - public function run() + protected $controller; + protected $actionName; + + protected function init() { $result = $this->dispatch; @@ -78,21 +81,25 @@ class Module extends Dispatch // 是否自动转换控制器和操作名 $convert = is_bool($this->convert) ? $this->convert : $this->app->config('app.url_convert'); // 获取控制器名 - $controller = strip_tags($result[1] ?: $this->app->config('app.default_controller')); - $controller = $convert ? strtolower($controller) : $controller; + $controller = strip_tags($result[1] ?: $this->app->config('app.default_controller')); + $this->controller = $convert ? strtolower($controller) : $controller; // 获取操作名 - $actionName = strip_tags($result[2] ?: $this->app->config('app.default_action')); + $this->actionName = strip_tags($result[2] ?: $this->app->config('app.default_action')); // 设置当前请求的控制器、操作 - $this->app['request']->controller(Loader::parseName($controller, 1))->action($actionName); + $this->app['request']->controller(Loader::parseName($this->controller, 1))->action($this->actionName); // 监听module_init $this->app['hook']->listen('module_init'); + } + + public function run() + { // 实例化控制器 try { - $instance = $this->app->controller($controller, + $instance = $this->app->controller($this->controller, $this->app->config('app.url_controller_layer'), $this->app->config('app.controller_suffix'), $this->app->config('app.empty_controller')); @@ -101,7 +108,7 @@ class Module extends Dispatch } // 获取当前操作名 - $action = $actionName . $this->app->config('app.action_suffix'); + $action = $this->actionName . $this->app->config('app.action_suffix'); if (is_callable([$instance, $action])) { // 执行操作方法 @@ -129,7 +136,6 @@ class Module extends Dispatch } $this->app['hook']->listen('action_begin', $call); - return Container::getInstance()->invokeReflectMethod($instance, $reflect, $vars); } } diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 1d5b9d01..f26197aa 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -17,13 +17,18 @@ use think\route\Dispatch; class Url extends Dispatch { - public function run() + protected function init() { // 解析默认的URL规则 $url = str_replace($this->param['depr'], '|', $this->dispatch); $result = $this->parseUrl($url); - return (new Module($result))->run(); + $this->dispatch = new Module($result); + } + + public function run() + { + return $this->dispatch->run(); } /** -- Gitee From 03ce2c12697d8792f6d3fc63d58ebbd43daede80 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 1 Apr 2018 09:27:10 +0800 Subject: [PATCH 1081/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3redis=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/Redis.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 2aed5396..8078e290 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -12,6 +12,7 @@ namespace think\cache\driver; use think\cache\Driver; +use think\Container; /** * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 -- Gitee From 0630dcc200ff0b104a4957efb95e4dc3e0a9b03e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 1 Apr 2018 09:37:20 +0800 Subject: [PATCH 1082/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/Redis.php | 40 ---------------------------- 1 file changed, 40 deletions(-) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 8078e290..77958c33 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -12,7 +12,6 @@ namespace think\cache\driver; use think\cache\Driver; -use think\Container; /** * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 @@ -204,43 +203,4 @@ class Redis extends Driver return $this->handler->flushDB(); } - /** - * 如果不存在则写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return mixed - */ - public function remember($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - - // 没有过期参数时,使用setnx - if (!$expire) { - $key = $this->getCacheKey($name); - if ($value instanceof \Closure) { - // 获取缓存数据 - $value = Container::getInstance()->invokeFunction($value); - } - $val = $this->serialize($value); - $res = $this->handler->setnx($key, $val); - if ($res) { - $this->writeTimes++; - return $value; - } else { - return $this->get($name); - } - } - - if ($this->has($name)) { - return $this->get($name); - } else { - $this->set($name, $value, $expire); - } - - return $value; - } } -- Gitee From 5a5817737c20474c9be494316524c8f22015e9e1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 1 Apr 2018 11:03:01 +0800 Subject: [PATCH 1083/1384] =?UTF-8?q?Log=E6=97=A5=E5=BF=97=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=94=AF=E6=8C=81close=E5=8F=82=E6=95=B0=20=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E5=85=A8=E5=B1=80=E5=85=B3=E9=97=AD=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=86=99=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Log.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/think/Log.php b/library/think/Log.php index 0e8248df..b584ee78 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -80,6 +80,9 @@ class Log implements LoggerInterface $this->config = $config; unset($config['type']); + if (!empty($config['close'])) { + $this->allowWrite = false; + } if (class_exists($class)) { $this->driver = new $class($config); -- Gitee From fe51204b44c09d2c520152a691299642b10d0adb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 1 Apr 2018 11:08:14 +0800 Subject: [PATCH 1084/1384] =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E4=B8=AD=E6=8D=95=E8=8E=B7HttpResponseException?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 96df84d3..09b34f8e 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -11,6 +11,10 @@ namespace think; +use InvalidArgumentException; +use LogicException; +use think\exception\HttpResponseException; + class Middleware { protected $queue = []; @@ -81,7 +85,7 @@ class Middleware } if (!is_string($middleware)) { - throw new \InvalidArgumentException('The middleware is invalid'); + throw new InvalidArgumentException('The middleware is invalid'); } if (false === strpos($middleware, '\\')) { @@ -110,15 +114,19 @@ class Middleware if (null !== $middleware) { list($call, $param) = $middleware; - $response = call_user_func_array($call, [$request, $this->resolve(), $param]); + try { + $response = call_user_func_array($call, [$request, $this->resolve(), $param]); + } catch (HttpResponseException $exception) { + $response = $exception->getResponse(); + } if (!$response instanceof Response) { - throw new \LogicException('The middleware must return Response instance'); + throw new LogicException('The middleware must return Response instance'); } return $response; } else { - throw new \InvalidArgumentException('The queue was exhausted, with no response returned'); + throw new InvalidArgumentException('The queue was exhausted, with no response returned'); } }; } -- Gitee From fd47d778206b7ec147832462dc65e2fbb1d7d4d1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 1 Apr 2018 11:19:48 +0800 Subject: [PATCH 1085/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E9=97=AD=E5=8C=85=E5=8F=82=E6=95=B0=E4=BC=A0?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 2 +- library/think/exception/Handle.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 09b34f8e..7f7baa6e 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -81,7 +81,7 @@ class Middleware } if ($middleware instanceof \Closure) { - return [$middleware, null]; + return [$middleware, isset($param) ? $param : null]; } if (!is_string($middleware)) { diff --git a/library/think/exception/Handle.php b/library/think/exception/Handle.php index 02c85ec1..7d947c62 100644 --- a/library/think/exception/Handle.php +++ b/library/think/exception/Handle.php @@ -59,7 +59,7 @@ class Handle $log .= "\r\n" . $exception->getTraceAsString(); } - Container::get('log')->record($log, 'error'); + Container::get('log')->write($log, 'error'); } } -- Gitee From 2abbd15524a6a4c81409fcc6422d4cfc7e8722cc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 1 Apr 2018 11:36:11 +0800 Subject: [PATCH 1086/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 7f7baa6e..07fc6b27 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -89,21 +89,19 @@ class Middleware } if (false === strpos($middleware, '\\')) { - $value = Container::get('config')->get('middleware.' . $middleware); - $class = $value ?: Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware; - } else { - $class = $middleware; + $value = Container::get('config')->get('middleware.' . $middleware); + $middleware = $value ?: Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware; } - if (is_array($class)) { - return $this->import($class); + if (is_array($middleware)) { + return $this->import($middleware); } - if (strpos($class, ':')) { - list($class, $param) = explode(':', $class, 2); + if (strpos($middleware, ':')) { + list($middleware, $param) = explode(':', $middleware, 2); } - return [[Container::get($class), 'handle'], isset($param) ? $param : null]; + return [[Container::get($middleware), 'handle'], isset($param) ? $param : null]; } protected function resolve() -- Gitee From b3b4664e3291ccceb2d8cf8f1f3d00c838daa4df Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 3 Apr 2018 11:14:27 +0800 Subject: [PATCH 1087/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Container.php b/library/think/Container.php index b138e600..08c48a07 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -165,7 +165,7 @@ class Container * 创建类的实例 * @access public * @param string $abstract 类名或者标识 - * @param array|true $args 变量 + * @param array|true $vars 变量 * @param bool $newInstance 是否每次创建新的实例 * @return object */ -- Gitee From 6f5446b4d22d725258417a6b1fae4ccc9c0a2cbd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 3 Apr 2018 22:33:33 +0800 Subject: [PATCH 1088/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 26 ++++++++++++------------- library/think/route/dispatch/Module.php | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 07fc6b27..6c2d2211 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -109,23 +109,23 @@ class Middleware return function (Request $request) { $middleware = array_shift($this->queue); - if (null !== $middleware) { - list($call, $param) = $middleware; + if (null === $middleware) { + throw new InvalidArgumentException('The queue was exhausted, with no response returned'); + } - try { - $response = call_user_func_array($call, [$request, $this->resolve(), $param]); - } catch (HttpResponseException $exception) { - $response = $exception->getResponse(); - } + list($call, $param) = $middleware; - if (!$response instanceof Response) { - throw new LogicException('The middleware must return Response instance'); - } + try { + $response = call_user_func_array($call, [$request, $this->resolve(), $param]); + } catch (HttpResponseException $exception) { + $response = $exception->getResponse(); + } - return $response; - } else { - throw new InvalidArgumentException('The queue was exhausted, with no response returned'); + if (!$response instanceof Response) { + throw new LogicException('The middleware must return Response instance'); } + + return $response; }; } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 8490bf42..26c92e94 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -128,7 +128,7 @@ class Module extends Dispatch } elseif (is_callable([$instance, '_empty'])) { // 空操作 $call = [$instance, '_empty']; - $vars = [$actionName]; + $vars = [$this->actionName]; $reflect = new ReflectionMethod($instance, '_empty'); } else { // 操作不存在 -- Gitee From ac0d0e9bd07413f2559cd5186b7154401d5a1e2d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 4 Apr 2018 15:06:00 +0800 Subject: [PATCH 1089/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index d6a5df4e..833a693f 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -492,13 +492,13 @@ abstract class Builder /** * 表达式查询 * @access protected - * @param Query $query 查询对象 - * @param string $key - * @param string $exp - * @param mixed $value - * @param string $field - * @param string $bindName - * @param integer $bindType + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param Expression $value + * @param string $field + * @param string $bindName + * @param integer $bindType * @return string */ protected function parseExp(Query $query, $key, $exp, Expression $value, $field, $bindName, $bindType) -- Gitee From 01d4166c289efece9185f37db8b1739eef4747b6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 4 Apr 2018 18:28:31 +0800 Subject: [PATCH 1090/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E7=BB=84?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E5=BB=B6=E8=BF=9F=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 4f2f7d14..bf8c6589 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -232,7 +232,7 @@ class RuleGroup extends Rule */ public function lazy($lazy = true) { - if (!$lazy && !is_object($this->rule)) { + if (!$lazy) { $this->parseGroupRule($this->rule); $this->rule = null; } -- Gitee From 7f9aef2ac9a4d9f1621069473ddc0eb19392eb0e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 4 Apr 2018 21:15:32 +0800 Subject: [PATCH 1091/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/exception/Handle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/exception/Handle.php b/library/think/exception/Handle.php index 7d947c62..02c85ec1 100644 --- a/library/think/exception/Handle.php +++ b/library/think/exception/Handle.php @@ -59,7 +59,7 @@ class Handle $log .= "\r\n" . $exception->getTraceAsString(); } - Container::get('log')->write($log, 'error'); + Container::get('log')->record($log, 'error'); } } -- Gitee From f28f8370f3e31069f55324ccfb6f1c68fe6eb3a4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Apr 2018 10:15:45 +0800 Subject: [PATCH 1092/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E7=9A=84url=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 6 +++++- library/think/Request.php | 16 +++++++++++++--- library/think/Url.php | 8 +++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index cf070372..6da1888b 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.7'; + const VERSION = '5.1.8'; /** * 当前模块路径 @@ -493,6 +493,10 @@ class App implements \ArrayAccess } } + if (is_file($this->runtimePath . 'rule_regex.php')) { + $this->route->setRuleRegexs(include $this->runtimePath . 'rule_regex.php'); + } + // 是否强制路由模式 $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); diff --git a/library/think/Request.php b/library/think/Request.php index e3fbdfc4..16db947b 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -323,10 +323,12 @@ class Request $server['PATH_INFO'] = ''; $server['REQUEST_METHOD'] = strtoupper($method); $info = parse_url($uri); + if (isset($info['host'])) { $server['SERVER_NAME'] = $info['host']; $server['HTTP_HOST'] = $info['host']; } + if (isset($info['scheme'])) { if ('https' === $info['scheme']) { $server['HTTPS'] = 'on'; @@ -336,27 +338,34 @@ class Request $server['SERVER_PORT'] = 80; } } + if (isset($info['port'])) { $server['SERVER_PORT'] = $info['port']; $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; } + if (isset($info['user'])) { $server['PHP_AUTH_USER'] = $info['user']; } + if (isset($info['pass'])) { $server['PHP_AUTH_PW'] = $info['pass']; } + if (!isset($info['path'])) { $info['path'] = '/'; } - $options = []; + + $options = []; + $queryString = ''; + $options[strtolower($method)] = $params; - $queryString = ''; + if (isset($info['query'])) { parse_str(html_entity_decode($info['query']), $query); if (!empty($params)) { $params = array_replace($query, $params); - $queryString = http_build_query($query, '', '&'); + $queryString = http_build_query($params, '', '&'); } else { $params = $query; $queryString = $info['query']; @@ -364,6 +373,7 @@ class Request } elseif (!empty($params)) { $queryString = http_build_query($params, '', '&'); } + if ($queryString) { parse_str($queryString, $get); $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; diff --git a/library/think/Url.php b/library/think/Url.php index a7281df4..da91c8d9 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -148,7 +148,7 @@ class Url // 检测URL绑定 if (!$this->bindCheck) { - $bind = $this->app['route']->getBind(); + $bind = $this->app['route']->getBind($domain); if ($bind && 0 === strpos($url, $bind)) { $url = substr($url, strlen($bind) + 1); @@ -252,11 +252,11 @@ class Url return ''; } + $rootDomain = $this->app['request']->rootDomain(); if (true === $domain) { // 自动判断域名 - $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(); - $rootDomain = $this->app['config']->get('url_domain_root'); + $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(); $domains = $this->app['route']->getDomains(); @@ -286,6 +286,8 @@ class Url } } } + } elseif (!strpos($domain, '.')) { + $domain .= '.' . $rootDomain; } if (false !== strpos($domain, '://')) { -- Gitee From 7c9bb73f64beeaaec888acc23f6d9022e7b26019 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Apr 2018 10:25:47 +0800 Subject: [PATCH 1093/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 3ff1a012..ebc435bd 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -92,7 +92,10 @@ class File extends Driver $dir = dirname($filename); if ($auto && !is_dir($dir)) { - mkdir($dir, 0755, true); + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + } } return $filename; -- Gitee From 2c191d951eb90f3ebbc1c4d3230136d5178c21c2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Apr 2018 10:53:53 +0800 Subject: [PATCH 1094/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 5 +++-- library/think/log/driver/File.php | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index ebc435bd..5f10200b 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -59,10 +59,11 @@ 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; diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index f7150922..1db14925 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -61,8 +61,11 @@ class File $filename = date('Ymd') . $cli . '.log'; $files = glob($this->config['path'] . '*.log'); - if (count($files) > $this->config['max_files']) { - unlink($files[0]); + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { } } else { $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; -- Gitee From 545320cdc3edfeab619ad190db037f161e0c2211 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Apr 2018 20:39:05 +0800 Subject: [PATCH 1095/1384] =?UTF-8?q?number=E9=AA=8C=E8=AF=81=E8=A7=84?= =?UTF-8?q?=E5=88=99=E8=B0=83=E6=95=B4=E4=B8=BA=E7=BA=AF=E6=95=B0=E5=AD=97?= =?UTF-8?q?=20=E4=B8=8D=E5=90=AB=E5=B0=8F=E6=95=B0=E5=92=8C=E8=B4=9F?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 4abfc6b2..930f7d0f 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -749,7 +749,7 @@ class Validate $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; case 'number': - $result = is_numeric($value); + $result = ctype_digit($value); break; case 'array': // 是否为数组 -- Gitee From 34072e2b90e4ecb9ca3594fb6afc4a35e7f654d8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Apr 2018 22:00:38 +0800 Subject: [PATCH 1096/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=AB=E5=90=8D=E7=9A=84URL=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Url.php b/library/think/Url.php index da91c8d9..532fd402 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -124,7 +124,7 @@ class Url if ($alias) { // 别名路由解析 foreach ($alias as $key => $item) { - $val = $item->gerRoute(); + $val = $item->getRoute(); if (0 === strpos($url, $val)) { $url = $key . substr($url, strlen($val)); -- Gitee From f1166d0759b83a48db36e0978e31721b0dbdd1d2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 6 Apr 2018 08:01:20 +0800 Subject: [PATCH 1097/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/think/Route.php b/library/think/Route.php index be411148..645fb9e1 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -313,6 +313,8 @@ class Route { if (is_null($domain)) { $domain = $this->domain; + } elseif (!strpos($domain, '.')) { + $domain .= '.' . $this->request->rootDomain(); } $subDomain = $this->request->subDomain(); -- Gitee From e1e58a68acac5b4531d16dde8e4c4e47cd20de37 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 6 Apr 2018 10:46:49 +0800 Subject: [PATCH 1098/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Url.php b/library/think/Url.php index 532fd402..1e4dfd7d 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -148,7 +148,7 @@ class Url // 检测URL绑定 if (!$this->bindCheck) { - $bind = $this->app['route']->getBind($domain); + $bind = $this->app['route']->getBind($domain ?: null); if ($bind && 0 === strpos($url, $bind)) { $url = substr($url, strlen($bind) + 1); -- Gitee From 39bb0fe6d50ee77e0779f646b10bce08c442a5e3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 6 Apr 2018 13:28:17 +0800 Subject: [PATCH 1099/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmysql=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 17 ----------------- library/think/db/builder/Mysql.php | 27 --------------------------- 2 files changed, 44 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 833a693f..78ea0f4a 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -143,11 +143,6 @@ abstract class Builder case 'DEC': $result[$item] = $item . ' - ' . floatval($val[1]); break; - default: - $value = $this->parseArrayData($query, $val); - if ($value) { - $result[$item] = $value; - } } } elseif (is_scalar($val)) { // 过滤非标量数据 @@ -158,18 +153,6 @@ abstract class Builder return $result; } - /** - * 数组数据解析 - * @access protected - * @param Query $query 查询对象 - * @param array $data - * @return mixed - */ - protected function parseArrayData(Query $query, $data) - { - return false; - } - /** * 数据绑定处理 * @access protected diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index f5cd5f40..a73fa293 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -173,33 +173,6 @@ class Mysql extends Builder return $fieldsStr; } - /** - * 数组数据解析 - * @access protected - * @param Query $query 查询对象 - * @param array $data - * @return mixed - */ - protected function parseArrayData(Query $query, $data) - { - list($type, $value) = $data; - - switch (strtolower($type)) { - case 'point': - $fun = isset($data[2]) ? $data[2] : 'GeomFromText'; - $point = isset($data[3]) ? $data[3] : 'POINT'; - if (is_array($value)) { - $value = implode(' ', $value); - } - $result = $fun . '(\'' . $point . '(' . $value . ')\')'; - break; - default: - $result = false; - } - - return $result; - } - /** * 随机排序 * @access protected -- Gitee From 9c8e67aaaf28312f1e17f3d9f8b0f0d1d1e2a69f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 6 Apr 2018 15:50:59 +0800 Subject: [PATCH 1100/1384] =?UTF-8?q?template.auto=5Frule=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=82=E6=95=B0=E5=A6=82=E6=9E=9C=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=BA0=20=E5=88=99=E4=BD=BF=E7=94=A8=E5=AE=9E=E9=99=85?= =?UTF-8?q?=E7=9A=84=E6=93=8D=E4=BD=9C=E6=96=B9=E6=B3=95=E5=90=8D=E4=BD=9C?= =?UTF-8?q?=E4=B8=BA=E9=BB=98=E8=AE=A4=E6=B8=B2=E6=9F=93=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/view/driver/Php.php | 10 +++++++++- library/think/view/driver/Think.php | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 6e5db3bd..f6987152 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -141,7 +141,7 @@ class Php if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); } elseif (false === strpos($template, $depr)) { $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } @@ -153,6 +153,14 @@ class Php return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + /** * 配置模板引擎 * @access private diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 137acf85..834a8c03 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -132,10 +132,11 @@ class Think if (0 !== strpos($template, '/')) { $template = str_replace(['/', ':'], $depr, $template); $controller = Loader::parseName($request->controller()); + if ($controller) { if ('' == $template) { // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); } elseif (false === strpos($template, $depr)) { $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; } @@ -147,6 +148,14 @@ class Think return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); } + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + /** * 配置或者获取模板引擎参数 * @access private -- Gitee From 3d0b163d52c058706c25a901c3fc18c3f7cab148 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 6 Apr 2018 16:43:19 +0800 Subject: [PATCH 1101/1384] =?UTF-8?q?Db=E7=B1=BB=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/Db.php b/library/think/Db.php index 6ce22f39..9be2dd2e 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -15,6 +15,7 @@ namespace think; * Class Db * @package think * @method \think\db\Query connect(array $config =[], mixed $name = false) static 连接/切换数据库连接 + * @method \think\db\Query master() static 从主服务器读取数据 * @method \think\db\Query table(string $table) static 指定数据表(含前缀) * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 -- Gitee From 9798c97a51c242fa04daac35d1e3a3a4d445a3c5 Mon Sep 17 00:00:00 2001 From: vibbow Date: Fri, 6 Apr 2018 17:16:01 +0800 Subject: [PATCH 1102/1384] Update Request.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正IPv6地址无法正确返回的问题 --- library/think/Request.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 16db947b..57c36e24 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1588,11 +1588,27 @@ class Request $ip = $_SERVER['REMOTE_ADDR']; } + // IP地址类型 + $ip_mode = (strpos($ip, ':') === false) ? 'ipv4' : 'ipv6'; + // IP地址合法验证 - $long = sprintf("%u", ip2long($ip)); - $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + if (filter_var($ip, FILTER_VALIDATE_IP) !== $ip) { + $ip = ($ip_mode === 'ipv4') ? '0.0.0.0' : '::'; + } + + // 除非明确指定需要int类型ip,否则直接返回IP地址 + if ($type !== 1) { + return $ip; + } - return $ip[$type]; + // 如果是ipv4地址,则直接使用ip2long返回int类型ip + if ($ip_mode === 'ipv4') { + return sprintf("%u", ip2long($ip)); + } + // 如果是ipv6地址,暂时不支持,直接返回0 + else { + return 0; + } } /** -- Gitee From d766043b4a575f2a731d43801afb01d86e9c89a5 Mon Sep 17 00:00:00 2001 From: vibbow Date: Fri, 6 Apr 2018 19:44:24 +0800 Subject: [PATCH 1103/1384] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=9A=84ip=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F=E4=B8=8D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 57c36e24..c627d06d 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1596,19 +1596,12 @@ class Request $ip = ($ip_mode === 'ipv4') ? '0.0.0.0' : '::'; } - // 除非明确指定需要int类型ip,否则直接返回IP地址 - if ($type !== 1) { - return $ip; - } + // 如果是ipv4地址,则直接使用ip2long返回int类型ip;如果是ipv6地址,暂时不支持,直接返回0 + $long_ip = ($ip_mode === 'ipv4') ? sprintf("%u", ip2long($ip)) : 0; - // 如果是ipv4地址,则直接使用ip2long返回int类型ip - if ($ip_mode === 'ipv4') { - return sprintf("%u", ip2long($ip)); - } - // 如果是ipv6地址,暂时不支持,直接返回0 - else { - return 0; - } + $ip = [$ip, $long_ip]; + + return $ip[$type]; } /** -- Gitee From 157e27d9b32268e04b8dcb5a88147500699b797e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 7 Apr 2018 23:24:57 +0800 Subject: [PATCH 1104/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=9C=A8=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E4=B8=AD=E8=AE=BE=E7=BD=AEdefaultSoftDelete=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E7=94=A8=E4=BA=8E=E8=BD=AF=E5=88=A0=E9=99=A4=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 13 ++++++++----- library/think/db/Query.php | 8 ++++++-- library/think/model/concern/SoftDelete.php | 21 ++++++++++++++++++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 433e6d09..4b5b0cca 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -104,6 +104,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected $error; + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + /** * 架构函数 * @access public @@ -245,11 +251,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if ($useBaseQuery) { // 软删除 - if (method_exists($this, 'getDeleteTimeField')) { - $field = $this->getDeleteTimeField(true); - if ($field) { - $query->useSoftDelete($field); - } + if (method_exists($this, 'withNoTrashed')) { + $this->withNoTrashed($query); } // 全局作用域 diff --git a/library/think/db/Query.php b/library/think/db/Query.php index ddb2bbea..72126f4f 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1357,7 +1357,7 @@ class Query public function useSoftDelete($field, $condition = null) { if ($field) { - $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + $this->options['soft_delete'] = [$field, $condition]; } return $this; @@ -1490,9 +1490,13 @@ class Query if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { // null查询 $where = [$field, $op, '']; + } elseif (in_array($op, ['=', null])) { + $where = [$field, 'NULL', '']; + } elseif (in_array($op, ['<>', 'neq'])) { + $where = [$field, 'NOTNULL', '']; } else { // 字段相等查询 - $where = is_null($op) ? [$field, 'NULL', ''] : [$field, '=', $op]; + $where = [$field, '=', $op]; } } else { $where = $field ? [$field, $op, $condition] : null; diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 473f9e3e..bf6efc5a 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -51,7 +51,7 @@ trait SoftDelete if ($field) { return $model ->db(false) - ->useSoftDelete($field, ['not null', '']); + ->useSoftDelete($field, is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]); } return $model->db(false); } @@ -151,8 +151,8 @@ trait SoftDelete // 恢复删除 return $this->db(false) ->where($where) - ->useSoftDelete($name, ['not null', '']) - ->update([$name => null]); + ->useSoftDelete($name, is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]) + ->update([$name => $this->defaultSoftDelete]); } return 0; @@ -183,4 +183,19 @@ trait SoftDelete return $field; } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed($query) + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $query->useSoftDelete($field, $this->defaultSoftDelete); + } + } } -- Gitee From 98e6d64cff80f39b905b91271432b0d1a6e09d89 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 8 Apr 2018 14:51:07 +0800 Subject: [PATCH 1105/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/SoftDelete.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index bf6efc5a..e084f251 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -51,11 +51,23 @@ trait SoftDelete if ($field) { return $model ->db(false) - ->useSoftDelete($field, is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]); + ->useSoftDelete($field, $model->getWithTrashedExp()); } + return $model->db(false); } + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp() + { + return is_null($this->defaultSoftDelete) ? + ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + /** * 删除当前的记录 * @access public @@ -151,7 +163,7 @@ trait SoftDelete // 恢复删除 return $this->db(false) ->where($where) - ->useSoftDelete($name, is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]) + ->useSoftDelete($name, $this->getWithTrashedExp()) ->update([$name => $this->defaultSoftDelete]); } -- Gitee From 60d18f0611dda6a3bbf0c902b11213f7c07e3bba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 8 Apr 2018 16:59:32 +0800 Subject: [PATCH 1106/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 4b5b0cca..3b6b0d47 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -577,7 +577,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 字段值(延迟)增长 + * 字段值(延迟)减少 * @access public * @param string $field 字段名 * @param integer $step 增长值 -- Gitee From 8cdec923e083b1cb4c2ea271d1762f13a28336c7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 8 Apr 2018 17:04:17 +0800 Subject: [PATCH 1107/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 3b6b0d47..7730d99b 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -580,7 +580,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * 字段值(延迟)减少 * @access public * @param string $field 字段名 - * @param integer $step 增长值 + * @param integer $step 减少值 * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws Exception -- Gitee From 25e14a03cdb672f05976ebe90fc5ec398834fc97 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 8 Apr 2018 18:22:55 +0800 Subject: [PATCH 1108/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=A7=84=E5=88=99=E5=AF=B9=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- library/think/route/RuleItem.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index bf8c6589..1fe3b272 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -316,7 +316,7 @@ class RuleGroup extends Rule } try { - if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/', $url, $match)) { + if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match)) { $var = []; foreach ($match as $key => $val) { if (is_string($key) && '' !== $val) { diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 27e5e754..117c1a9b 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -309,7 +309,7 @@ class RuleItem extends Rule $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); try { - if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { + if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/u', $url, $match)) { return false; } } catch (\Exception $e) { -- Gitee From 878ee382822198dd497afcc295183b776daa0f63 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 9 Apr 2018 11:32:40 +0800 Subject: [PATCH 1109/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 72126f4f..7f51bf90 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1490,9 +1490,9 @@ class Query if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { // null查询 $where = [$field, $op, '']; - } elseif (in_array($op, ['=', null])) { + } elseif (in_array(strtolower($op), ['=', 'eq', null], true)) { $where = [$field, 'NULL', '']; - } elseif (in_array($op, ['<>', 'neq'])) { + } elseif (in_array(strtolower($op), ['<>', 'neq'], true)) { $where = [$field, 'NOTNULL', '']; } else { // 字段相等查询 -- Gitee From 334bc0cb7f13132f7a8e51b0f48235cdfdd9b5c3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 9 Apr 2018 11:55:11 +0800 Subject: [PATCH 1110/1384] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=97=AD=E5=8C=85?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=9A=84=E6=97=B6=E5=80=99=E4=BD=BF=E7=94=A8?= =?UTF-8?q?cache(true)=20=E4=BC=9A=E6=8A=9B=E5=87=BA=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 1 + library/think/db/Connection.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 16b1bb7c..5a3e0bd6 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -69,6 +69,7 @@ return [ 'chunk not support order' => 'Chunk不支持调用order方法', 'route pattern error' => '路由变量规则定义错误', 'route behavior will not support' => '路由行为废弃(使用中间件替代)', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', // 上传错误信息 'unknown upload error' => '未知上传错误!', diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 142e8044..509dcce4 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -2076,7 +2076,7 @@ abstract class Connection { if (is_scalar($value)) { $data = $value; - } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'])) { + } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'], true)) { $data = $value[2]; } @@ -2089,7 +2089,7 @@ abstract class Connection try { return md5($prefix . serialize($query->getOptions()) . serialize($query->getBind(false))); } catch (\Exception $e) { - return; + throw new Exception('closure not support cache(true)'); } } -- Gitee From d0758a8b93f8a025185d3a9ea3cc2ecafd568230 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 9 Apr 2018 12:21:50 +0800 Subject: [PATCH 1111/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BLoader=E7=B1=BBload?= =?UTF-8?q?ComposerAutoloadFiles=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/Loader.php b/library/think/Loader.php index 6354f9e4..e8d6999e 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -338,6 +338,10 @@ class Loader if (is_file(self::$composerPath . 'autoload_files.php')) { $includeFiles = require self::$composerPath . 'autoload_files.php'; foreach ($includeFiles as $fileIdentifier => $file) { + if (isset($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + continue; + } + if (empty(self::$autoloadFiles[$fileIdentifier])) { __require_file($file); self::$autoloadFiles[$fileIdentifier] = true; -- Gitee From db8a18493a6cd0be79cf17c90eb3f49f3be5064c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 9 Apr 2018 23:14:13 +0800 Subject: [PATCH 1112/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 11 +++++------ library/think/db/Query.php | 6 +----- library/think/db/builder/Mysql.php | 7 ++++--- library/think/db/builder/Pgsql.php | 3 ++- library/think/db/builder/Sqlite.php | 3 ++- library/think/db/builder/Sqlsrv.php | 9 +++++++-- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 78ea0f4a..23802f86 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -183,9 +183,10 @@ abstract class Builder * @access public * @param Query $query 查询对象 * @param string $key 字段名 + * @param bool $strict 严格检测 * @return string */ - public function parseKey(Query $query, $key) + public function parseKey(Query $query, $key, $strict = false) { return $key; } @@ -363,7 +364,7 @@ abstract class Builder protected function parseWhereItem(Query $query, $field, $val, $rule = '', $binds = [], $bindName = null) { // 字段分析 - $key = $field ? $this->parseKey($query, $field) : ''; + $key = $field ? $this->parseKey($query, $field, true) : ''; // 查询规则和条件 if (!is_array($val)) { @@ -833,14 +834,12 @@ abstract class Builder } elseif (is_numeric($key)) { if ('[rand]' == $val) { $array[] = $this->parseRand($query); - } elseif (false === strpos($val, '(')) { - $array[] = $this->parseKey($query, $val); } else { - $array[] = $val; + $array[] = $this->parseKey($query, $val, true); } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($query, $key) . ' ' . $sort; + $array[] = $this->parseKey($query, $key, true) . ' ' . $sort; } } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 7f51bf90..ed5ab44e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1517,11 +1517,7 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - if (is_null($val)) { - $where[$key] = [$key, 'NULL', '']; - } else { - $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val]; - } + $where[$key] = is_null($val) ? [$key, 'NULL', ''] : [$key, '=', $val]; } } else { // 数组批量查询 diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index a73fa293..a17657d9 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -105,9 +105,10 @@ class Mysql extends Builder * @access public * @param Query $query 查询对象 * @param string $key 字段名 + * @param bool $strict 严格检测 * @return string */ - public function parseKey(Query $query, $key) + public function parseKey(Query $query, $key, $strict = false) { if (is_int($key)) { return $key; @@ -118,7 +119,7 @@ class Mysql extends Builder // JSON字段支持 list($field, $name) = explode('->', $key, 2); - $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . str_replace('->', '.', $name) . '\')'; + return 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . str_replace('->', '.', $name) . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); @@ -134,7 +135,7 @@ class Mysql extends Builder } } - if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + if ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 4db6932d..32373b64 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -51,9 +51,10 @@ class Pgsql extends Builder * @access public * @param Query $query 查询对象 * @param string $key 字段名 + * @param bool $strict 严格检测 * @return string */ - public function parseKey(Query $query, $key) + public function parseKey(Query $query, $key, $strict = false) { $key = trim($key); diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 141410c1..9b244617 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -59,9 +59,10 @@ class Sqlite extends Builder * @access public * @param Query $query 查询对象 * @param string $key 字段名 + * @param bool $strict 严格检测 * @return string */ - public function parseKey(Query $query, $key) + public function parseKey(Query $query, $key, $strict = false) { $key = trim($key); if (strpos($key, '.')) { diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index b95e234b..c6a53049 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -75,10 +75,15 @@ class Sqlsrv extends Builder * @access public * @param Query $query 查询对象 * @param string $key 字段名 + * @param bool $strict 严格检测 * @return string */ - public function parseKey(Query $query, $key) + public function parseKey(Query $query, $key, $strict = false) { + if (is_int($key)) { + return $key; + } + $key = trim($key); if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { @@ -96,7 +101,7 @@ class Sqlsrv extends Builder } } - if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + if ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { $key = '[' . $key . ']'; } -- Gitee From 99c38f0a835386636941d433067453f91e6561bd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 9 Apr 2018 23:41:38 +0800 Subject: [PATCH 1113/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 5 ++++- library/think/db/Query.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 23802f86..e6aab9f6 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -835,7 +835,10 @@ abstract class Builder if ('[rand]' == $val) { $array[] = $this->parseRand($query); } else { - $array[] = $this->parseKey($query, $val, true); + list($field, $sort) = explode(' ', $val); + + $sort = in_array(strtolower(trim($sort)), ['asc', 'desc']) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $field, true) . ' ' . $sort; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; diff --git a/library/think/db/Query.php b/library/think/db/Query.php index ed5ab44e..0e997858 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1776,7 +1776,7 @@ class Query if (!empty($this->options['via'])) { $field = $this->options['via'] . '.' . $field; } - + $field = array_map('trim', explode(',', $field)); $field = empty($order) ? $field : [$field => $order]; } elseif (!empty($this->options['via'])) { foreach ($field as $key => $val) { -- Gitee From dfa998bb2f3c3c3387c829bad365564e82ba2ba8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Apr 2018 09:48:24 +0800 Subject: [PATCH 1114/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E4=B8=AD=E6=8E=A7=E5=88=B6=E5=99=A8=E5=90=8D?= =?UTF-8?q?=E9=A9=BC=E5=B3=B0=E9=97=AE=E9=A2=98=20=E5=92=8C=20module=5Fini?= =?UTF-8?q?t=20=E5=92=8C=20app=5Fbegin=20=E7=9A=84=E9=92=A9=E5=AD=90?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 2 +- library/think/route/dispatch/Module.php | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 8d53c437..8f9d47e9 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -864,7 +864,7 @@ abstract class Rule $request->route($var); // 路由到模块/控制器/操作 - return (new ModuleDispatch([$module, $controller, $action]))->convert(false); + return new ModuleDispatch([$module, $controller, $action], [], false); } /** diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 26c92e94..ec341997 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -23,6 +23,15 @@ class Module extends Dispatch protected $controller; protected $actionName; + public function __construct($dispatch, $param = [], $convert = null) + { + $this->app = Container::get('app'); + $this->dispatch = $dispatch; + $this->param = $param; + $this->convert = $convert; + $this->init(); + } + protected function init() { $result = $this->dispatch; @@ -90,13 +99,13 @@ class Module extends Dispatch // 设置当前请求的控制器、操作 $this->app['request']->controller(Loader::parseName($this->controller, 1))->action($this->actionName); - // 监听module_init - $this->app['hook']->listen('module_init'); - } public function run() { + // 监听module_init + $this->app['hook']->listen('module_init'); + // 实例化控制器 try { $instance = $this->app->controller($this->controller, -- Gitee From ca764e424dcb13908e816036dab378373ba814f7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Apr 2018 19:57:31 +0800 Subject: [PATCH 1115/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 509dcce4..aa1b7402 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -2076,7 +2076,7 @@ abstract class Connection { if (is_scalar($value)) { $data = $value; - } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'], true)) { + } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'], true) && is_scalar($value[2])) { $data = $value[2]; } -- Gitee From 6fd991b673187c8887a1319258f6f3c1973a533f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Apr 2018 20:16:26 +0800 Subject: [PATCH 1116/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Border=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 60 +++++++++++++++++------------------- library/think/db/Query.php | 8 +++-- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index e6aab9f6..b65cb90f 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -809,46 +809,44 @@ abstract class Builder return ''; } - if (is_array($order)) { - $array = []; - - foreach ($order as $key => $val) { - if ($val instanceof Expression) { - $array[] = $val->getValue(); - } elseif (is_array($val)) { - if (isset($val['sort'])) { - $sort = ' ' . $val['sort']; - unset($val['sort']); - } else { - $sort = ''; - } + $array = []; - $options = $query->getOptions(); - $bind = $this->connection->getFieldsBind($options['table']); + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_array($val)) { + if (isset($val['sort'])) { + $sort = ' ' . $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } - foreach ($val as $k => $item) { - $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); - } + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); - $array[] = 'field(' . $this->parseKey($query, $key) . ',' . implode(',', $val) . ')' . $sort; - } elseif (is_numeric($key)) { - if ('[rand]' == $val) { - $array[] = $this->parseRand($query); - } else { - list($field, $sort) = explode(' ', $val); + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); + } - $sort = in_array(strtolower(trim($sort)), ['asc', 'desc']) ? ' ' . $sort : ''; - $array[] = $this->parseKey($query, $field, true) . ' ' . $sort; - } + $array[] = 'field(' . $this->parseKey($query, $key) . ',' . implode(',', $val) . ')' . $sort; + } elseif (is_numeric($key)) { + if ('[rand]' == $val) { + $array[] = $this->parseRand($query); } else { - $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($query, $key, true) . ' ' . $sort; + list($field, $sort) = explode(' ', $val); + + $sort = in_array(strtolower(trim($sort)), ['asc', 'desc']) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $field, true) . ' ' . $sort; } + } else { + $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; + $array[] = $this->parseKey($query, $key, true) . ' ' . $sort; } - - $order = implode(',', $array); } + $order = implode(',', $array); + return ' ORDER BY ' . $order; } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 0e997858..8fd96d35 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1776,8 +1776,12 @@ class Query if (!empty($this->options['via'])) { $field = $this->options['via'] . '.' . $field; } - $field = array_map('trim', explode(',', $field)); - $field = empty($order) ? $field : [$field => $order]; + + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } } elseif (!empty($this->options['via'])) { foreach ($field as $key => $val) { if (is_numeric($key)) { -- Gitee From aca7d8aaea8cc53755043c8c11fe61b38967e489 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Apr 2018 20:42:29 +0800 Subject: [PATCH 1117/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BModel=E7=B1=BBsave?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 7730d99b..9a98a408 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -472,7 +472,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess foreach ((array) $pk as $key) { if (isset($data[$key])) { - $array[$key] = [$key, '=', $data[$key]]; + $array[] = [$key, '=', $data[$key]]; unset($data[$key]); } } -- Gitee From 7444a5e78f98109f98b31b32246296c4ae0834fb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Apr 2018 21:15:43 +0800 Subject: [PATCH 1118/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4restore=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 19 +++++++++---------- library/think/db/Query.php | 9 ++------- library/think/model/concern/SoftDelete.php | 5 +++-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index b65cb90f..3d876f0e 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -830,18 +830,17 @@ abstract class Builder } $array[] = 'field(' . $this->parseKey($query, $key) . ',' . implode(',', $val) . ')' . $sort; - } elseif (is_numeric($key)) { - if ('[rand]' == $val) { - $array[] = $this->parseRand($query); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', $val); } else { - list($field, $sort) = explode(' ', $val); - - $sort = in_array(strtolower(trim($sort)), ['asc', 'desc']) ? ' ' . $sort : ''; - $array[] = $this->parseKey($query, $field, true) . ' ' . $sort; + $sort = $val; } - } else { - $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($query, $key, true) . ' ' . $sort; + + $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; } } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 8fd96d35..0329e1c2 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1447,7 +1447,6 @@ class Query return $this->parseArrayWhereItems($field, $logic); } elseif ($field instanceof \Closure) { $where = $field; - $field = ''; } elseif (is_string($field)) { if (preg_match('/[,=\<\'\"\(\s]/', $field)) { return $this->whereRaw($field, $op); @@ -1460,11 +1459,7 @@ class Query } if (!empty($where)) { - if (isset($this->options['where'][$logic][$field])) { - $this->options['where'][$logic][] = $where; - } else { - $this->options['where'][$logic][$field] = $where; - } + $this->options['where'][$logic][] = $where; } return $this; @@ -1517,7 +1512,7 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - $where[$key] = is_null($val) ? [$key, 'NULL', ''] : [$key, '=', $val]; + $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, '=', $val]; } } else { // 数组批量查询 diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index e084f251..68b4c197 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -155,8 +155,9 @@ trait SoftDelete $name = $this->getDeleteTimeField(); if (empty($where)) { - $pk = $this->getPk(); - $where[$pk] = [$pk, '=', $this->getData($pk)]; + $pk = $this->getPk(); + + $where[] = [$pk, '=', $this->getData($pk)]; } if ($name) { -- Gitee From 6e88a84d118a220e787ebf0bafa61bd12ab99632 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Apr 2018 21:36:15 +0800 Subject: [PATCH 1119/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bnumber=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index 930f7d0f..bb170045 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -749,7 +749,7 @@ class Validate $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; case 'number': - $result = ctype_digit($value); + $result = ctype_digit((string) $value); break; case 'array': // 是否为数组 -- Gitee From e1c6a878ad3e6cadf085e0ffe17031e19ae90108 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 11 Apr 2018 08:09:27 +0800 Subject: [PATCH 1120/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 3d876f0e..3f1aa4cb 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -113,7 +113,7 @@ abstract class Builder $result = []; foreach ($data as $key => $val) { - $item = $this->parseKey($query, $key); + $item = $this->parseKey($query, $key, true); if ($val instanceof Expression) { $result[$item] = $val->getValue(); @@ -210,7 +210,7 @@ abstract class Builder if ($field instanceof Expression) { $array[] = $field->getValue(); } elseif (!is_numeric($key)) { - $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field); + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); } else { $array[] = $this->parseKey($query, $field); } @@ -1069,7 +1069,7 @@ abstract class Builder $fields = []; foreach ($insertFields as $field) { - $fields[] = $this->parseKey($query, $field); + $fields[] = $this->parseKey($query, $field, true); } return str_replace( @@ -1101,7 +1101,7 @@ abstract class Builder } foreach ($fields as &$field) { - $field = $this->parseKey($query, $field); + $field = $this->parseKey($query, $field, true); } return 'INSERT INTO ' . $this->parseTable($query, $table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); -- Gitee From 1ffc445923d27eca75cb69f27d044fdbe5e317e3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 11 Apr 2018 08:52:05 +0800 Subject: [PATCH 1121/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 3f1aa4cb..e576af76 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -833,7 +833,7 @@ abstract class Builder } elseif ('[rand]' == $val) { $array[] = $this->parseRand($query); } else { - if (is_numeric($key)) { + if (is_numeric($key) && strpos($val, ' ')) { list($key, $sort) = explode(' ', $val); } else { $sort = $val; -- Gitee From 70f9611968a41e2f0840137dca2184e64b06d4e1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 11 Apr 2018 11:24:30 +0800 Subject: [PATCH 1122/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index e576af76..f3c22922 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -833,8 +833,8 @@ abstract class Builder } elseif ('[rand]' == $val) { $array[] = $this->parseRand($query); } else { - if (is_numeric($key) && strpos($val, ' ')) { - list($key, $sort) = explode(' ', $val); + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); } else { $sort = $val; } -- Gitee From ef60703b1040f366e0faafac589bad7a07092c2f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 11 Apr 2018 14:26:05 +0800 Subject: [PATCH 1123/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BorderField=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index f3c22922..57458a2b 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -829,7 +829,7 @@ abstract class Builder $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); } - $array[] = 'field(' . $this->parseKey($query, $key) . ',' . implode(',', $val) . ')' . $sort; + $array[] = 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; } elseif ('[rand]' == $val) { $array[] = $this->parseRand($query); } else { -- Gitee From 1a479831eed4824bdc33c2df94e5f5988d7800ba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 11 Apr 2018 16:56:13 +0800 Subject: [PATCH 1124/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bsqlsrv=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 6 +----- library/think/db/builder/Sqlsrv.php | 33 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 57458a2b..31e5fc5e 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -949,11 +949,7 @@ abstract class Builder return ''; } - if (is_array($index)) { - $index = join(",", $index); - } - - return sprintf(" FORCE INDEX ( %s ) ", $index); + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); } /** diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index c6a53049..14a073e2 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -12,6 +12,7 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Expression; use think\db\Query; /** @@ -35,27 +36,31 @@ class Sqlsrv extends Builder */ protected function parseOrder(Query $query, $order) { - if (is_array($order)) { - $array = []; + if (empty($order)) { + return ''; + } + + $array = []; - foreach ($order as $key => $val) { + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { if (is_numeric($key)) { - if (false === strpos($val, '(')) { - $array[] = $this->parseKey($query, $val); - } elseif ('[rand]' == $val) { - $array[] = $this->parseRand($query); - } else { - $array[] = $val; - } + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); } else { - $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; - $array[] = $this->parseKey($query, $key) . ' ' . $sort; + $sort = $val; } - } - $order = implode(',', $array); + $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } } + $order = implode(',', $array); + return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()'; } -- Gitee From 36d59622a0788e412abbef8a63eccf05ea4ecbb8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 12 Apr 2018 14:06:03 +0800 Subject: [PATCH 1125/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BCLI=E8=B0=83?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 8 +++----- library/think/Loader.php | 34 +++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 6da1888b..50a6cadd 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -126,7 +126,7 @@ class App implements \ArrayAccess public function __construct($appPath = '') { - $this->appPath = $appPath ?: $this->getAppPath(); + $this->appPath = $appPath ? realpath($appPath) : $this->getAppPath(); $this->container = Container::getInstance(); } @@ -164,7 +164,7 @@ class App implements \ArrayAccess $this->beginTime = microtime(true); $this->beginMem = memory_get_usage(); $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; - $this->rootPath = dirname(realpath($this->appPath)) . DIRECTORY_SEPARATOR; + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; @@ -756,9 +756,7 @@ class App implements \ArrayAccess public function getAppPath() { if (is_null($this->appPath)) { - $scriptName = 'cli' == PHP_SAPI ? getcwd() . DIRECTORY_SEPARATOR . $_SERVER['argv'][0] : $_SERVER['SCRIPT_FILENAME']; - - $this->appPath = realpath(dirname(dirname($scriptName)) . DIRECTORY_SEPARATOR . 'application') . DIRECTORY_SEPARATOR; + $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; } return $this->appPath; diff --git a/library/think/Loader.php b/library/think/Loader.php index e8d6999e..e2bed16f 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -52,22 +52,38 @@ class Loader */ private static $composerPath; - // 注册自动加载机制 - public static function register($autoload = '') + // 获取应用根目录 + public static function getRootPath() { - // 注册系统自动加载 - spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + if ('cli' == PHP_SAPI) { + $cwdPath = getcwd(); - $scriptName = 'cli' == PHP_SAPI ? getcwd() . DIRECTORY_SEPARATOR . $_SERVER['argv'][0] : $_SERVER['SCRIPT_FILENAME']; + if (0 === strpos($_SERVER['argv'][0], $cwdPath)) { + $scriptName = $_SERVER['argv'][0]; + } else { + $scriptName = $cwdPath . DIRECTORY_SEPARATOR . $_SERVER['argv'][0]; + } + } else { + $scriptName = $_SERVER['SCRIPT_FILENAME']; + } $path = realpath(dirname($scriptName)); - if ('cli-server' == PHP_SAPI || !is_file('./think')) { - $rootPath = dirname($path) . DIRECTORY_SEPARATOR; - } else { - $rootPath = $path . DIRECTORY_SEPARATOR; + if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) { + $path = dirname($path); } + return $path . DIRECTORY_SEPARATOR; + } + + // 注册自动加载机制 + public static function register($autoload = '') + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + $rootPath = self::getRootPath(); + self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; // Composer自动加载支持 -- Gitee From 8bd8cf565b9f1f9f364787af37630ea209267b0f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 12 Apr 2018 16:25:12 +0800 Subject: [PATCH 1126/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 50a6cadd..039a876a 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.8'; + const VERSION = '5.1.9'; /** * 当前模块路径 -- Gitee From fff4acf5727d7f83f059026098c681042db75bb7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 12 Apr 2018 16:33:20 +0800 Subject: [PATCH 1127/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bsqlsrv=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Sqlsrv.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index 14a073e2..4d3dc15b 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -37,7 +37,7 @@ class Sqlsrv extends Builder protected function parseOrder(Query $query, $order) { if (empty($order)) { - return ''; + return ' ORDER BY rand()'; } $array = []; @@ -59,9 +59,7 @@ class Sqlsrv extends Builder } } - $order = implode(',', $array); - - return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()'; + return ' ORDER BY ' . implode(',', $array); } /** -- Gitee From c49df2fa54879105e451f7eaaf841d218206f02f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 12 Apr 2018 19:16:28 +0800 Subject: [PATCH 1128/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 039a876a..02a2e6fb 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -126,7 +126,7 @@ class App implements \ArrayAccess public function __construct($appPath = '') { - $this->appPath = $appPath ? realpath($appPath) : $this->getAppPath(); + $this->appPath = $appPath ? realpath($appPath) . DIRECTORY_SEPARATOR : $this->getAppPath(); $this->container = Container::getInstance(); } -- Gitee From 62a7fb17c12b913f99fac59f841fda0bbe57d3e6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 13 Apr 2018 13:35:23 +0800 Subject: [PATCH 1129/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Border=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 31e5fc5e..b4e61efb 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -815,13 +815,6 @@ abstract class Builder if ($val instanceof Expression) { $array[] = $val->getValue(); } elseif (is_array($val)) { - if (isset($val['sort'])) { - $sort = ' ' . $val['sort']; - unset($val['sort']); - } else { - $sort = ''; - } - $options = $query->getOptions(); $bind = $this->connection->getFieldsBind($options['table']); @@ -829,7 +822,7 @@ abstract class Builder $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); } - $array[] = 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + $array[] = 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')'; } elseif ('[rand]' == $val) { $array[] = $this->parseRand($query); } else { @@ -839,7 +832,8 @@ abstract class Builder $sort = $val; } - $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; $array[] = $this->parseKey($query, $key, true) . $sort; } } -- Gitee From a7c2ec8768beab0a3a2496a0124ce4ba221601c7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 13 Apr 2018 15:04:11 +0800 Subject: [PATCH 1130/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bexists=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 43 +++++++++++++++++++++++++++++------- library/think/db/Query.php | 19 +++++++++++++--- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index b4e61efb..9b684ea5 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -563,6 +563,10 @@ abstract class Builder // EXISTS 查询 if ($value instanceof \Closure) { $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); + } else { + throw new Exception('where express error:' . $value); } return $exp . ' (' . $value . ')'; @@ -815,14 +819,7 @@ abstract class Builder if ($val instanceof Expression) { $array[] = $val->getValue(); } elseif (is_array($val)) { - $options = $query->getOptions(); - $bind = $this->connection->getFieldsBind($options['table']); - - foreach ($val as $k => $item) { - $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); - } - - $array[] = 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')'; + $array[] = $this->parseOrderField($query, $key, $val); } elseif ('[rand]' == $val) { $array[] = $this->parseRand($query); } else { @@ -843,6 +840,36 @@ abstract class Builder return ' ORDER BY ' . $order; } + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $key + * @param array $val + * @return string + */ + protected function parseOrderField($query, $key, $val) + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind, $k); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + /** * group分析 * @access protected diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 0329e1c2..3bcc8159 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1233,6 +1233,10 @@ class Query */ public function whereExists($condition, $logic = 'AND') { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; return $this; } @@ -1246,6 +1250,10 @@ class Query */ public function whereNotExists($condition, $logic = 'AND') { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; return $this; } @@ -1493,6 +1501,8 @@ class Query // 字段相等查询 $where = [$field, '=', $op]; } + } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; } else { $where = $field ? [$field, $op, $condition] : null; } @@ -1827,11 +1837,14 @@ class Query * @param string $order * @return $this */ - public function orderField($field, array $values = [], $order = '') + public function orderField($field, array $values, $order = '') { - $values['sort'] = $order; + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } - $this->options['order'][$field] = $values; return $this; } -- Gitee From 4a669f13bca4f6e8cfad316f0b834e7afc44f530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=99=AF=E7=AB=8B?= Date: Fri, 13 Apr 2018 14:35:45 +0800 Subject: [PATCH 1131/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3cli=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E5=BD=93=E5=89=8D=E7=9B=AE=E5=BD=95=E4=B8=8E=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=9B=AE=E5=BD=95=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正cli模式当前目录与项目目录异常 --- library/think/Loader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index e2bed16f..1caf4292 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -61,7 +61,7 @@ class Loader if (0 === strpos($_SERVER['argv'][0], $cwdPath)) { $scriptName = $_SERVER['argv'][0]; } else { - $scriptName = $cwdPath . DIRECTORY_SEPARATOR . $_SERVER['argv'][0]; + $scriptName = realpath($_SERVER['argv'][0]); } } else { $scriptName = $_SERVER['SCRIPT_FILENAME']; -- Gitee From 9f9ec649c181b36754d1e49abf7d868bf09e80ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=99=AF=E7=AB=8B?= Date: Fri, 13 Apr 2018 14:50:49 +0800 Subject: [PATCH 1132/1384] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=E4=BF=AE?= =?UTF-8?q?=E6=94=B9cli=E6=A8=A1=E5=BC=8F=E5=85=A5=E5=8F=A3=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BD=8D=E7=BD=AE=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 已测试通过 php7 --- library/think/Loader.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index 1caf4292..af9f6bb4 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -56,13 +56,7 @@ class Loader public static function getRootPath() { if ('cli' == PHP_SAPI) { - $cwdPath = getcwd(); - - if (0 === strpos($_SERVER['argv'][0], $cwdPath)) { - $scriptName = $_SERVER['argv'][0]; - } else { - $scriptName = realpath($_SERVER['argv'][0]); - } + $scriptName = realpath($_SERVER['argv'][0]); } else { $scriptName = $_SERVER['SCRIPT_FILENAME']; } -- Gitee From cf4077b8c23ce486356375dcb2f52cf8c002047d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 13 Apr 2018 15:41:46 +0800 Subject: [PATCH 1133/1384] =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 28 ++-------------------------- library/think/db/builder/Pgsql.php | 2 +- library/think/db/builder/Sqlsrv.php | 2 +- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index a17657d9..cab32861 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -70,7 +70,7 @@ class Mysql extends Builder $fields = []; foreach ($insertFields as $field) { - $fields[] = $this->parseKey($query, $field); + $fields[] = $this->parseKey($query, $field, true); } return str_replace( @@ -135,7 +135,7 @@ class Mysql extends Builder } } - if ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { $key = '`' . $key . '`'; } @@ -150,30 +150,6 @@ class Mysql extends Builder return $key; } - /** - * field分析 - * @access protected - * @param Query $query 查询对象 - * @param mixed $fields 字段名 - * @return string - */ - protected function parseField(Query $query, $fields) - { - $fieldsStr = parent::parseField($query, $fields); - $options = $query->getOptions(); - - if (!empty($options['point'])) { - $array = []; - foreach ($options['point'] as $key => $field) { - $key = !is_numeric($key) ? $key : $field; - $array[] = 'AsText(' . $this->parseKey($query, $key) . ') AS ' . $this->parseKey($query, $field); - } - $fieldsStr .= ',' . implode(',', $array); - } - - return $fieldsStr; - } - /** * 随机排序 * @access protected diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 32373b64..7d2f72a1 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -51,7 +51,7 @@ class Pgsql extends Builder * @access public * @param Query $query 查询对象 * @param string $key 字段名 - * @param bool $strict 严格检测 + * @param bool $strict 严格检测 * @return string */ public function parseKey(Query $query, $key, $strict = false) diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index 4d3dc15b..b37f01a6 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -104,7 +104,7 @@ class Sqlsrv extends Builder } } - if ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { $key = '[' . $key . ']'; } -- Gitee From 545302c5c4d8173d244f85952028a6c46a92effc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 13 Apr 2018 15:49:25 +0800 Subject: [PATCH 1134/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3null=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3bcc8159..3bf2d708 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1493,9 +1493,9 @@ class Query if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { // null查询 $where = [$field, $op, '']; - } elseif (in_array(strtolower($op), ['=', 'eq', null], true)) { + } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) { $where = [$field, 'NULL', '']; - } elseif (in_array(strtolower($op), ['<>', 'neq'], true)) { + } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) { $where = [$field, 'NOTNULL', '']; } else { // 字段相等查询 -- Gitee From aeb536d50777328f53d98207f5030f54ed60a725 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 13 Apr 2018 16:28:29 +0800 Subject: [PATCH 1135/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BparseTime=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 9b684ea5..a2e65422 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -748,6 +748,10 @@ abstract class Builder $bindName = $bindName ?: $key; + if ($query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + $query->bind($bindName, $value, $bindType); return ':' . $bindName; -- Gitee From 08589dd87f98aa37589e892ce8c744ce481346ed Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 14 Apr 2018 17:55:57 +0800 Subject: [PATCH 1136/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=85=B3=E8=81=94?= =?UTF-8?q?=E9=A2=84=E8=BD=BD=E5=85=A5=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/db/Query.php | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 02a2e6fb..d9344791 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.9'; + const VERSION = '5.1.10'; /** * 当前模块路径 diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3bf2d708..622521d1 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1547,8 +1547,12 @@ class Query { $logic = strtoupper($logic); - if (isset($this->options['where'][$logic][$field])) { - unset($this->options['where'][$logic][$field]); + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } } return $this; -- Gitee From 054391fb5b4b87ebfe034e4009a307b02f5c5400 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 15 Apr 2018 09:28:25 +0800 Subject: [PATCH 1137/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmysql=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index cab32861..ff7f0a6f 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -12,6 +12,7 @@ namespace think\db\builder; use think\db\Builder; +use think\db\Expression; use think\db\Query; /** @@ -88,16 +89,16 @@ class Mysql extends Builder /** * 正则查询 * @access protected - * @param Query $query 查询对象 - * @param string $key - * @param string $exp - * @param mixed $value - * @param string $field + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param Expression $value + * @param string $field * @return string */ - protected function parseRegexp(Query $query, $key, $exp, $value, $field) + protected function parseRegexp(Query $query, $key, $exp, Expression $value, $field) { - return $key . ' ' . $exp . ' ' . $value; + return $key . ' ' . $exp . ' ' . $value->getValue(); } /** -- Gitee From db8c6723c78a18c739db8e78ff221c16f00d8017 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 15 Apr 2018 09:31:59 +0800 Subject: [PATCH 1138/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 622521d1..965b8f81 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1501,7 +1501,7 @@ class Query // 字段相等查询 $where = [$field, '=', $op]; } - } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + } elseif (in_array(strtoupper($op), ['REGEXP', 'NOT REGEXP', 'EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; } else { $where = $field ? [$field, $op, $condition] : null; -- Gitee From a0e7e4ec2c7811b9a2c55ea5536614cb7c4bf043 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 15 Apr 2018 09:59:25 +0800 Subject: [PATCH 1139/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index a2e65422..ddf10aea 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -839,9 +839,7 @@ abstract class Builder } } - $order = implode(',', $array); - - return ' ORDER BY ' . $order; + return ' ORDER BY ' . implode(',', $array); } /** -- Gitee From f833309f80e807ba203bf9b9da67627680d53d0d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 15 Apr 2018 11:00:17 +0800 Subject: [PATCH 1140/1384] =?UTF-8?q?inc/dec=E7=94=A8=E6=B3=95=E6=94=B9?= =?UTF-8?q?=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ddf10aea..9646fc95 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -136,7 +136,7 @@ abstract class Builder } elseif (is_null($val)) { $result[$item] = 'NULL'; } elseif (is_array($val) && !empty($val)) { - switch ($val[0]) { + switch (strtoupper($val[0])) { case 'INC': $result[$item] = $item . ' + ' . floatval($val[1]); break; -- Gitee From c2855576b83709960b0ab639b402561158a9db3b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 15 Apr 2018 12:32:41 +0800 Subject: [PATCH 1141/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bthink=20clear?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=20=E6=94=AF=E6=8C=81=20-c=20-l=20-r=20?= =?UTF-8?q?=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/console/command/Clear.php | 36 ++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/library/think/console/command/Clear.php b/library/think/console/command/Clear.php index 9896aea5..7ee31ea7 100644 --- a/library/think/console/command/Clear.php +++ b/library/think/console/command/Clear.php @@ -24,22 +24,40 @@ class Clear extends Command $this ->setName('clear') ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->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') ?: App::getRuntimePath(); - $files = scandir($path); - if ($files) { - foreach ($files as $file) { - if ('.' != $file && '..' != $file && is_dir($path . $file)) { - array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); - } elseif ('.gitignore' != $file && is_file($path . $file)) { - unlink($path . $file); + if ($input->getOption('cache')) { + $path = App::getRuntimePath() . 'cache'; + } elseif ($input->getOption('log')) { + $path = App::getRuntimePath() . 'log'; + } else { + $path = $input->getOption('path') ?: App::getRuntimePath(); + } + + $rmdir = $input->getOption('dir') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); + $output->writeln("Clear Successed"); + } + + protected function clear($path, $rmdir) + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); + if ($rmdir) { + rmdir($path . $file); } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); } } - $output->writeln("Clear Successed"); } } -- Gitee From 5f1db3a08b0fd4dca493a2fee610c2d6e2e31c3a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 16 Apr 2018 11:53:52 +0800 Subject: [PATCH 1142/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=AE=9A=E4=B9=89=E7=9A=84=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 8f9d47e9..96f86d93 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -985,13 +985,22 @@ abstract class Rule } // 是否区分 / 地址访问 - if (!empty($option['remove_slash']) && '/' != $rule) { - $rule = rtrim($rule, '/'); + if ('/' != $rule) { + if (!empty($option['remove_slash'])) { + $rule = rtrim($rule, '/'); + } elseif (substr($rule, -1) == '/') { + $rule = rtrim($rule, '/'); + $hasSlash = true; + } } $regex = str_replace($match, $replace, $rule); $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); + if (isset($hasSlash)) { + $regex .= '\/'; + } + return $regex . ($completeMatch ? '$' : ''); } -- Gitee From db66653f705167d125f1a6285120e23374672e86 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 16 Apr 2018 12:53:08 +0800 Subject: [PATCH 1143/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3insertAll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 9646fc95..e61e6502 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -1088,7 +1088,7 @@ abstract class Builder $fields = []; foreach ($insertFields as $field) { - $fields[] = $this->parseKey($query, $field, true); + $fields[] = $this->parseKey($query, $field); } return str_replace( -- Gitee From 66b546f7cac130712d1e08fe2620105228f4bd8a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 16 Apr 2018 13:33:00 +0800 Subject: [PATCH 1144/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3mysql=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/builder/Mysql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index ff7f0a6f..9663dca6 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -71,7 +71,7 @@ class Mysql extends Builder $fields = []; foreach ($insertFields as $field) { - $fields[] = $this->parseKey($query, $field, true); + $fields[] = $this->parseKey($query, $field); } return str_replace( -- Gitee From 25306516905740220bc3e12c6f2d75a03a87de11 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 16 Apr 2018 19:03:26 +0800 Subject: [PATCH 1145/1384] =?UTF-8?q?JSON=E6=9F=A5=E8=AF=A2=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8C=87=E5=AE=9AJSON=E6=95=B0=E6=8D=AE=E7=9A=84?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=B1=BB=E5=9E=8B=20Db=E7=B1=BB=E4=BD=BF?= =?UTF-8?q?=E7=94=A8setJsonFieldType=E6=96=B9=E6=B3=95=20=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=B1=BB=E8=AE=BE=E7=BD=AEjsonType=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=88=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=B1=BB=E5=9E=8B=E6=97=A0?= =?UTF-8?q?=E9=9C=80=E6=8C=87=E5=AE=9A=EF=BC=89=20$jsonType=20=3D=20['info?= =?UTF-8?q?->id'=20=3D>=20'int','info->user=5Fid'=20=3D>=20'int'];?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 5 ++++- library/think/db/Builder.php | 7 +++++++ library/think/db/Query.php | 23 +++++++++++++++++++++++ library/think/model/concern/Attribute.php | 6 ++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index 9a98a408..3fd30288 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -207,7 +207,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { // 设置当前模型 确保查询返回模型对象 $class = $this->query; - $query = (new $class())->connect($this->connection)->model($this)->json($this->json); + $query = (new $class())->connect($this->connection) + ->model($this) + ->json($this->json) + ->setJsonFieldType($this->jsonType); // 设置当前数据表和模型名 if (!empty($this->table)) { diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index e61e6502..863f42e8 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -414,6 +414,13 @@ abstract class Builder $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + if (strpos($field, '->')) { + $jsonType = $query->getFieldType($field); + if ($jsonType) { + $bindType = $this->connection->getFieldBindType($jsonType); + } + } + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { if (strpos($value, ':') !== 0 || !$query->isBind(substr($value, 1))) { if ($query->isBind($bindName)) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 965b8f81..f61fba68 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2076,6 +2076,29 @@ class Query return $this; } + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setJsonFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getJsonFieldType($field) + { + return isset($this->options['field_type'][$field]) ? $this->options['field_type'][$field] : null; + } + /** * 添加查询范围 * @access public diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index c91cd835..a32b85b7 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -36,6 +36,12 @@ trait Attribute */ protected $json = []; + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + /** * 数据表废弃字段 * @var array -- Gitee From 632e43f2b028b57b3b1207c6719ce44a4f15c463 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 16 Apr 2018 19:05:26 +0800 Subject: [PATCH 1146/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 863f42e8..bc343709 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -415,7 +415,7 @@ abstract class Builder $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; if (strpos($field, '->')) { - $jsonType = $query->getFieldType($field); + $jsonType = $query->getJsonFieldType($field); if ($jsonType) { $bindType = $this->connection->getFieldBindType($jsonType); } -- Gitee From 1975f2ce3df25dbe84ffb5580ada2404646a252c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 17 Apr 2018 11:12:25 +0800 Subject: [PATCH 1147/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bjson=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=B1=BB=E5=9E=8B=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index bc343709..ff74a1a8 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -412,13 +412,11 @@ abstract class Builder $value = $value->__toString(); } - $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; - if (strpos($field, '->')) { $jsonType = $query->getJsonFieldType($field); - if ($jsonType) { - $bindType = $this->connection->getFieldBindType($jsonType); - } + $bindType = $this->connection->getFieldBindType($jsonType); + } else { + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; } if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { -- Gitee From f2917f95b2ec76e8b50017cba5a4b163118d3edc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 17 Apr 2018 21:15:34 +0800 Subject: [PATCH 1148/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3selectinsert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ff74a1a8..96df307b 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -1118,8 +1118,6 @@ abstract class Builder */ public function selectInsert(Query $query, $fields, $table) { - $options = $query->getOptions(); - if (is_string($fields)) { $fields = explode(',', $fields); } @@ -1128,7 +1126,7 @@ abstract class Builder $field = $this->parseKey($query, $field, true); } - return 'INSERT INTO ' . $this->parseTable($query, $table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); } /** -- Gitee From 05f1bf7af11dd083806c147b8d5f6784db9fe3f3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Apr 2018 10:38:01 +0800 Subject: [PATCH 1149/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BwhereColumn?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E6=94=AF=E6=8C=81=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=20=E6=94=B9=E8=BF=9BparseKey=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=94=AF=E6=8C=81Expression=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E4=BC=A0=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 33 ++++++++++++++++++++++++----- library/think/db/Connection.php | 2 +- library/think/db/Query.php | 19 +++++++++++------ library/think/db/builder/Mysql.php | 6 +++++- library/think/db/builder/Pgsql.php | 8 ++++++- library/think/db/builder/Sqlite.php | 9 +++++++- library/think/db/builder/Sqlsrv.php | 4 +++- 7 files changed, 65 insertions(+), 16 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index ff74a1a8..cc19a3f8 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -33,6 +33,7 @@ abstract class Builder 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], ]; // SQL表达式 @@ -182,13 +183,13 @@ abstract class Builder * 字段名分析 * @access public * @param Query $query 查询对象 - * @param string $key 字段名 + * @param mixed $key 字段名 * @param bool $strict 严格检测 * @return string */ public function parseKey(Query $query, $key, $strict = false) { - return $key; + return $key instanceof Expression ? $key->getValue() : $key; } /** @@ -207,9 +208,7 @@ abstract class Builder $array = []; foreach ($fields as $key => $field) { - if ($field instanceof Expression) { - $array[] = $field->getValue(); - } elseif (!is_numeric($key)) { + if (!is_numeric($key)) { $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); } else { $array[] = $this->parseKey($query, $field); @@ -478,6 +477,30 @@ abstract class Builder return $whereStr; } + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, $key, $exp, array $value, $field, $bindName, $bindType) + { + // 字段比较查询 + list($op, $field2) = $value; + + if (!in_array($op, ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field2, true) . ' )'; + } + /** * 表达式查询 * @access protected diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index aa1b7402..c4657900 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1313,7 +1313,7 @@ abstract class Connection */ public function aggregate(Query $query, $aggregate, $field) { - $field = $aggregate . '(' . $this->builder->parseKey($query, $field) . ') AS tp_' . strtolower($aggregate); + $field = $aggregate . '(' . $this->builder->parseKey($query, $field, true) . ') AS tp_' . strtolower($aggregate); return $this->value($query, $field, 0); } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index f61fba68..d5c5e151 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1339,20 +1339,27 @@ class Query /** * 比较两个字段 * @access public - * @param string $field1 查询字段 - * @param string $operator 比较操作符 - * @param string $field2 比较字段 - * @param string $logic 查询逻辑 and or xor + * @param string|array $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ - public function whereColumn($field1, $operator, $field2 = null, $logic = 'AND') + public function whereColumn($field1, $operator = null, $field2 = null, $logic = 'AND') { + if (is_array($field1)) { + foreach ($field1 as $item) { + $this->whereColumn($item[0], $item[1], isset($item[2]) ? $item[2] : null); + } + return $this; + } + if (is_null($field2)) { $field2 = $operator; $operator = '='; } - return $this->whereExp($field1, $operator . ' ' . $field2, [], $logic); + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); } /** diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 9663dca6..4b486f39 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -32,6 +32,7 @@ class Mysql extends Builder '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%'; @@ -105,7 +106,7 @@ class Mysql extends Builder * 字段和表名处理 * @access public * @param Query $query 查询对象 - * @param string $key 字段名 + * @param mixed $key 字段名 * @param bool $strict 严格检测 * @return string */ @@ -113,7 +114,10 @@ class Mysql extends Builder { if (is_int($key)) { return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); } + $key = trim($key); if (strpos($key, '->') && false === strpos($key, '(')) { diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 7d2f72a1..261b6af6 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -50,12 +50,18 @@ class Pgsql extends Builder * 字段和表名处理 * @access public * @param Query $query 查询对象 - * @param string $key 字段名 + * @param mixed $key 字段名 * @param bool $strict 严格检测 * @return string */ public function parseKey(Query $query, $key, $strict = false) { + if (is_int($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + $key = trim($key); if (strpos($key, '->') && false === strpos($key, '(')) { diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 9b244617..ef01de65 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -58,13 +58,20 @@ class Sqlite extends Builder * 字段和表名处理 * @access public * @param Query $query 查询对象 - * @param string $key 字段名 + * @param mixed $key 字段名 * @param bool $strict 严格检测 * @return string */ public function parseKey(Query $query, $key, $strict = false) { + if (is_int($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + $key = trim($key); + if (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index b37f01a6..2ac57764 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -77,7 +77,7 @@ class Sqlsrv extends Builder * 字段和表名处理 * @access public * @param Query $query 查询对象 - * @param string $key 字段名 + * @param mixed $key 字段名 * @param bool $strict 严格检测 * @return string */ @@ -85,6 +85,8 @@ class Sqlsrv extends Builder { if (is_int($key)) { return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); } $key = trim($key); -- Gitee From c2c0eef8181bc8f586f3c13345a17cf2df5f04a8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Apr 2018 16:36:54 +0800 Subject: [PATCH 1150/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E7=B1=BBmake=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/library/think/Container.php b/library/think/Container.php index 08c48a07..6172a221 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -39,6 +39,12 @@ class Container */ protected $bind = []; + /** + * 容器标识别名 + * @var array + */ + protected $name = []; + /** * 获取当前容器的实例(单例) * @access public @@ -177,6 +183,8 @@ class Container $vars = []; } + $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; + if (isset($this->instances[$abstract]) && !$newInstance) { return $this->instances[$abstract]; } @@ -187,7 +195,8 @@ class Container if ($concrete instanceof Closure) { $object = $this->invokeFunction($concrete, $vars); } else { - $object = $this->make($concrete, $vars, $newInstance); + $this->name[$abstract] = $concrete; + return $this->make($concrete, $vars, $newInstance); } } else { $object = $this->invokeClass($abstract, $vars); @@ -208,6 +217,8 @@ class Container */ public function delete($abstract) { + $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; + if (isset($this->instances[$abstract])) { unset($this->instances[$abstract]); } @@ -222,6 +233,7 @@ class Container { $this->instances = []; $this->bind = []; + $this->name = []; } /** -- Gitee From d84e1bdda247ccee1b31917124d360c3201f9d5a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Apr 2018 16:50:28 +0800 Subject: [PATCH 1151/1384] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=90=8E=E8=87=AA=E5=8A=A8=E6=B8=85=E7=90=86?= =?UTF-8?q?=E5=AE=B9=E5=99=A8=E5=AF=B9=E8=B1=A1=E5=AE=9E=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/Container.php | 10 ++++++---- library/think/Response.php | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/convention.php b/convention.php index 3e2d31ff..49bcfe9e 100644 --- a/convention.php +++ b/convention.php @@ -41,6 +41,8 @@ return [ 'class_suffix' => false, // 控制器类后缀 'controller_suffix' => false, + // 需要自动清理的容器对象实例 + 'auto_clear_instance' => ['request'], // +---------------------------------------------------------------------- // | 模块设置 diff --git a/library/think/Container.php b/library/think/Container.php index 6172a221..0dac60ed 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -212,15 +212,17 @@ class Container /** * 删除容器中的对象实例 * @access public - * @param string $abstract 类名或者标识 + * @param string|array $abstract 类名或者标识 * @return void */ public function delete($abstract) { - $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; + foreach ((array) $abstract as $name) { + $name = isset($this->name[$name]) ? $this->name[$name] : $name; - if (isset($this->instances[$abstract])) { - unset($this->instances[$abstract]); + if (isset($this->instances[$name])) { + unset($this->instances[$name]); + } } } diff --git a/library/think/Response.php b/library/think/Response.php index de8feaac..1b59a872 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -159,6 +159,9 @@ class Response if (!($this instanceof RedirectResponse)) { Container::get('session')->flush(); } + + // 自动清理容器对象实例 + Container::remove(Container::get('config')->get('app.auto_clear_instance')); } /** -- Gitee From c01f2284ad0f92fe31ff4cc387d7fdd4cc6c1e71 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Apr 2018 16:52:54 +0800 Subject: [PATCH 1152/1384] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convention.php b/convention.php index 49bcfe9e..7ed9a600 100644 --- a/convention.php +++ b/convention.php @@ -42,7 +42,7 @@ return [ // 控制器类后缀 'controller_suffix' => false, // 需要自动清理的容器对象实例 - 'auto_clear_instance' => ['request'], + 'auto_clear_instance' => ['think\Request'], // +---------------------------------------------------------------------- // | 模块设置 -- Gitee From 61fb00784ef99274fb106a55ed74a2e1038094e0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Apr 2018 17:44:36 +0800 Subject: [PATCH 1153/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 -- library/think/Response.php | 3 --- 2 files changed, 5 deletions(-) diff --git a/convention.php b/convention.php index 7ed9a600..3e2d31ff 100644 --- a/convention.php +++ b/convention.php @@ -41,8 +41,6 @@ return [ 'class_suffix' => false, // 控制器类后缀 'controller_suffix' => false, - // 需要自动清理的容器对象实例 - 'auto_clear_instance' => ['think\Request'], // +---------------------------------------------------------------------- // | 模块设置 diff --git a/library/think/Response.php b/library/think/Response.php index 1b59a872..de8feaac 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -159,9 +159,6 @@ class Response if (!($this instanceof RedirectResponse)) { Container::get('session')->flush(); } - - // 自动清理容器对象实例 - Container::remove(Container::get('config')->get('app.auto_clear_instance')); } /** -- Gitee From ee0c73937f5f30adef82f085e9661deb54a81fa8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Apr 2018 18:41:24 +0800 Subject: [PATCH 1154/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomposer=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Loader.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/library/think/Loader.php b/library/think/Loader.php index af9f6bb4..b048ca74 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -41,10 +41,10 @@ class Loader private static $fallbackDirsPsr0 = []; /** - * 自动加载的文件列表 + * 需要加载的文件 * @var array */ - private static $autoloadFiles = []; + private static $files = []; /** * Composer安装路径 @@ -88,7 +88,7 @@ class Loader $declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass); - foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'prefixesPsr0', 'classMap'] as $attr) { + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { if (property_exists($composerClass, $attr)) { self::${$attr} = $composerClass::${$attr}; } @@ -340,22 +340,20 @@ class Loader self::addClassMap($classMap); } } + + if (is_file($composerPath . 'autoload_files.php')) { + self::$files = require $composerPath . 'autoload_files.php'; + } } // 加载composer autofile文件 public static function loadComposerAutoloadFiles() { - if (is_file(self::$composerPath . 'autoload_files.php')) { - $includeFiles = require self::$composerPath . 'autoload_files.php'; - foreach ($includeFiles as $fileIdentifier => $file) { - if (isset($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - continue; - } + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); - if (empty(self::$autoloadFiles[$fileIdentifier])) { - __require_file($file); - self::$autoloadFiles[$fileIdentifier] = true; - } + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } } -- Gitee From ff7f8ef3e32f322de7ae0e09176f380f8096890f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 19 Apr 2018 17:08:12 +0800 Subject: [PATCH 1155/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- library/think/template/driver/File.php | 10 +++++++++- library/think/view/driver/Php.php | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index a821c9ba..9e41c407 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -462,7 +462,7 @@ abstract class Builder // 模糊匹配 if (is_array($value)) { foreach ($value as $k => $item) { - $bindKey = $bindName . '_' . $k; + $bindKey = $bindName . '_' . intval($k); $bind[$bindKey] = [$item, $bindType]; $array[] = $key . ' ' . $exp . ' :' . $bindKey; } diff --git a/library/think/template/driver/File.php b/library/think/template/driver/File.php index 95a61403..45a0ab02 100644 --- a/library/think/template/driver/File.php +++ b/library/think/template/driver/File.php @@ -48,7 +48,15 @@ class File { if (!empty($vars) && is_array($vars)) { // 模板阵列变量分解成为独立变量 - extract($vars, EXTR_OVERWRITE); + if (isset($vars['cacheFile'])) { + $_think_cacheFile = $cacheFile; + $cacheFile = $vars['cacheFile']; + unset($vars['cacheFile'], $vars['_think_cacheFile']); + extract($vars, EXTR_OVERWRITE); + include $_think_cacheFile; + return; + } + extract($vars); } //载入模版缓存文件 diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index f6987152..7deae113 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -77,6 +77,8 @@ class Php if (isset($data['template'])) { $__template__ = $template; + $template = $data['template']; + unset($data['template'], $data['__template__']); extract($data, EXTR_OVERWRITE); include $__template__; } else { @@ -96,6 +98,8 @@ class Php { if (isset($data['content'])) { $__content__ = $content; + $content = $data['content']; + unset($data['content'], $data['__content__']); extract($data, EXTR_OVERWRITE); eval('?>' . $__content__); } else { -- Gitee From 3156528b80dfa004109dfbfdbeb770b376a13f01 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 19 Apr 2018 18:12:09 +0800 Subject: [PATCH 1156/1384] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/App.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/convention.php b/convention.php index 3e2d31ff..28b97986 100644 --- a/convention.php +++ b/convention.php @@ -79,6 +79,8 @@ return [ 'pathinfo_depr' => '/', // HTTPS代理标识 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'X-REAL-IP', // URL伪静态后缀 'url_html_suffix' => 'html', // URL普通方式参数 用于自动生成 diff --git a/library/think/App.php b/library/think/App.php index d9344791..edeb2d04 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.10'; + const VERSION = '5.1.11'; /** * 当前模块路径 -- Gitee From 630fb859907f1d58a9f21699afeb82b4a384d099 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 20 Apr 2018 12:41:07 +0800 Subject: [PATCH 1157/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BB?= =?UTF-8?q?=E6=94=AF=E6=8C=81=5F=5Fset=E6=96=B9=E6=B3=95=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index c627d06d..fd3540fc 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1593,11 +1593,11 @@ class Request // IP地址合法验证 if (filter_var($ip, FILTER_VALIDATE_IP) !== $ip) { - $ip = ($ip_mode === 'ipv4') ? '0.0.0.0' : '::'; + $ip = ('ipv4' === $ip_mode) ? '0.0.0.0' : '::'; } // 如果是ipv4地址,则直接使用ip2long返回int类型ip;如果是ipv6地址,暂时不支持,直接返回0 - $long_ip = ($ip_mode === 'ipv4') ? sprintf("%u", ip2long($ip)) : 0; + $long_ip = ('ipv4' === $ip_mode) ? sprintf("%u", ip2long($ip)) : 0; $ip = [$ip, $long_ip]; @@ -1952,10 +1952,21 @@ class Request return $this->cache; } + /** + * 设置请求数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->param[$name] = $value; + } + /** * 获取请求数据的值 * @access public - * @param string $name 名称 + * @param string $name 参数名 * @return mixed */ public function __get($name) -- Gitee From c290f25580ad8df7b6ca54601f504ab43e2924ed Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 20 Apr 2018 16:03:55 +0800 Subject: [PATCH 1158/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bcomment=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 9e41c407..bc2dcb20 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -871,7 +871,7 @@ abstract class Builder } /** - * group分析 + * orderField分析 * @access protected * @param Query $query 查询对象 * @param mixed $key @@ -945,6 +945,10 @@ abstract class Builder */ protected function parseComment(Query $query, $comment) { + if (false !== strpos($comment, '*/')) { + $comment = strstr($coment, '*/', true); + } + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; } -- Gitee From 9d2f342ce62f0b62e452a666bf09ed47bc48f116 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 21 Apr 2018 19:12:54 +0800 Subject: [PATCH 1159/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index d5c5e151..1210399d 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -3070,16 +3070,16 @@ class Query $key = isset($alias) ? $alias . '.' . $pk : $pk; // 根据主键查询 if (is_array($data)) { - $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + $where[] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; } else { - $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + $where[] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; } } elseif (is_array($pk) && is_array($data) && !empty($data)) { // 根据复合主键查询 foreach ($pk as $key) { if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[$key] = [$attr, '=', $data[$key]]; + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[] = [$attr, '=', $data[$key]]; } else { throw new Exception('miss complex primary data'); } -- Gitee From 064668d151a0ea412f2871b37498c55fb943b0cb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 21 Apr 2018 20:42:14 +0800 Subject: [PATCH 1160/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3App=E7=B1=BB=5F=5Fu?= =?UTF-8?q?nset=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index edeb2d04..35f99b56 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -891,7 +891,7 @@ class App implements \ArrayAccess public function __unset($name) { - $this->container->__unset($name); + $this->container->delete($name); } public function offsetExists($key) -- Gitee From 4780ec830bf49abbecf583a443f0c7cb1e39c2b1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 22 Apr 2018 16:10:48 +0800 Subject: [PATCH 1161/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Burl=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=9A=84=E5=9F=9F=E5=90=8D=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 2 ++ library/think/Url.php | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 645fb9e1..9d340e45 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -313,6 +313,8 @@ class Route { if (is_null($domain)) { $domain = $this->domain; + } elseif (true === $domain) { + return $this->bind; } elseif (!strpos($domain, '.')) { $domain .= '.' . $this->request->rootDomain(); } diff --git a/library/think/Url.php b/library/think/Url.php index 1e4dfd7d..4a746540 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -134,6 +134,24 @@ class Url } } + // 检测URL绑定 + if (!$this->bindCheck) { + $bind = $this->app['route']->getBind($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 (!$matchAlias) { // 路由标识不存在 直接解析 $url = $this->parseUrl($url); @@ -146,15 +164,6 @@ class Url } } - // 检测URL绑定 - if (!$this->bindCheck) { - $bind = $this->app['route']->getBind($domain ?: null); - - if ($bind && 0 === strpos($url, $bind)) { - $url = substr($url, strlen($bind) + 1); - } - - } // 还原URL分隔符 $depr = $this->app['config']->get('pathinfo_depr'); $url = str_replace('/', $depr, $url); -- Gitee From ce438c7b6921ef7e34496941dc92611f0a130ef6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 23 Apr 2018 14:32:54 +0800 Subject: [PATCH 1162/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E5=B8=83?= =?UTF-8?q?=E5=BC=8F=E5=86=99=E5=85=A5=E6=95=B0=E6=8D=AE=E5=90=8E=E5=8F=8A?= =?UTF-8?q?=E6=97=B6=E6=9F=A5=E8=AF=A2=E6=95=B0=E6=8D=AE=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 21 +++++++++++++++++++++ library/think/db/Connection.php | 23 ++++++++++++++++------- library/think/db/Query.php | 12 ++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 3fd30288..06d76b4a 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -92,6 +92,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected static $initialized = []; + /** + * 是否从主库读取(主从分布式有效) + * @var bool + */ + protected static $readMaster = false; + /** * 查询对象实例 * @var Query @@ -185,6 +191,17 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this->name; } + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $master 是否从主库读取 + * @return void + */ + public function readMaster($master) + { + static::$readMaster = $master; + } + /** * 创建新的模型实例 * @access public @@ -212,6 +229,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess ->json($this->json) ->setJsonFieldType($this->jsonType); + if (static::$readMaster) { + $query->master(true); + } + // 设置当前数据表和模型名 if (!empty($this->table)) { $query->table($this->table); diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index c4657900..2b50a385 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -90,6 +90,8 @@ abstract class Connection 'master_num' => 1, // 指定从服务器序号 'slave_no' => '', + // 模型自动读取主服务器 + 'read_master' => false, // 是否严格检查字段是否存在 'fields_strict' => true, // 数据集返回类型 @@ -718,13 +720,14 @@ abstract class Connection * @access public * @param string $sql sql指令 * @param array $bind 参数绑定 + * @param Query $query 查询对象 * @return int * @throws BindParamException * @throws \PDOException * @throws \Exception * @throws \Throwable */ - public function execute($sql, $bind = []) + public function execute($sql, $bind = [], Query $query = null) { $this->initConnect(true); @@ -768,6 +771,10 @@ abstract class Connection // 调试结束 $this->debug(false); + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->setModelReadMaster(true); + } + $this->numRows = $this->PDOStatement->rowCount(); return $this->numRows; @@ -976,7 +983,7 @@ abstract class Connection } // 执行操作 - $result = $this->execute($sql, $bind); + $result = $this->execute($sql, $bind, $query); if ($result) { $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); @@ -1037,7 +1044,7 @@ abstract class Connection if (!empty($options['fetch_sql'])) { $fetchSql[] = $this->getRealSql($sql, $bind); } else { - $count += $this->execute($sql, $bind); + $count += $this->execute($sql, $bind, $query); } } @@ -1061,7 +1068,7 @@ abstract class Connection return $this->getRealSql($sql, $bind); } - return $this->execute($sql, $bind); + return $this->execute($sql, $bind, $query); } /** @@ -1088,7 +1095,7 @@ abstract class Connection return $this->getRealSql($sql, $bind); } - return $this->execute($sql, $bind); + return $this->execute($sql, $bind, $query); } /** @@ -1165,7 +1172,7 @@ abstract class Connection } // 执行操作 - $result = '' == $sql ? 0 : $this->execute($sql, $bind); + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); if ($result) { if (is_string($pk) && isset($where[$pk])) { @@ -1177,6 +1184,7 @@ abstract class Connection $query->setOption('data', $data); $query->trigger('after_update'); + $this->setModelReadMaster(); } return $result; @@ -1231,7 +1239,7 @@ abstract class Connection } // 执行操作 - $result = $this->execute($sql, $bind); + $result = $this->execute($sql, $bind, $query); if ($result) { if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { @@ -1243,6 +1251,7 @@ abstract class Connection $options['data'] = $data; $query->trigger('after_delete'); + $this->setModelReadMaster(); } return $result; diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1210399d..a6ef6b54 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -242,6 +242,18 @@ class Query return $this->model ? $this->model->setQuery($this) : null; } + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function setModelReadMaster($master = true) + { + if ($this->model) { + $this->model->readMaster($master); + } + } + /** * 指定当前数据表名(不含前缀) * @access public -- Gitee From 075926eb90bd6fcf2f100bce4185d39419bdec51 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 23 Apr 2018 14:44:50 +0800 Subject: [PATCH 1163/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 +- library/think/db/Query.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 2b50a385..3e765f9c 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -90,7 +90,7 @@ abstract class Connection 'master_num' => 1, // 指定从服务器序号 'slave_no' => '', - // 模型自动读取主服务器 + // 模型写入后自动读取主服务器 'read_master' => false, // 是否严格检查字段是否存在 'fields_strict' => true, diff --git a/library/think/db/Query.php b/library/think/db/Query.php index a6ef6b54..714d3e12 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -243,9 +243,10 @@ class Query } /** - * 获取当前的模型对象 + * 设置模型从主库读取数据 * @access public - * @return Model|null + * @param bool $master + * @return void */ public function setModelReadMaster($master = true) { -- Gitee From 363533e9b5425e47fbaf814cfe1b54adde8489ac Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 23 Apr 2018 15:01:08 +0800 Subject: [PATCH 1164/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 3e765f9c..8741980a 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1184,7 +1184,6 @@ abstract class Connection $query->setOption('data', $data); $query->trigger('after_update'); - $this->setModelReadMaster(); } return $result; @@ -1251,7 +1250,6 @@ abstract class Connection $options['data'] = $data; $query->trigger('after_delete'); - $this->setModelReadMaster(); } return $result; -- Gitee From 14e0917cc640ca319e49e1eb2cc3638571ef4dae Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 23 Apr 2018 16:39:48 +0800 Subject: [PATCH 1165/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 06d76b4a..b72b02dc 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -94,9 +94,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 是否从主库读取(主从分布式有效) - * @var bool + * @var array */ - protected static $readMaster = false; + protected static $readMaster; /** * 查询对象实例 @@ -199,7 +199,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function readMaster($master) { - static::$readMaster = $master; + if ($master) { + static::$readMaster[static::class] = true; + } } /** @@ -229,7 +231,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess ->json($this->json) ->setJsonFieldType($this->jsonType); - if (static::$readMaster) { + if (isset(static::$readMaster[static::class])) { $query->master(true); } -- Gitee From bec65914cc3d202e3620afa5157525c5d98b6a12 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 23 Apr 2018 18:41:15 +0800 Subject: [PATCH 1166/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3value=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=BC=93=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 8741980a..e39c462d 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1268,8 +1268,8 @@ abstract class Connection $options = $query->getOptions(); if (empty($options['fetch_sql']) && !empty($options['cache'])) { - - $result = $this->getCacheData($query, $options['cache'], $field, $key); + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, $field, $key); if (false !== $result) { return $result; -- Gitee From 6cccbf5ba02baebec6b0888207fdf66c27b62e3c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 24 Apr 2018 12:18:17 +0800 Subject: [PATCH 1167/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/template/driver/File.php | 16 +++++-------- library/think/view/driver/Php.php | 31 +++++++++----------------- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/library/think/template/driver/File.php b/library/think/template/driver/File.php index 45a0ab02..3b96a0f3 100644 --- a/library/think/template/driver/File.php +++ b/library/think/template/driver/File.php @@ -15,6 +15,8 @@ use think\Exception; class File { + protected $cacheFile; + /** * 写入编译缓存 * @access public @@ -46,21 +48,15 @@ class File */ public function read($cacheFile, $vars = []) { + $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { // 模板阵列变量分解成为独立变量 - if (isset($vars['cacheFile'])) { - $_think_cacheFile = $cacheFile; - $cacheFile = $vars['cacheFile']; - unset($vars['cacheFile'], $vars['_think_cacheFile']); - extract($vars, EXTR_OVERWRITE); - include $_think_cacheFile; - return; - } - extract($vars); + extract($vars, EXTR_OVERWRITE); } //载入模版缓存文件 - include $cacheFile; + include $this->cacheFile; } /** diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 7deae113..a1a03ebd 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -31,6 +31,9 @@ class Php 'view_depr' => DIRECTORY_SEPARATOR, ]; + protected $template; + protected $content; + public function __construct($config = []) { $this->config = array_merge($this->config, (array) $config); @@ -71,20 +74,14 @@ class Php throw new TemplateNotFoundException('template not exists:' . $template, $template); } + $this->template = $template; + // 记录视图信息 Container::get('app') ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); - if (isset($data['template'])) { - $__template__ = $template; - $template = $data['template']; - unset($data['template'], $data['__template__']); - extract($data, EXTR_OVERWRITE); - include $__template__; - } else { - extract($data, EXTR_OVERWRITE); - include $template; - } + extract($data, EXTR_OVERWRITE); + include $this->template; } /** @@ -96,16 +93,10 @@ class Php */ public function display($content, $data = []) { - if (isset($data['content'])) { - $__content__ = $content; - $content = $data['content']; - unset($data['content'], $data['__content__']); - extract($data, EXTR_OVERWRITE); - eval('?>' . $__content__); - } else { - extract($data, EXTR_OVERWRITE); - eval('?>' . $content); - } + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); } /** -- Gitee From f0e0c7449f9aaaea45044088e02a3980816ab67c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 24 Apr 2018 13:38:52 +0800 Subject: [PATCH 1168/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Paginator.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/library/think/Paginator.php b/library/think/Paginator.php index e2f9b898..bbe63e2e 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -431,7 +431,15 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J public function __call($name, $arguments) { - return call_user_func_array([$this->getCollection(), $name], $arguments); + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; } } -- Gitee From b9caa9121222a7d7e09ad925d11cf825d06be072 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 24 Apr 2018 13:54:50 +0800 Subject: [PATCH 1169/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDb=E7=B1=BB?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 7 +++ library/think/paginator/Collection.php | 74 -------------------------- 2 files changed, 7 insertions(+), 74 deletions(-) delete mode 100644 library/think/paginator/Collection.php diff --git a/library/think/Db.php b/library/think/Db.php index 9be2dd2e..6af0e3d4 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -18,12 +18,19 @@ namespace think; * @method \think\db\Query master() 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 mixed value(string $field) static 获取某个字段的值 * @method array column(string $field, string $key = '') static 获取某个列的值 diff --git a/library/think/paginator/Collection.php b/library/think/paginator/Collection.php deleted file mode 100644 index 4005c9ff..00000000 --- a/library/think/paginator/Collection.php +++ /dev/null @@ -1,74 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\paginator; - -use Exception; -use think\Paginator; - -/** - * Class Collection - * @package think\paginator - * @method integer total() - * @method integer listRows() - * @method integer currentPage() - * @method string render() - * @method Paginator fragment($fragment) - * @method Paginator appends($key, $value) - * @method integer lastPage() - * @method boolean hasPages() - */ -class Collection extends \think\Collection -{ - - /** @var Paginator */ - protected $paginator; - - public function __construct($items = [], Paginator $paginator = null) - { - $this->paginator = $paginator; - parent::__construct($items); - } - - public static function make($items = [], Paginator $paginator = null) - { - return new static($items, $paginator); - } - - public function toArray() - { - if ($this->paginator) { - try { - $total = $this->total(); - } catch (Exception $e) { - $total = null; - } - - return [ - 'total' => $total, - 'per_page' => $this->listRows(), - 'current_page' => $this->currentPage(), - 'data' => parent::toArray(), - ]; - } else { - return parent::toArray(); - } - } - - public function __call($method, $args) - { - if ($this->paginator && method_exists($this->paginator, $method)) { - return call_user_func_array([$this->paginator, $method], $args); - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); - } - } -} -- Gitee From b65874b1e4501d801ceb49b49fc668fa10e057e6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 25 Apr 2018 14:59:54 +0800 Subject: [PATCH 1170/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=BB=E4=BB=8E?= =?UTF-8?q?=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Connection.php | 8 ++++---- library/think/db/Query.php | 20 ++++++++++++++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index b72b02dc..b8df1cbe 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -197,7 +197,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param bool $master 是否从主库读取 * @return void */ - public function readMaster($master) + public function readMaster($master = true) { if ($master) { static::$readMaster[static::class] = true; diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index e39c462d..bfbfe513 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -772,7 +772,7 @@ abstract class Connection $this->debug(false); if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { - $query->setModelReadMaster(true); + $query->readMaster(true); } $this->numRows = $this->PDOStatement->rowCount(); @@ -780,19 +780,19 @@ abstract class Connection return $this->numRows; } catch (\PDOException $e) { if ($this->isBreak($e)) { - return $this->close()->execute($sql, $bind); + return $this->close()->execute($sql, $bind, $query); } throw new PDOException($e, $this->config, $this->getLastsql()); } catch (\Throwable $e) { if ($this->isBreak($e)) { - return $this->close()->execute($sql, $bind); + return $this->close()->execute($sql, $bind, $query); } throw $e; } catch (\Exception $e) { if ($this->isBreak($e)) { - return $this->close()->execute($sql, $bind); + return $this->close()->execute($sql, $bind, $query); } throw $e; diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 714d3e12..3fb8c626 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -88,6 +88,12 @@ class Query */ private static $extend = []; + /** + * 读取主库的表 + * @var array + */ + private static $readMaster = []; + /** * 日期查询表达式 * @var array @@ -243,16 +249,18 @@ class Query } /** - * 设置模型从主库读取数据 + * 设置从主库读取数据 * @access public * @param bool $master * @return void */ - public function setModelReadMaster($master = true) + public function readMaster($master = true) { - if ($this->model) { - $this->model->readMaster($master); + if ($master) { + static::$readMaster[$this->getTable()] = true; } + + return $this; } /** @@ -3151,6 +3159,10 @@ class Query } } + if (isset(static::$readMaster[$options['table']])) { + $options['master'] = true; + } + foreach (['join', 'union', 'group', 'having', 'limit', 'force', 'comment'] as $name) { if (!isset($options[$name])) { $options[$name] = ''; -- Gitee From 2c16134935368911e4c59cc613262774bbef29ab Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 25 Apr 2018 15:24:30 +0800 Subject: [PATCH 1171/1384] =?UTF-8?q?sql=E6=97=A5=E5=BF=97=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=BB=E4=BB=8E=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index bfbfe513..d1257e6b 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -616,7 +616,7 @@ abstract class Connection $this->PDOStatement->execute(); // 调试结束 - $this->debug(false); + $this->debug(false, '', $master); // 返回结果集 while ($result = $this->PDOStatement->fetch($this->fetchType)) { @@ -690,7 +690,7 @@ abstract class Connection $this->PDOStatement->execute(); // 调试结束 - $this->debug(false); + $this->debug(false, '', $master); // 返回结果集 return $this->getResult($pdo, $procedure); @@ -769,7 +769,7 @@ abstract class Connection $this->PDOStatement->execute(); // 调试结束 - $this->debug(false); + $this->debug(false, '', true); if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { $query->readMaster(true); @@ -1874,9 +1874,10 @@ abstract class Connection * @access protected * @param boolean $start 调试开始标记 true 开始 false 结束 * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 * @return void */ - protected function debug($start, $sql = '') + protected function debug($start, $sql = '', $master = false) { if (!empty($this->config['debug'])) { // 开启数据库调试模式 @@ -1897,7 +1898,7 @@ abstract class Connection } // SQL监听 - $this->triggerSql($sql, $runtime, $result); + $this->triggerSql($sql, $runtime, $result, $master); } } } @@ -1919,19 +1920,27 @@ abstract class Connection * @param string $sql SQL语句 * @param float $runtime SQL运行时间 * @param mixed $explain SQL分析 - * @return bool + * @param bool $master 主从标记 + * @return void */ - protected function triggerSql($sql, $runtime, $explain = []) + protected function triggerSql($sql, $runtime, $explain = [], $master = false) { if (!empty(self::$event)) { foreach (self::$event as $callback) { if (is_callable($callback)) { - call_user_func_array($callback, [$sql, $runtime, $explain]); + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); } } } else { + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + // 未注册监听则记录到日志中 - $this->log('[ SQL ] ' . $sql . ' [ RunTime:' . $runtime . 's ]'); + $this->log('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]'); if (!empty($explain)) { $this->log('[ EXPLAIN : ' . var_export($explain, true) . ' ]'); -- Gitee From bf2e07b148d7044899c50a6a3c679839d284b936 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 25 Apr 2018 17:18:40 +0800 Subject: [PATCH 1172/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=B8=BB=E4=BB=8E?= =?UTF-8?q?=E8=AF=BB=E5=8F=96readMaster=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 16 +++++++++------- library/think/db/Connection.php | 2 +- library/think/db/Query.php | 14 +++++++------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index b8df1cbe..d1d67d45 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -194,14 +194,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 是否从主库读取数据(主从分布有效) * @access public - * @param bool $master 是否从主库读取 - * @return void + * @param bool $all 是否所有模型有效 + * @return $this */ - public function readMaster($master = true) + public function readMaster($all = false) { - if ($master) { - static::$readMaster[static::class] = true; - } + $model = $all ? '*' : static::class; + + static::$readMaster[$model] = true; + + return $this; } /** @@ -231,7 +233,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess ->json($this->json) ->setJsonFieldType($this->jsonType); - if (isset(static::$readMaster[static::class])) { + if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) { $query->master(true); } diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index d1257e6b..36b0fa54 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -772,7 +772,7 @@ abstract class Connection $this->debug(false, '', true); if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { - $query->readMaster(true); + $query->readMaster(); } $this->numRows = $this->PDOStatement->rowCount(); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 3fb8c626..c9b3f225 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -251,14 +251,14 @@ class Query /** * 设置从主库读取数据 * @access public - * @param bool $master - * @return void + * @param bool $all 是否所有表有效 + * @return $this */ - public function readMaster($master = true) + public function readMaster($all = false) { - if ($master) { - static::$readMaster[$this->getTable()] = true; - } + $table = $all ? '*' : $this->getTable(); + + static::$readMaster[$table] = true; return $this; } @@ -3159,7 +3159,7 @@ class Query } } - if (isset(static::$readMaster[$options['table']])) { + if (isset(static::$readMaster['*']) || isset(static::$readMaster[$options['table']])) { $options['master'] = true; } -- Gitee From d6d54fc7a58bb2d866b27e82023c15b36774117d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 25 Apr 2018 17:42:53 +0800 Subject: [PATCH 1173/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 35f99b56..600946a9 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.11'; + const VERSION = '5.1.12'; /** * 当前模块路径 -- Gitee From 9255518eedf75763a1b164e5f7a569f33a71f5f0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 25 Apr 2018 23:09:05 +0800 Subject: [PATCH 1174/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3like=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c9b3f225..9aa3b2ec 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1532,7 +1532,7 @@ class Query } elseif (in_array(strtoupper($op), ['REGEXP', 'NOT REGEXP', 'EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; } else { - $where = $field ? [$field, $op, $condition] : null; + $where = $field ? [$field, $op, $condition, isset($param[2]) ? $param[2] : null] : null; } return $where; -- Gitee From 4be6d7180253bc5fabdf5c14792c7903fbf39459 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 26 Apr 2018 08:24:58 +0800 Subject: [PATCH 1175/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmodel\Collection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Collection.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/think/model/Collection.php b/library/think/model/Collection.php index 1b6061a2..3a9b60f5 100644 --- a/library/think/model/Collection.php +++ b/library/think/model/Collection.php @@ -16,6 +16,18 @@ use think\Model; class Collection extends BaseCollection { + /** + * 返回数组中指定的一列 + * @access public + * @param string $column_key + * @param string|null $index_key + * @return array + */ + public function column($column_key, $index_key = null) + { + return array_column($this->toArray(), $column_key, $index_key); + } + /** * 延迟预载入关联查询 * @access public -- Gitee From e6c8f88f8cb0d0f607ada99bf0d9b567cd715ef1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 26 Apr 2018 11:11:06 +0800 Subject: [PATCH 1176/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E5=A4=84B?= =?UTF-8?q?UG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c9b3f225..0a24bd37 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -3159,7 +3159,7 @@ class Query } } - if (isset(static::$readMaster['*']) || isset(static::$readMaster[$options['table']])) { + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { $options['master'] = true; } -- Gitee From f879603ee321af8fde56d8855445cf98bc81b042 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 26 Apr 2018 11:46:41 +0800 Subject: [PATCH 1177/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Url=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Url.php b/library/think/Url.php index 4a746540..613b3211 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -136,12 +136,13 @@ class Url // 检测URL绑定 if (!$this->bindCheck) { - $bind = $this->app['route']->getBind($domain ?: null); + $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); -- Gitee From 181889c3cd0c9a0f2493f0070dcf5bc4b5bf5bac Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 26 Apr 2018 17:26:54 +0800 Subject: [PATCH 1178/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 991d2316..fcf90029 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -3079,7 +3079,6 @@ class Query public function parsePkWhere($data) { $pk = $this->getPk($this->options); - // 获取当前数据表 $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; @@ -3091,16 +3090,16 @@ class Query $key = isset($alias) ? $alias . '.' . $pk : $pk; // 根据主键查询 if (is_array($data)) { - $where[] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; } else { - $where[] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; } } elseif (is_array($pk) && is_array($data) && !empty($data)) { // 根据复合主键查询 foreach ($pk as $key) { if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[] = [$attr, '=', $data[$key]]; + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$key] = [$attr, '=', $data[$key]]; } else { throw new Exception('miss complex primary data'); } -- Gitee From d31bbeb72d9d47294ad9c22570fc9a07f9123da9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 26 Apr 2018 18:20:04 +0800 Subject: [PATCH 1179/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BUrl=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Url.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/Url.php b/library/think/Url.php index 613b3211..ac7564dc 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -134,6 +134,11 @@ class Url } } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url); + } + // 检测URL绑定 if (!$this->bindCheck) { $bind = $this->app['route']->getBind($domain && is_string($domain) ? $domain : null); @@ -153,11 +158,6 @@ class Url } } - if (!$matchAlias) { - // 路由标识不存在 直接解析 - $url = $this->parseUrl($url); - } - if (isset($info['query'])) { // 解析地址里面参数 合并到vars parse_str($info['query'], $params); -- Gitee From 96d4b33046b0c0503ebff2ccf231540222c74fbb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 27 Apr 2018 10:42:42 +0800 Subject: [PATCH 1180/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 36b0fa54..45c16434 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -814,11 +814,8 @@ abstract class Connection $options = $query->getOptions(); $pk = $query->getPk($options); - if (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($query, $options['where']['AND'][$pk]); - } - $data = $options['data']; + $query->setOption('limit', 1); if (empty($options['fetch_sql']) && !empty($options['cache'])) { // 判断查询缓存 @@ -826,7 +823,7 @@ abstract class Connection if (is_string($cache['key'])) { $key = $cache['key']; - } elseif (!isset($key)) { + } else { $key = $this->getCacheKey($query, $data); } @@ -848,7 +845,6 @@ abstract class Connection } $query->setOption('data', $data); - $query->setOption('limit', 1); // 生成查询SQL $sql = $this->builder->select($query); -- Gitee From eeda0ef163d18791938b0bb3eb43b8c2d67a8729 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 27 Apr 2018 10:47:00 +0800 Subject: [PATCH 1181/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3value=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=9A=84=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 45c16434..f2397893 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1265,7 +1265,7 @@ abstract class Connection if (empty($options['fetch_sql']) && !empty($options['cache'])) { $cache = $options['cache']; - $result = $this->getCacheData($query, $cache, $field, $key); + $result = $this->getCacheData($query, $cache, null, $key); if (false !== $result) { return $result; -- Gitee From de6bd745107690d41963480a1b26bf7961271679 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 27 Apr 2018 12:17:18 +0800 Subject: [PATCH 1182/1384] =?UTF-8?q?join=E6=96=B9=E6=B3=95=E5=92=8Cview?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=9A=84=E6=9D=A1=E4=BB=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BD=BF=E7=94=A8Expression=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index bc2dcb20..329b3f81 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -815,9 +815,12 @@ abstract class Builder $condition = []; foreach ((array) $on as $val) { - if (strpos($val, '=')) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { list($val1, $val2) = explode('=', $val, 2); - $condition[] = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + + $condition[] = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); } else { $condition[] = $val; } -- Gitee From 0d6eb631c3f65e18583bcf67981d824a0f0eb614 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 27 Apr 2018 15:38:16 +0800 Subject: [PATCH 1183/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=E7=9A=84parseKey=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 1 + library/think/db/Query.php | 2 +- library/think/db/builder/Mysql.php | 2 +- library/think/db/builder/Pgsql.php | 2 +- library/think/db/builder/Sqlite.php | 2 +- library/think/db/builder/Sqlsrv.php | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index 6af0e3d4..911501c9 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -16,6 +16,7 @@ namespace think; * @package think * @method \think\db\Query connect(array $config =[], mixed $name = false) 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 使用表达式设置数据 diff --git a/library/think/db/Query.php b/library/think/db/Query.php index fcf90029..0f283c84 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1107,7 +1107,7 @@ class Query * @access public * @param string|array $table 数据表 * @param string|array $field 查询字段 - * @param string|array $on JOIN条件 + * @param mixed $on JOIN条件 * @param string $type JOIN类型 * @return $this */ diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index 4b486f39..ab21648e 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -112,7 +112,7 @@ class Mysql extends Builder */ public function parseKey(Query $query, $key, $strict = false) { - if (is_int($key)) { + if (is_numeric($key)) { return $key; } elseif ($key instanceof Expression) { return $key->getValue(); diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 261b6af6..742c7db3 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -56,7 +56,7 @@ class Pgsql extends Builder */ public function parseKey(Query $query, $key, $strict = false) { - if (is_int($key)) { + if (is_numeric($key)) { return $key; } elseif ($key instanceof Expression) { return $key->getValue(); diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index ef01de65..2b887ca8 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -64,7 +64,7 @@ class Sqlite extends Builder */ public function parseKey(Query $query, $key, $strict = false) { - if (is_int($key)) { + if (is_numeric($key)) { return $key; } elseif ($key instanceof Expression) { return $key->getValue(); diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index 2ac57764..25da4d9f 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -83,7 +83,7 @@ class Sqlsrv extends Builder */ public function parseKey(Query $query, $key, $strict = false) { - if (is_int($key)) { + if (is_numeric($key)) { return $key; } elseif ($key instanceof Expression) { return $key->getValue(); -- Gitee From fb73bcac7f328c9f2f0fb3d16bad85f78b6e76ea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 27 Apr 2018 17:46:55 +0800 Subject: [PATCH 1184/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBhos?= =?UTF-8?q?t=E6=96=B9=E6=B3=95=E5=92=8Cdomain=E6=96=B9=E6=B3=95=E5=AF=B9?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E7=9A=84=E5=A4=84=E7=90=86=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=8C=85=E5=90=AB=E7=AB=AF=E5=8F=A3=20=E4=BC=A0?= =?UTF-8?q?=E5=85=A5true=E5=88=99=E4=B8=8D=E5=90=AB=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 21 +++++++++++++-------- library/think/Route.php | 2 +- library/think/Url.php | 5 ++--- library/think/facade/Request.php | 2 +- library/think/route/Rule.php | 2 +- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index fd3540fc..d2723a1a 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -404,12 +404,14 @@ class Request /** * 设置或获取当前包含协议的域名 * @access public - * @param string $domain 域名 + * @param string|bool $domain 域名 * @return string|$this */ public function domain($domain = null) { - if (!is_null($domain)) { + if (true === $domain) { + $this->domain = $this->scheme() . '://' . $this->host(true); + } elseif (!is_null($domain)) { $this->domain = $domain; return $this; } elseif (!$this->domain) { @@ -429,7 +431,7 @@ class Request $root = $this->config->get('app.url_domain_root'); if (!$root) { - $item = explode('.', $this->host()); + $item = explode('.', $this->host(true)); $count = count($item); $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; } @@ -450,9 +452,9 @@ class Request if ($rootDomain) { // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($this->host(), $rootDomain, true), '.')); + $domain = explode('.', rtrim(stristr($this->host(true), $rootDomain, true), '.')); } else { - $domain = explode('.', $this->host(), -2); + $domain = explode('.', $this->host(true), -2); } $this->subDomain = implode('.', $domain); @@ -1647,15 +1649,18 @@ class Request /** * 当前请求的host * @access public + * @param bool $strict true 仅仅获取HOST * @return string */ - public function host() + public function host($strict = false) { if (isset($_SERVER['HTTP_X_REAL_HOST'])) { - return $_SERVER['HTTP_X_REAL_HOST']; + $host = $_SERVER['HTTP_X_REAL_HOST']; + } else { + $host = $this->server('HTTP_HOST'); } - return $this->server('HTTP_HOST'); + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; } /** diff --git a/library/think/Route.php b/library/think/Route.php index 9d340e45..a1c9f876 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -122,7 +122,7 @@ class Route public function __construct(Request $request) { $this->request = $request; - $this->host = $this->request->host(); + $this->host = $this->request->host(true); $this->setDefaultDomain(); } diff --git a/library/think/Url.php b/library/think/Url.php index ac7564dc..89e69f83 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -105,7 +105,7 @@ class Url $url = $match[0]; if (!empty($match[1])) { - $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(); + $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(true); if ($domain || $match[1] != $host) { $domain = $match[1]; } @@ -264,9 +264,8 @@ class Url $rootDomain = $this->app['request']->rootDomain(); if (true === $domain) { - // 自动判断域名 - $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(); + $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(true); $domains = $this->app['route']->getDomains(); diff --git a/library/think/facade/Request.php b/library/think/facade/Request.php index 20b6429d..50e642fb 100644 --- a/library/think/facade/Request.php +++ b/library/think/facade/Request.php @@ -66,7 +66,7 @@ use think\Facade; * @method bool isMobile() static 检测是否使用手机访问 * @method string scheme() static 当前URL地址中的scheme参数 * @method string query() static 当前请求URL地址中的query参数 - * @method string host() static 当前请求的host + * @method string host(bool $stric = false) static 当前请求的host * @method string port() static 当前请求URL地址中的port参数 * @method string protocol() static 当前请求 SERVER_PROTOCOL * @method string remotePort() static 当前请求 REMOTE_PORT diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 96f86d93..c51828fc 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -900,7 +900,7 @@ abstract class Rule } // 域名检查 - if ((isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], $request->subDomain()]))) { + if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) { return false; } -- Gitee From dae472be49bd1732a55d7e558b0d74c6364e142b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 27 Apr 2018 18:07:14 +0800 Subject: [PATCH 1185/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index d2723a1a..55462303 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -409,16 +409,19 @@ class Request */ public function domain($domain = null) { + if (is_null($domain)) { + if (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + return $this->domain; + } + if (true === $domain) { - $this->domain = $this->scheme() . '://' . $this->host(true); - } elseif (!is_null($domain)) { - $this->domain = $domain; - return $this; - } elseif (!$this->domain) { - $this->domain = $this->scheme() . '://' . $this->host(); + return $this->scheme() . '://' . $this->host(true); } - return $this->domain; + $this->domain = $domain; + return $this; } /** -- Gitee From 4f644d99b76953140fa16021cdd608d54b459663 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 28 Apr 2018 14:34:42 +0800 Subject: [PATCH 1186/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=A2=9E=E5=8A=A0w?= =?UTF-8?q?ithEvent=E6=96=B9=E6=B3=95=E7=94=A8=E4=BA=8E=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E5=BD=93=E5=89=8D=E6=93=8D=E4=BD=9C=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E6=89=A7=E8=A1=8C=E6=A8=A1=E5=9E=8B=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/ModelEvent.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/library/think/model/concern/ModelEvent.php b/library/think/model/concern/ModelEvent.php index 1f5bdafc..83b71cd1 100644 --- a/library/think/model/concern/ModelEvent.php +++ b/library/think/model/concern/ModelEvent.php @@ -24,6 +24,12 @@ trait ModelEvent */ private static $event = []; + /** + * 是否需要事件响应 + * @var bool + */ + private $withEvent = true; + /** * 注册回调方法 * @access public @@ -43,6 +49,18 @@ trait ModelEvent self::$event[$class][$event][] = $callback; } + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent($event) + { + $this->withEvent = $event; + return $this; + } + /** * 触发事件 * @access protected @@ -53,7 +71,7 @@ trait ModelEvent { $class = static::class; - if (isset(self::$event[$class][$event])) { + if ($this->withEvent && isset(self::$event[$class][$event])) { foreach (self::$event[$class][$event] as $callback) { $result = Container::getInstance()->invoke($callback, [$this]); -- Gitee From ca35f8a2abd60c31b9821599c647c0526f628c48 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 28 Apr 2018 14:53:27 +0800 Subject: [PATCH 1187/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8BsetInc/setDec?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E6=94=AF=E6=8C=81=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/library/think/Model.php b/library/think/Model.php index d1d67d45..df3f7b32 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -595,12 +595,20 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 读取更新条件 $where = $this->getWhere(); + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + $result = $this->db(false)->where($where)->setInc($field, $step, $lazyTime); if (true !== $result) { $this->data[$field] += $step; } + // 更新回调 + $this->trigger('after_update'); + return $result; } @@ -618,12 +626,20 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 读取更新条件 $where = $this->getWhere(); + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + $result = $this->db(false)->where($where)->setDec($field, $step, $lazyTime); if (true !== $result) { $this->data[$field] -= $step; } + // 更新回调 + $this->trigger('after_update'); + return $result; } -- Gitee From f5cb5ab38f1e24351399465f7dda4b7622f8be05 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 30 Apr 2018 12:38:17 +0800 Subject: [PATCH 1188/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=B7=BB=E5=8A=A0b?= =?UTF-8?q?efore=5Frestore/after=5Frestore=E4=BA=8B=E4=BB=B6=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=A8=A1=E5=9E=8B=E4=BA=8B=E4=BB=B6=E8=A7=82=E5=AF=9F?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 5 +++ library/think/model/concern/ModelEvent.php | 40 ++++++++++++++++++++++ library/think/model/concern/SoftDelete.php | 11 +++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/library/think/Model.php b/library/think/Model.php index df3f7b32..c1b2a625 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -177,6 +177,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->connection = array_merge($config->pull('database'), $this->connection); } + if ($this->observerClass) { + // 注册模型观察者 + static::observe($this->observerClass); + } + // 执行初始化操作 $this->initialize(); } diff --git a/library/think/model/concern/ModelEvent.php b/library/think/model/concern/ModelEvent.php index 83b71cd1..06def0f1 100644 --- a/library/think/model/concern/ModelEvent.php +++ b/library/think/model/concern/ModelEvent.php @@ -12,6 +12,7 @@ namespace think\model\concern; use think\Container; +use think\Loader; /** * 模型事件处理 @@ -24,6 +25,18 @@ trait ModelEvent */ private static $event = []; + /** + * 模型事件观察 + * @var array + */ + protected static $observe = ['before_write', 'after_write', 'before_insert', 'after_insert', 'before_update', 'after_update', 'before_delete', 'after_delete', 'before_restore', 'after_restore']; + + /** + * 绑定模型事件观察者类 + * @var array + */ + protected $observerClass; + /** * 是否需要事件响应 * @var bool @@ -49,6 +62,33 @@ trait ModelEvent self::$event[$class][$event][] = $callback; } + /** + * 清除回调方法 + * @access public + * @return void + */ + public static function flushEvent() + { + self::$event[static::class] = []; + } + + /** + * 注册一个模型观察者 + * + * @param object|string $class + * @return void + */ + public static function observe($class) + { + foreach (static::$observe as $event) { + $eventFuncName = Loader::parseName($event, 1, false); + + if (method_exists($class, $eventFuncName)) { + static::event($event, [$class, $eventFuncName]); + } + } + } + /** * 当前操作的事件响应 * @access protected diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 68b4c197..da774b82 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -161,11 +161,20 @@ trait SoftDelete } if ($name) { + if (false === $this->trigger('before_restore')) { + return false; + } + // 恢复删除 - return $this->db(false) + $result = $this->db(false) ->where($where) ->useSoftDelete($name, $this->getWithTrashedExp()) ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('after_restore'); + + return $result; + } return 0; -- Gitee From 0792fd00cddffca9dc8fb045947a22ca77905db7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 30 Apr 2018 13:24:19 +0800 Subject: [PATCH 1189/1384] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/ModelEvent.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/library/think/model/concern/ModelEvent.php b/library/think/model/concern/ModelEvent.php index 06def0f1..2bb6d5fe 100644 --- a/library/think/model/concern/ModelEvent.php +++ b/library/think/model/concern/ModelEvent.php @@ -212,4 +212,25 @@ trait ModelEvent self::event('after_delete', $callback, $override); } + /** + * 模型before_restore事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeRestore($callback, $override = false) + { + self::event('before_restore', $callback, $override); + } + + /** + * 模型after_restore事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterRestore($callback, $override = false) + { + self::event('after_restore', $callback, $override); + } } -- Gitee From f9c1dec4949a1edddca009b0f3197cf6fe1c987f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 1 May 2018 18:14:23 +0800 Subject: [PATCH 1190/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=A2=9E=E5=8A=A0m?= =?UTF-8?q?obile=E6=96=B9=E6=B3=95=E8=AE=BE=E7=BD=AE=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E6=89=8B=E6=9C=BA=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index c51828fc..cb6fbffb 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -387,6 +387,17 @@ abstract class Rule return $this->option('pjax', $pjax); } + /** + * 检查是否为手机访问 + * @access public + * @param bool $mobile + * @return $this + */ + public function mobile($mobile = true) + { + return $this->option('mobile', $mobile); + } + /** * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量 * @access public @@ -884,7 +895,7 @@ abstract class Rule } // AJAX PJAX 请求检查 - foreach (['ajax', 'pjax'] as $item) { + foreach (['ajax', 'pjax', 'mobile'] as $item) { if (isset($option[$item])) { $call = 'is' . $item; if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { -- Gitee From 1c7930bac0c923475c577b5ca876f7965168303d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 May 2018 17:33:26 +0800 Subject: [PATCH 1191/1384] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93XA?= =?UTF-8?q?=E4=BA=8B=E5=8A=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 53 +++++++++++++++++++++++++++++++++ library/think/db/Query.php | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index f2397893..87b6c721 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1618,6 +1618,59 @@ abstract class Connection } } + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransX($xid) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + $this->execute("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareX($xid) + { + $this->initConnect(true); + $this->execute("XA END '$xid'"); + $this->execute("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitX($xid) + { + $this->initConnect(true); + $this->execute("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackX($xid) + { + $this->initConnect(true); + $this->execute("XA ROLLBACK '$xid'"); + } + /** * 启动事务 * @access public diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 0f283c84..9ea36ba1 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -397,6 +397,58 @@ class Query return $this->connection->getLastSql(); } + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionX($callback, array $dbs = []) + { + $xid = uniqid('xa'); + + foreach ($dbs as $key => $db) { + if ($db instanceof Query) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransX($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + foreach ($dbs as $db) { + $db->prepareX($xid); + } + + foreach ($dbs as $db) { + $db->commitX($xid); + } + + return $result; + } catch (\Exception $e) { + foreach ($dbs as $db) { + $db->rollbackX($xid); + } + throw $e; + } catch (\Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackX($xid); + } + throw $e; + } + } + /** * 执行数据库事务 * @access public -- Gitee From 1a81a8910fff2c60d44941e450197734230b84c1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 May 2018 17:38:54 +0800 Subject: [PATCH 1192/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 9ea36ba1..8c668775 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -411,6 +411,10 @@ class Query { $xid = uniqid('xa'); + if (empty($dbs)) { + $dbs[] = $this->getConnection(); + } + foreach ($dbs as $key => $db) { if ($db instanceof Query) { $db = $db->getConnection(); -- Gitee From 24a365ca489de7fad7ea8dbd9eaa7bd75df0f287 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 2 May 2018 18:01:01 +0800 Subject: [PATCH 1193/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=9F=A5=E8=AF=A2=E5=AF=B9IN=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 8c668775..9ab3d9b2 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1606,7 +1606,7 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, '=', $val]; + $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val]; } } else { // 数组批量查询 -- Gitee From 30c77fef304c04747b7436a09e4ecb9d7700882d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 May 2018 11:14:30 +0800 Subject: [PATCH 1194/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3invokeMethod?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/think/Container.php b/library/think/Container.php index 0dac60ed..f2638023 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -280,6 +280,10 @@ class Container return $reflect->invokeArgs(isset($class) ? $class : null, $args); } catch (ReflectionException $e) { + if (is_array($method) && is_object($method[0])) { + $method[0] = get_class($method[0]); + } + throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()'); } } -- Gitee From 9ac36ac8ed348e9f256d5be5d6197592d3808259 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 May 2018 13:35:59 +0800 Subject: [PATCH 1195/1384] =?UTF-8?q?xa=E4=BA=8B=E5=8A=A1=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 33 +++++------------- library/think/db/Query.php | 12 +++---- library/think/db/connector/Mysql.php | 52 ++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 87b6c721..4cb4d9fa 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1624,15 +1624,8 @@ abstract class Connection * @param string $xid XA事务id * @return void */ - public function startTransX($xid) - { - $this->initConnect(true); - if (!$this->linkID) { - return false; - } - - $this->execute("XA START '$xid'"); - } + public function startTransXa($xid) + {} /** * 预编译XA事务 @@ -1640,12 +1633,8 @@ abstract class Connection * @param string $xid XA事务id * @return void */ - public function prepareX($xid) - { - $this->initConnect(true); - $this->execute("XA END '$xid'"); - $this->execute("XA PREPARE '$xid'"); - } + public function prepareXa($xid) + {} /** * 提交XA事务 @@ -1653,11 +1642,8 @@ abstract class Connection * @param string $xid XA事务id * @return void */ - public function commitX($xid) - { - $this->initConnect(true); - $this->execute("XA COMMIT '$xid'"); - } + public function commitXa($xid) + {} /** * 回滚XA事务 @@ -1665,11 +1651,8 @@ abstract class Connection * @param string $xid XA事务id * @return void */ - public function rollbackX($xid) - { - $this->initConnect(true); - $this->execute("XA ROLLBACK '$xid'"); - } + public function rollbackXa($xid) + {} /** * 启动事务 diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 9ab3d9b2..29953aa0 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -407,7 +407,7 @@ class Query * @throws \Exception * @throws \Throwable */ - public function transactionX($callback, array $dbs = []) + public function transactionXa($callback, array $dbs = []) { $xid = uniqid('xa'); @@ -422,7 +422,7 @@ class Query $dbs[$key] = $db; } - $db->startTransX($xid); + $db->startTransXa($xid); } try { @@ -432,22 +432,22 @@ class Query } foreach ($dbs as $db) { - $db->prepareX($xid); + $db->prepareXa($xid); } foreach ($dbs as $db) { - $db->commitX($xid); + $db->commitXa($xid); } return $result; } catch (\Exception $e) { foreach ($dbs as $db) { - $db->rollbackX($xid); + $db->rollbackXa($xid); } throw $e; } catch (\Throwable $e) { foreach ($dbs as $db) { - $db->rollbackX($xid); + $db->rollbackXa($xid); } throw $e; } diff --git a/library/think/db/connector/Mysql.php b/library/think/db/connector/Mysql.php index 331d550f..93b8a182 100644 --- a/library/think/db/connector/Mysql.php +++ b/library/think/db/connector/Mysql.php @@ -154,4 +154,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->execute("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + { + $this->initConnect(true); + $this->execute("XA END '$xid'"); + $this->execute("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + { + $this->initConnect(true); + $this->execute("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + { + $this->initConnect(true); + $this->execute("XA ROLLBACK '$xid'"); + } } -- Gitee From 4b2cab6936e8d1b8bd6a777270a32a7e0fc98151 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 May 2018 13:52:23 +0800 Subject: [PATCH 1196/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=A9=BA=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=86=99=E5=85=A5=E8=BF=94=E5=9B=9E=E5=80=BC=E7=9A=84?= =?UTF-8?q?BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- library/think/db/Connection.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index 329b3f81..bebc9a37 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -1070,7 +1070,7 @@ abstract class Builder // 分析并处理数据 $data = $this->parseData($query, $options['data']); if (empty($data)) { - return 0; + return ''; } $fields = array_keys($data); diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 4cb4d9fa..af37f5b9 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -979,7 +979,7 @@ abstract class Connection } // 执行操作 - $result = $this->execute($sql, $bind, $query); + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); if ($result) { $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); -- Gitee From cb4b90eddb64b198a31dc1a20a1d7cfa91124f68 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 May 2018 14:43:59 +0800 Subject: [PATCH 1197/1384] =?UTF-8?q?redis=E9=A9=B1=E5=8A=A8=E6=94=AF?= =?UTF-8?q?=E6=8C=81predis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/Redis.php | 39 +++++++++++++++--------- library/think/session/driver/Redis.php | 42 +++++++++++++++----------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/library/think/cache/driver/Redis.php b/library/think/cache/driver/Redis.php index 77958c33..d20d3b1d 100644 --- a/library/think/cache/driver/Redis.php +++ b/library/think/cache/driver/Redis.php @@ -41,28 +41,37 @@ class Redis extends Driver */ 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 (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['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 ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } - if (0 != $this->options['select']) { - $this->handler->select($this->options['select']); + 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'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + $this->handler = new \Predis\Client($this->options, $params); + } else { + throw new \BadFunctionCallException('not support: redis'); } } diff --git a/library/think/session/driver/Redis.php b/library/think/session/driver/Redis.php index e09988ee..a521ca0f 100644 --- a/library/think/session/driver/Redis.php +++ b/library/think/session/driver/Redis.php @@ -44,23 +44,31 @@ class Redis implements SessionHandlerInterface */ public function open($savePath, $sessName) { - // 检测php环境 - if (!extension_loaded('redis')) { - throw new Exception('not support:redis'); - } - - $this->handler = new \Redis; - - // 建立连接 - $func = $this->config['persistent'] ? 'pconnect' : 'connect'; - $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); - - if ('' != $this->config['password']) { - $this->handler->auth($this->config['password']); - } - - if (0 != $this->config['select']) { - $this->handler->select($this->config['select']); + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->config as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication'])) { + $params[$key] = $val; + unset($this->config[$key]); + } + } + $this->handler = new \Predis\Client($this->config, $params); + } else { + throw new \BadFunctionCallException('not support: redis'); } return true; -- Gitee From b0c6466b19e3a3e97071ca912b68e7cc330c0275 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 3 May 2018 17:41:30 +0800 Subject: [PATCH 1198/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BparseData=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn.php | 1 + library/think/db/Builder.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 5a3e0bd6..d7c82f0c 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -51,6 +51,7 @@ return [ 'where express error' => '查询表达式错误', 'no data to update' => '没有任何数据需要更新', 'miss data to insert' => '缺少需要写入的数据', + 'not support data' => '不支持的数据表达式', 'miss complex primary data' => '缺少复合主键数据', 'miss update condition' => '缺少更新条件', 'model data Not Found' => '模型数据不存在', diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index bebc9a37..e7dfb475 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -144,6 +144,8 @@ abstract class Builder case 'DEC': $result[$item] = $item . ' - ' . floatval($val[1]); break; + case 'EXP': + throw new Exception('not support data:[' . $val[0] . ']'); } } elseif (is_scalar($val)) { // 过滤非标量数据 -- Gitee From bccca85343e9b4f08134ae7f1ec7e90ec4fb7c52 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 4 May 2018 11:55:24 +0800 Subject: [PATCH 1199/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 ++ library/think/route/dispatch/Module.php | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 600946a9..f3feda4d 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -304,6 +304,8 @@ class App implements \ArrayAccess } } + $this->setModulePath($path); + $this->request->filter($this->config('app.default_filter')); } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index ec341997..66346a15 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -78,15 +78,8 @@ class Module extends Dispatch } else { throw new HttpException(404, 'module not exists:' . $module); } - } else { - // 单一模块部署 - $module = ''; - $this->app['request']->module($module); } - // 当前模块路径 - $this->app->setModulePath($this->app->getAppPath() . ($module ? $module . DIRECTORY_SEPARATOR : '')); - // 是否自动转换控制器和操作名 $convert = is_bool($this->convert) ? $this->convert : $this->app->config('app.url_convert'); // 获取控制器名 -- Gitee From c62852704c584578a8d8df0e7e1bf886e162b0df Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 4 May 2018 18:46:45 +0800 Subject: [PATCH 1200/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index af37f5b9..9f612a05 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1141,8 +1141,12 @@ abstract class Connection $options['where']['AND'] = $where; $query->setOption('where', ['AND' => $where]); } - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($query, $options['where']['AND'][$pk]); + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } } // 更新数据 @@ -1204,8 +1208,12 @@ abstract class Connection $key = $options['cache']['key']; } elseif (!is_null($data) && true !== $data && !is_array($data)) { $key = $this->getCacheKey($query, $data); - } elseif (is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($query, $options['where']['AND'][$pk]); + } elseif (is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } } if (true !== $data && empty($options['where'])) { -- Gitee From 1290fed7773e53609d0e45490c76cc7bfd7a7b71 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 4 May 2018 18:51:25 +0800 Subject: [PATCH 1201/1384] =?UTF-8?q?App=E7=B1=BB=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index f3feda4d..07f70ddf 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -128,6 +128,12 @@ class App implements \ArrayAccess { $this->appPath = $appPath ? realpath($appPath) . DIRECTORY_SEPARATOR : $this->getAppPath(); $this->container = Container::getInstance(); + + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; } /** @@ -161,13 +167,8 @@ class App implements \ArrayAccess */ public function initialize() { - $this->beginTime = microtime(true); - $this->beginMem = memory_get_usage(); - $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; - $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; - $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; - $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; - $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); // 设置路径环境变量 $this->env->set([ -- Gitee From 16486ac014756031fe7308a7d22443b73a6ce22a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 7 May 2018 15:56:31 +0800 Subject: [PATCH 1202/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=AF=B9=E8=A1=A8=E8=BE=BE=E5=BC=8FExpressio?= =?UTF-8?q?n=E5=AF=B9=E8=B1=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 29953aa0..aeba2c0e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -1606,7 +1606,13 @@ class Query if (key($field) !== 0) { $where = []; foreach ($field as $key => $val) { - $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val]; + if ($val instanceof Expression) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } else { + $where[] = [$key, is_array($val) ? 'IN' : '=', $val]; + } } } else { // 数组批量查询 -- Gitee From 1b25ee5033c94c3fcf0f8c55f6eee3e29858b8f9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 9 May 2018 16:03:43 +0800 Subject: [PATCH 1203/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper.php b/helper.php index 30670f9f..8a965036 100644 --- a/helper.php +++ b/helper.php @@ -70,7 +70,7 @@ if (!function_exists('app')) { * @param string $name 类名或标识 默认获取当前应用实例 * @param array $args 参数 * @param bool $newInstance 是否每次创建新的实例 - * @return object + * @return mixed|\think\App */ function app($name = 'think\App', $args = [], $newInstance = false) { -- Gitee From ec81a1a3ad8afd1a6d635255ce10566b9eb998be Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 10 May 2018 16:36:48 +0800 Subject: [PATCH 1204/1384] =?UTF-8?q?Facade=E7=B1=BB=E7=9A=84getFacadeClas?= =?UTF-8?q?s=E6=96=B9=E6=B3=95=E6=94=AF=E6=8C=81=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E9=97=AD=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 18 ++++++++++++------ library/think/Facade.php | 14 +++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index f2638023..8f6631a4 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -130,17 +130,23 @@ class Container /** * 绑定一个类实例当容器 * @access public - * @param string $abstract 类名或者标识 - * @param object $instance 类的实例 + * @param string $abstract 类名或者标识 + * @param object|\Closure $instance 类的实例 * @return $this */ public function instance($abstract, $instance) { - if (isset($this->bind[$abstract])) { - $abstract = $this->bind[$abstract]; - } + if ($instance instanceof \Closure) { + if (!isset($this->bind[$abstract])) { + $this->bind[$abstract] = $instance; + } + } else { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } - $this->instances[$abstract] = $instance; + $this->instances[$abstract] = $instance; + } return $this; } diff --git a/library/think/Facade.php b/library/think/Facade.php index c455f662..0083b005 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -61,16 +61,24 @@ class Facade $facadeClass = static::getFacadeClass(); if ($facadeClass) { - $class = $facadeClass; + $abstract = $facadeClass; } elseif (isset(self::$bind[$class])) { - $class = self::$bind[$class]; + $abstract = self::$bind[$class]; + } else { + $abstract = $class; } if (static::$alwaysNewInstance) { $newInstance = true; } - return Container::getInstance()->make($class, $args, $newInstance); + if ($abstract instanceof \Closure) { + return Container::getInstance() + ->instance($class, $abstract) + ->make($class, [], $newInstance); + } + + return Container::getInstance()->make($abstract, $args, $newInstance); } /** -- Gitee From ace3ee5b3c35e76f41f34d836ec65635fadfdfa8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 10 May 2018 16:39:51 +0800 Subject: [PATCH 1205/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Facade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Facade.php b/library/think/Facade.php index 0083b005..c026c165 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -75,7 +75,7 @@ class Facade if ($abstract instanceof \Closure) { return Container::getInstance() ->instance($class, $abstract) - ->make($class, [], $newInstance); + ->make($class, $args, $newInstance); } return Container::getInstance()->make($abstract, $args, $newInstance); -- Gitee From 7ba8658e9548a68a2279b8db1f4e36140ee07541 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 10 May 2018 16:49:24 +0800 Subject: [PATCH 1206/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Facade.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/library/think/Facade.php b/library/think/Facade.php index c026c165..bb4ad2d6 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -57,17 +57,14 @@ class Facade */ protected static function createFacade($class = '', $args = [], $newInstance = false) { - $class = $class ?: static::class; - $facadeClass = static::getFacadeClass(); + $class = $class ?: static::class; - if ($facadeClass) { - $abstract = $facadeClass; - } elseif (isset(self::$bind[$class])) { - $abstract = self::$bind[$class]; - } else { - $abstract = $class; + if (isset(self::$bind[$class])) { + $class = self::$bind[$class]; } + $abstract = static::getFacadeClass() ?: $class; + if (static::$alwaysNewInstance) { $newInstance = true; } -- Gitee From a118428ae2cf09d5ad28bb9f7671f95553659905 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 10 May 2018 17:05:24 +0800 Subject: [PATCH 1207/1384] =?UTF-8?q?Facade=E7=B1=BB=E5=A2=9E=E5=8A=A0getF?= =?UTF-8?q?acadeInstance=E6=96=B9=E6=B3=95=E5=AE=9A=E4=B9=89=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 4 +--- library/think/Facade.php | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index 8f6631a4..c41ac538 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -137,9 +137,7 @@ class Container public function instance($abstract, $instance) { if ($instance instanceof \Closure) { - if (!isset($this->bind[$abstract])) { - $this->bind[$abstract] = $instance; - } + $this->bind[$abstract] = $instance; } else { if (isset($this->bind[$abstract])) { $abstract = $this->bind[$abstract]; diff --git a/library/think/Facade.php b/library/think/Facade.php index bb4ad2d6..28db7a9f 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -59,11 +59,15 @@ class Facade { $class = $class ?: static::class; - if (isset(self::$bind[$class])) { + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } elseif (isset(self::$bind[$class])) { $class = self::$bind[$class]; } - $abstract = static::getFacadeClass() ?: $class; + $abstract = static::getFacadeInstance() ?: $class; if (static::$alwaysNewInstance) { $newInstance = true; @@ -86,6 +90,14 @@ class Facade protected static function getFacadeClass() {} + /** + * 获取当前Facade对应的对象实例(闭包) + * @access protected + * @return void|\Closure + */ + protected static function getFacadeInstance() + {} + /** * 带参数实例化当前Facade类 * @access public -- Gitee From 2e423c9c2b08a5b0d1062d91673897a69cd4e70f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 11:09:30 +0800 Subject: [PATCH 1208/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=97=AD=E5=8C=85?= =?UTF-8?q?=E7=9A=84=E4=BE=9D=E8=B5=96=E6=B3=A8=E5=85=A5=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Container.php b/library/think/Container.php index c41ac538..e48d722f 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -256,7 +256,7 @@ class Container $args = $this->bindParams($reflect, $vars); - return $reflect->invokeArgs($args); + return call_user_func_array($function, $args); } catch (ReflectionException $e) { throw new Exception('function not exists: ' . $function . '()'); } -- Gitee From f05dac41472c7ce2f171eb35872856521780bf84 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 11:11:51 +0800 Subject: [PATCH 1209/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index aeba2c0e..59b1086e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -619,13 +619,7 @@ class Query { $this->parseOptions(); - $result = $this->connection->value($this, $field, $default); - - if (!empty($this->options['fetch_sql'])) { - return $result; - } - - return $result; + return $this->connection->value($this, $field, $default); } /** -- Gitee From 29ddd7b05b1fa1d64b94f7727401be7d008815a2 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Fri, 11 May 2018 11:29:43 +0800 Subject: [PATCH 1210/1384] =?UTF-8?q?Cache=E7=B1=BB=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cache.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/think/Cache.php b/library/think/Cache.php index 7d5f6997..175beac4 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -13,6 +13,14 @@ namespace think; use think\cache\Driver; +/** + * Class Cache + * + * @package think + * + * @mixin Driver + * @mixin \think\cache\driver\File + */ class Cache { /** -- Gitee From 9b839187b61c752e8c2d5a4655fd8cce34d5b528 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 13:44:53 +0800 Subject: [PATCH 1211/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 07f70ddf..0a0d6e74 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App implements \ArrayAccess { - const VERSION = '5.1.12'; + const VERSION = '5.1.13'; /** * 当前模块路径 -- Gitee From 5b0c5746e44a66aae65e48a1171465d4918cfc25 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 13:58:41 +0800 Subject: [PATCH 1212/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=A4=9A=E5=AF=B9?= =?UTF-8?q?=E5=A4=9A=E7=9A=84=E4=B8=AD=E9=97=B4=E8=A1=A8=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../think/model/relation/BelongsToMany.php | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index 67424926..a798fd6d 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -67,22 +67,37 @@ class BelongsToMany extends Relation return $this; } + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + /** * 实例化中间表模型 * @access public - * @param $data + * @param array $data + * @param bool $isUpdate * @return Pivot * @throws Exception */ - protected function newPivot($data = []) + protected function newPivot($data = [], $isUpdate = false) { $class = $this->pivotName ?: '\\think\\model\\Pivot'; $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { - return $pivot; - } else { - throw new Exception('pivot model must extends: \think\model\Pivot'); + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; } + + throw new Exception('pivot model must extends: \think\model\Pivot'); } /** @@ -106,7 +121,7 @@ class BelongsToMany extends Relation } } - $model->setRelation('pivot', $this->newPivot($pivot)); + $model->setRelation('pivot', $this->newPivot($pivot, true)); } } @@ -398,7 +413,7 @@ class BelongsToMany extends Relation } } - $set->setRelation('pivot', $this->newPivot($pivot)); + $set->setRelation('pivot', $this->newPivot($pivot, true)); $data[$pivot[$this->localKey]][] = $set; } @@ -509,7 +524,7 @@ class BelongsToMany extends Relation foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; $this->pivot->insert($pivot, true); - $result[] = $this->newPivot($pivot); + $result[] = $this->newPivot($pivot, true); } if (count($result) == 1) { -- Gitee From a959111488ee755284a9727f1ba52f4fafc18025 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 14:59:20 +0800 Subject: [PATCH 1213/1384] =?UTF-8?q?composer.json=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3e504960..cc4fca91 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ ], "require": { "php": ">=5.6.0", - "topthink/think-installer": "~1.0" + "topthink/think-installer": "2.*" }, "require-dev": { "phpunit/phpunit": "^5.0|^6.0", -- Gitee From 0e9aef3b9937182f9fca7d33dcead7c0504257b1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 15:48:34 +0800 Subject: [PATCH 1214/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E7=B1=BB=E7=9A=84invokeClass=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 10 ++++++++++ library/think/Facade.php | 10 +--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index e48d722f..6929350a 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -335,11 +335,21 @@ class Container try { $reflect = new ReflectionClass($class); + if ($reflect->hasMethod('__make')) { + $method = new ReflectionMethod($class, '__make'); + + if ($method->isPublic() && $method->isStatic()) { + $args = $this->bindParams($method, $vars); + return $method->invokeArgs(null, $args); + } + } + $constructor = $reflect->getConstructor(); $args = $constructor ? $this->bindParams($constructor, $vars) : []; return $reflect->newInstanceArgs($args); + } catch (ReflectionException $e) { throw new ClassNotFoundException('class not exists: ' . $class, $class); } diff --git a/library/think/Facade.php b/library/think/Facade.php index 28db7a9f..6cadfaaa 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -67,19 +67,11 @@ class Facade $class = self::$bind[$class]; } - $abstract = static::getFacadeInstance() ?: $class; - if (static::$alwaysNewInstance) { $newInstance = true; } - if ($abstract instanceof \Closure) { - return Container::getInstance() - ->instance($class, $abstract) - ->make($class, $args, $newInstance); - } - - return Container::getInstance()->make($abstract, $args, $newInstance); + return Container::getInstance()->make($class, $args, $newInstance); } /** -- Gitee From 1cc81707dab128e360405aa4811a27b7a5403a78 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 11 May 2018 15:50:00 +0800 Subject: [PATCH 1215/1384] =?UTF-8?q?Facade=E7=B1=BB=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Facade.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/library/think/Facade.php b/library/think/Facade.php index 6cadfaaa..28fe667b 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -82,14 +82,6 @@ class Facade protected static function getFacadeClass() {} - /** - * 获取当前Facade对应的对象实例(闭包) - * @access protected - * @return void|\Closure - */ - protected static function getFacadeInstance() - {} - /** * 带参数实例化当前Facade类 * @access public -- Gitee From 979316b135b43ef2e84ca4d6919f886bb62fff0c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 12 May 2018 11:19:52 +0800 Subject: [PATCH 1216/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=B7=A5=E5=8E=82?= =?UTF-8?q?=E7=B1=BB=E7=AE=A1=E7=90=86=E5=92=8C=E4=BE=9D=E8=B5=96=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E7=9A=84=E5=AF=B9=E8=B1=A1=E4=BC=A0=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cache.php | 7 +++--- library/think/Config.php | 6 +++-- library/think/Container.php | 8 ++++++- library/think/Debug.php | 13 ++++------ library/think/Factory.php | 36 ++++++++++++++++++++++++++++ library/think/Log.php | 14 ++++------- library/think/Template.php | 7 +++--- library/think/View.php | 6 ++--- library/think/config/driver/Ini.php | 15 ++++++++---- library/think/config/driver/Json.php | 11 ++++++--- library/think/config/driver/Xml.php | 15 ++++++++---- library/think/db/Connection.php | 5 ++-- 12 files changed, 100 insertions(+), 43 deletions(-) create mode 100644 library/think/Factory.php diff --git a/library/think/Cache.php b/library/think/Cache.php index 175beac4..1e91c2ed 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -23,6 +23,7 @@ use think\cache\Driver; */ class Cache { + use Factory; /** * 缓存实例 * @var array @@ -55,14 +56,12 @@ class Cache */ 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($this->instance[$name])) { - $class = false !== strpos($type, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type); + $type = !empty($options['type']) ? $options['type'] : 'File'; // 记录初始化信息 $this->app->log('[ CACHE ] INIT ' . $type); @@ -71,7 +70,7 @@ class Cache $name = md5(serialize($options)); } - $this->instance[$name] = new $class($options); + $this->instance[$name] = self::instanceFactory($type, $options, '\\think\\cache\\driver\\'); } return $this->instance[$name]; diff --git a/library/think/Config.php b/library/think/Config.php index 603c655f..62dbf0f2 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -13,6 +13,8 @@ namespace think; class Config implements \ArrayAccess { + use Factory; + /** * 配置参数 * @var array @@ -50,9 +52,9 @@ class Config implements \ArrayAccess $type = pathinfo($config, PATHINFO_EXTENSION); } - $class = false !== strpos($type, '\\') ? $type : '\\think\\config\\driver\\' . ucwords($type); + $object = self::instanceFactory($type, $config, '\\think\\config\\driver\\'); - return $this->set((new $class())->parse($config), $name); + return $this->set($object->parse(), $name); } /** diff --git a/library/think/Container.php b/library/think/Container.php index 6929350a..df2b124a 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -379,7 +379,13 @@ class Container if ($class) { $className = $class->getName(); - $args[] = $this->make($className); + $value = array_shift($vars); + if ($value instanceof $className) { + $args[] = $value; + } else { + array_unshift($vars, $option); + $args[] = $this->make($className); + } } elseif (1 == $type && !empty($vars)) { $args[] = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { diff --git a/library/think/Debug.php b/library/think/Debug.php index 75003770..a6b8ba24 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -11,12 +11,13 @@ namespace think; -use think\exception\ClassNotFoundException; use think\model\Collection as ModelCollection; use think\response\Redirect; class Debug { + use Factory; + /** * 区间时间信息 * @var array @@ -221,7 +222,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo($output); + echo ($output); return; } return $output; @@ -231,14 +232,10 @@ class Debug { $config = $this->app['config']->pull('trace'); $type = isset($config['type']) ? $config['type'] : 'Html'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); + unset($config['type']); - if (class_exists($class)) { - $trace = new $class($config); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } + $trace = self::instanceFactory($type, $config, '\\think\\debug\\'); if ($response instanceof Redirect) { //TODO 记录 diff --git a/library/think/Factory.php b/library/think/Factory.php new file mode 100644 index 00000000..9b7d7a23 --- /dev/null +++ b/library/think/Factory.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +trait Factory +{ + /** + * 创建工厂对象实例 + * @access protected + * @param string $name 工厂类名 + * @param mixed $option 实例化参数 + * @param string $namespace 默认命名空间 + * @return $this + */ + protected static function instanceFactory($name, $option = null, $namespace = '') + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + if (class_exists($class)) { + return Container::getInstance()->invokeClass($class, is_null($option) ? [] : [$option]); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } +} diff --git a/library/think/Log.php b/library/think/Log.php index b584ee78..d673c67c 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -11,10 +11,10 @@ namespace think; -use think\exception\ClassNotFoundException; - class Log implements LoggerInterface { + use Factory; + const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; @@ -74,21 +74,17 @@ class Log implements LoggerInterface */ 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; unset($config['type']); + if (!empty($config['close'])) { $this->allowWrite = false; } - if (class_exists($class)) { - $this->driver = new $class($config); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } + $this->driver = self::instanceFactory($type, $config, '\\think\\log\\driver\\'); // 记录初始化信息 $this->app->isDebug() && $this->record('[ LOG ] INIT ' . $type); diff --git a/library/think/Template.php b/library/think/Template.php index ef4a197f..a8c3ef7c 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -20,6 +20,8 @@ use think\exception\TemplateNotFoundException; */ class Template { + use Factory; + /** * 模板变量 * @var array @@ -97,10 +99,9 @@ class Template $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); // 初始化模板编译存储器 - $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $this->storage = new $class(); + $this->storage = self::instanceFactory($type, null, '\\think\\template\\driver\\'); } /** diff --git a/library/think/View.php b/library/think/View.php index 6127235d..c7edc285 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -13,6 +13,8 @@ namespace think; class View { + use Factory; + /** * 模板引擎实例 * @var object @@ -102,13 +104,11 @@ 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 = self::instanceFactory($type, $options, '\\think\\view\\driver\\'); return $this; } diff --git a/library/think/config/driver/Ini.php b/library/think/config/driver/Ini.php index bcd12b69..b2a647d1 100644 --- a/library/think/config/driver/Ini.php +++ b/library/think/config/driver/Ini.php @@ -13,12 +13,19 @@ namespace think\config\driver; class Ini { - public function parse($config) + protected $config; + + public function __construct($config) + { + $this->config = $config; + } + + public function parse() { - if (is_file($config)) { - return parse_ini_file($config, true); + if (is_file($this->config)) { + return parse_ini_file($this->config, true); } else { - return parse_ini_string($config, true); + return parse_ini_string($this->config, true); } } } diff --git a/library/think/config/driver/Json.php b/library/think/config/driver/Json.php index 808bfff8..0d77c8ed 100644 --- a/library/think/config/driver/Json.php +++ b/library/think/config/driver/Json.php @@ -13,14 +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); + $this->config = $config; + } - return $result; + public function parse() + { + return json_decode($this->config, true); } } diff --git a/library/think/config/driver/Xml.php b/library/think/config/driver/Xml.php index b149b57f..9d696338 100644 --- a/library/think/config/driver/Xml.php +++ b/library/think/config/driver/Xml.php @@ -13,12 +13,19 @@ namespace think\config\driver; class Xml { - public function parse($config) + protected $config; + + public function __construct($config) + { + $this->config = $config; + } + + public function parse() { - if (is_file($config)) { - $content = simplexml_load_file($config); + 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; diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 9f612a05..ea6b730f 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -20,9 +20,11 @@ use think\db\exception\BindParamException; use think\Debug; use think\Exception; use think\exception\PDOException; +use think\Factory; abstract class Connection { + use Factory; protected static $instance = []; /** @var PDOStatement PDO操作实例 */ protected $PDOStatement; @@ -189,7 +191,6 @@ abstract class Connection throw new InvalidArgumentException('Undefined db type'); } - $class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']); // 记录初始化信息 Container::get('app')->log('[ DB ] INIT ' . $options['type']); @@ -197,7 +198,7 @@ abstract class Connection $name = md5(serialize($config)); } - self::$instance[$name] = new $class($options); + self::$instance[$name] = self::instanceFactory($options['type'], $options, '\\think\\db\\connector\\'); } return self::$instance[$name]; -- Gitee From 35bf7ab599d76a806dd0e90531d2d92f1bf1f3b3 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 12 May 2018 11:26:33 +0800 Subject: [PATCH 1217/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 2 +- library/think/Factory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index df2b124a..c9397b91 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -383,7 +383,7 @@ class Container if ($value instanceof $className) { $args[] = $value; } else { - array_unshift($vars, $option); + array_unshift($vars, $value); $args[] = $this->make($className); } } elseif (1 == $type && !empty($vars)) { diff --git a/library/think/Factory.php b/library/think/Factory.php index 9b7d7a23..20a2bf1d 100644 --- a/library/think/Factory.php +++ b/library/think/Factory.php @@ -21,7 +21,7 @@ trait Factory * @param string $name 工厂类名 * @param mixed $option 实例化参数 * @param string $namespace 默认命名空间 - * @return $this + * @return mixed */ protected static function instanceFactory($name, $option = null, $namespace = '') { -- Gitee From f69b54efa0444dd92679e434488dddd96b3499d8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 13 May 2018 14:37:40 +0800 Subject: [PATCH 1218/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E7=B1=BB=E7=9A=84=E5=AE=B9=E5=99=A8=E5=AE=9E=E4=BE=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 89 +++++++++++++++++---------------- library/think/App.php | 5 ++ library/think/Cache.php | 29 ++++++----- library/think/Container.php | 29 ++++++++--- library/think/Controller.php | 4 +- library/think/Cookie.php | 28 ++++------- library/think/Debug.php | 18 +++++-- library/think/Error.php | 21 +++++++- library/think/Lang.php | 20 +++++--- library/think/Log.php | 18 +++---- library/think/Request.php | 56 +++++++++++---------- library/think/Route.php | 6 --- library/think/Session.php | 13 ++--- library/think/Template.php | 5 ++ library/think/View.php | 5 ++ library/think/db/Connection.php | 6 +++ 16 files changed, 204 insertions(+), 148 deletions(-) diff --git a/convention.php b/convention.php index 28b97986..8606db1a 100644 --- a/convention.php +++ b/convention.php @@ -33,8 +33,7 @@ return [ 'default_timezone' => 'Asia/Shanghai', // 是否开启多语言 'lang_switch_on' => false, - // 默认全局过滤方法 用逗号分隔多个 - 'default_filter' => '', + // 默认语言 'default_lang' => 'zh-cn', // 应用类库后缀 @@ -67,74 +66,76 @@ return [ // 自动搜索控制器 'controller_auto_search' => false, + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + // +---------------------------------------------------------------------- - // | URL设置 + // | 异常及错误设置 // +---------------------------------------------------------------------- + // 异常页面的模板文件 + 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + ], + + 'request' => [ + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', // PATHINFO变量名 用于兼容模式 - 'var_pathinfo' => 's', + 'var_pathinfo' => 's', // 兼容PATH_INFO获取 - 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], // pathinfo分隔符 - 'pathinfo_depr' => '/', + 'pathinfo_depr' => '/', // HTTPS代理标识 - 'https_agent_name' => '', + 'https_agent_name' => '', // IP代理获取标识 - 'http_agent_ip' => 'X-REAL-IP', + 'http_agent_ip' => 'X-REAL-IP', // URL伪静态后缀 - 'url_html_suffix' => 'html', + 'url_html_suffix' => 'html', // URL普通方式参数 用于自动生成 - 'url_common_param' => false, + 'url_common_param' => false, // URL参数方式 0 按名称成对解析 1 按顺序解析 - 'url_param_type' => 0, + 'url_param_type' => 0, // 是否开启路由延迟解析 - 'url_lazy_route' => false, + 'url_lazy_route' => false, // 是否强制使用路由 - 'url_route_must' => false, + 'url_route_must' => false, // 合并路由规则 - 'route_rule_merge' => false, + 'route_rule_merge' => false, // 路由是否完全匹配 - 'route_complete_match' => false, + 'route_complete_match' => false, // 使用注解路由 - 'route_annotation' => false, + 'route_annotation' => false, // 域名根,如thinkphp.cn - 'url_domain_root' => '', + 'url_domain_root' => '', // 是否自动转换URL中的控制器和操作名 - 'url_convert' => true, + 'url_convert' => true, // 默认的访问控制器层 - 'url_controller_layer' => 'controller', + 'url_controller_layer' => 'controller', // 表单请求类型伪装变量 - 'var_method' => '_method', + 'var_method' => '_method', // 表单ajax伪装变量 - 'var_ajax' => '_ajax', + 'var_ajax' => '_ajax', // 表单pjax伪装变量 - 'var_pjax' => '_pjax', + 'var_pjax' => '_pjax', // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 - 'request_cache' => false, + 'request_cache' => false, // 请求缓存有效期 - 'request_cache_expire' => null, + 'request_cache_expire' => null, // 全局请求缓存排除规则 - 'request_cache_except' => [], - - // 默认跳转页面对应的模板文件 - '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' => '', + 'request_cache_except' => [], ], - // +---------------------------------------------------------------------- // | 模板设置 // +---------------------------------------------------------------------- diff --git a/library/think/App.php b/library/think/App.php index 0a0d6e74..03c6efe7 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -218,6 +218,11 @@ class App implements \ArrayAccess } } + // 注册异常处理类 + if ($this->config('app.exception_handle')) { + Error::setExceptionHandler($this->config('app.exception_handle')); + } + // 注册根命名空间 if (!empty($this->config('app.root_namespace'))) { Loader::addNamespace($this->config('app.root_namespace')); diff --git a/library/think/Cache.php b/library/think/Cache.php index 1e91c2ed..c2b85ced 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -24,6 +24,7 @@ use think\cache\Driver; class Cache { use Factory; + /** * 缓存实例 * @var array @@ -42,9 +43,9 @@ class Cache */ protected $handler; - public function __construct(App $app) + public function __construct(array $config = []) { - $this->app = $app; + $this->config = $config; } /** @@ -63,9 +64,6 @@ class Cache if (true === $name || !isset($this->instance[$name])) { $type = !empty($options['type']) ? $options['type'] : 'File'; - // 记录初始化信息 - $this->app->log('[ CACHE ] INIT ' . $type); - if (true === $name) { $name = md5(serialize($options)); } @@ -85,14 +83,10 @@ class Cache public function init(array $options = []) { if (is_null($this->handler)) { - // 自动初始化缓存 - $config = $this->app['config']; - - 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->pull('cache'); + + if ('complex' == $options['type']) { + $default = $options['default']; + $options = $options[$default['type']] ?: $default; } $this->handler = $this->connect($options); @@ -101,6 +95,11 @@ class Cache return $this->handler; } + public static function __make(Config $config) + { + return (new static())->init($config->pull('cache')); + } + /** * 切换缓存类型 需要配置 cache.type 为 complex * @access public @@ -109,8 +108,8 @@ class Cache */ public function store($name = '') { - if ('' !== $name && 'complex' == $this->app['config']->get('cache.type')) { - return $this->connect($this->app['config']->get('cache.' . $name), strtolower($name)); + if ('' !== $name && 'complex' == $this->config['type']) { + return $this->connect($this->config[$name], strtolower($name)); } return $this->init(); diff --git a/library/think/Container.php b/library/think/Container.php index c9397b91..c292ceb3 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -378,14 +378,7 @@ class Container $class = $param->getClass(); if ($class) { - $className = $class->getName(); - $value = array_shift($vars); - if ($value instanceof $className) { - $args[] = $value; - } else { - array_unshift($vars, $value); - $args[] = $this->make($className); - } + $args[] = $this->getObjectParam($class->getName(), $vars); } elseif (1 == $type && !empty($vars)) { $args[] = array_shift($vars); } elseif (0 == $type && isset($vars[$name])) { @@ -400,4 +393,24 @@ class Container return $args; } + /** + * 获取对象类型的参数值 + * @access protected + * @param string $className 类名 + * @param array $vars 参数 + * @return mixed + */ + protected function getObjectParam($className, &$vars) + { + $value = array_shift($vars); + + if ($value instanceof $className) { + $result = $value; + } else { + array_unshift($vars, $value); + $result = $this->make($className); + } + + return $result; + } } diff --git a/library/think/Controller.php b/library/think/Controller.php index ffa38186..2a24e540 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -62,9 +62,7 @@ class Controller { $this->request = Container::get('request'); $this->app = Container::get('app'); - $this->view = Container::get('view')->init( - $this->app['config']->pull('template') - ); + $this->view = Container::get('view'); // 控制器初始化 $this->initialize(); diff --git a/library/think/Cookie.php b/library/think/Cookie.php index c645fde6..69402073 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -35,10 +35,13 @@ class Cookie ]; /** - * 是否初始化 - * @var bool + * 构造方法 + * @access public */ - protected $init; + public function __construct(array $config = []) + { + $this->config = $config; + } /** * Cookie初始化 @@ -48,17 +51,16 @@ class Cookie */ public function init(array $config = []) { - if (empty($config)) { - $config = Container::get('config')->pull('cookie'); - } - $this->config = array_merge($this->config, array_change_key_case($config)); if (!empty($this->config['httponly'])) { ini_set('session.cookie_httponly', 1); } + } - $this->init = true; + public static function __make(Config $config) + { + return new static($config->pull('cookie')); } /** @@ -87,8 +89,6 @@ class Cookie */ public function set($name, $value = '', $option = null) { - !isset($this->init) && $this->init(); - // 参数设置(会覆盖黙认设置) if (!is_null($option)) { if (is_numeric($option)) { @@ -147,8 +147,6 @@ class Cookie */ public function has($name, $prefix = null) { - !isset($this->init) && $this->init(); - $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; $name = $prefix . $name; @@ -164,8 +162,6 @@ class Cookie */ public function get($name = '', $prefix = null) { - !isset($this->init) && $this->init(); - $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; $key = $prefix . $name; @@ -204,8 +200,6 @@ class Cookie */ public function delete($name, $prefix = null) { - !isset($this->init) && $this->init(); - $config = $this->config; $prefix = !is_null($prefix) ? $prefix : $config['prefix']; $name = $prefix . $name; @@ -231,8 +225,6 @@ class Cookie return; } - !isset($this->init) && $this->init(); - // 要删除的cookie前缀,不指定则删除config设置的指定前缀 $config = $this->config; $prefix = !is_null($prefix) ? $prefix : $config['prefix']; diff --git a/library/think/Debug.php b/library/think/Debug.php index a6b8ba24..8b110f95 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -18,6 +18,12 @@ class Debug { use Factory; + /** + * 配置参数 + * @var array + */ + protected $config = []; + /** * 区间时间信息 * @var array @@ -36,9 +42,15 @@ class Debug */ protected $app; - public function __construct(App $app) + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + } + + public static function __make(App $app, Config $config) { - $this->app = $app; + return new static($app, $config->pull('trace')); } /** @@ -230,7 +242,7 @@ class Debug public function inject(Response $response, &$content) { - $config = $this->app['config']->pull('trace'); + $config = $this->config; $type = isset($config['type']) ? $config['type'] : 'Html'; unset($config['type']); diff --git a/library/think/Error.php b/library/think/Error.php index b8c8fc58..ea3328ee 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -18,6 +18,12 @@ use think\exception\ThrowableError; class Error { + /** + * 配置参数 + * @var array + */ + protected static $exceptionHandler; + /** * 注册异常处理 * @access public @@ -100,6 +106,18 @@ class Error return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); } + /** + * 设置异常处理类 + * + * @access public + * @param mixed $handle + * @return void + */ + public static function setExceptionHandler($handle) + { + self::$exceptionHandler = $handle; + } + /** * Get an instance of the exception handler. * @@ -112,7 +130,8 @@ class Error if (!$handle) { // 异常处理handle - $class = Container::get('config')->get('exception_handle'); + $class = self::$exceptionHandler; + if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { $handle = new $class; } else { diff --git a/library/think/Lang.php b/library/think/Lang.php index 2168c13a..2de0e474 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -200,11 +200,8 @@ class Lang } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // 自动侦测浏览器语言 preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); - $langSet = strtolower($matches[1]); - $acceptLangs = Container::get('config')->get('header_accept_lang'); - if (isset($acceptLangs[$langSet])) { - $langSet = $acceptLangs[$langSet]; - } elseif (isset($this->acceptLanguage[$langSet])) { + $langSet = strtolower($matches[1]); + if (isset($this->acceptLanguage[$langSet])) { $langSet = $this->acceptLanguage[$langSet]; } } @@ -258,8 +255,19 @@ class Lang * @param array $list 语言列表 * @return void */ - public function setAllowLangList($list) + public function setAllowLangList(array $list) { $this->allowLangList = $list; } + + /** + * 设置转义的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public function setAcceptLanguage(array $list) + { + $this->acceptLanguage = array_merge($this->acceptLanguage, $list); + } } diff --git a/library/think/Log.php b/library/think/Log.php index d673c67c..3edb83c2 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -66,6 +66,11 @@ class Log implements LoggerInterface $this->app = $app; } + public static function __make(App $app, Config $config) + { + return (new static($app))->init($config->pull('log')); + } + /** * 日志初始化 * @access public @@ -86,9 +91,6 @@ class Log implements LoggerInterface $this->driver = self::instanceFactory($type, $config, '\\think\\log\\driver\\'); - // 记录初始化信息 - $this->app->isDebug() && $this->record('[ LOG ] INIT ' . $type); - return $this; } @@ -196,14 +198,10 @@ class Log implements LoggerInterface */ public function save() { - if (empty($this->log) || !$this->allowWrite) { + if (empty($this->log) || !$this->allowWrite || !$this->driver) { return true; } - if (is_null($this->driver)) { - $this->init($this->app['config']->pull('log')); - } - if (!$this->check($this->config)) { // 检测日志写入权限 return false; @@ -257,10 +255,6 @@ class Log implements LoggerInterface // 监听log_write $this->app['hook']->listen('log_write', $log); - if (is_null($this->driver)) { - $this->init($this->app['config']->pull('log')); - } - // 写入日志 $result = $this->driver->save($log); diff --git a/library/think/Request.php b/library/think/Request.php index 55462303..af382e3e 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -22,8 +22,8 @@ class Request protected $instance; /** - * 配置对象 - * @var Config + * 配置参数 + * @var array */ protected $config; @@ -262,22 +262,26 @@ class Request * @access public * @param array $options 参数 */ - public function __construct($options = []) + public function __construct(array $options = []) { - foreach ($options as $name => $item) { - if (property_exists($this, $name)) { - $this->$name = $item; - } - } + $this->init($options); - $this->config = Container::get('config'); + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function init(array $options = []) + { + $this->config = $options; - if (is_null($this->filter)) { - $this->filter = $this->config->get('default_filter'); + if (is_null($this->filter) && !empty($this->config['default_filter'])) { + $this->filter = $this->config['default_filter']; } + } - // 保存 php://input - $this->input = file_get_contents('php://input'); + public static function __make(Config $config) + { + return new static($config->pull('request')); } public function __call($method, $args) @@ -431,7 +435,7 @@ class Request */ public function rootDomain() { - $root = $this->config->get('app.url_domain_root'); + $root = $this->config['url_domain_root']; if (!$root) { $item = explode('.', $this->host(true)); @@ -451,7 +455,7 @@ class Request { if (is_null($this->subDomain)) { // 获取当前主域名 - $rootDomain = $this->config->get('app.url_domain_root'); + $rootDomain = $this->config['url_domain_root']; if ($rootDomain) { // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 @@ -609,10 +613,10 @@ class Request public function pathinfo() { if (is_null($this->pathinfo)) { - if (isset($_GET[$this->config->get('var_pathinfo')])) { + if (isset($_GET[$this->config['var_pathinfo']])) { // 判断URL里面是否有兼容模式参数 - $_SERVER['PATH_INFO'] = $_GET[$this->config->get('var_pathinfo')]; - unset($_GET[$this->config->get('var_pathinfo')]); + $_SERVER['PATH_INFO'] = $_GET[$this->config['var_pathinfo']]; + unset($_GET[$this->config['var_pathinfo']]); } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; @@ -622,7 +626,7 @@ class Request // 分析PATHINFO信息 if (!isset($_SERVER['PATH_INFO'])) { - foreach ($this->config->get('pathinfo_fetch') as $type) { + foreach ($this->config['pathinfo_fetch'] as $type) { if (!empty($_SERVER[$type])) { $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; @@ -645,7 +649,7 @@ class Request public function path() { if (is_null($this->path)) { - $suffix = $this->config->get('url_html_suffix'); + $suffix = $this->config['url_html_suffix']; $pathinfo = $this->pathinfo(); if (false === $suffix) { // 禁止伪静态访问 @@ -736,8 +740,8 @@ class Request // 获取原始请求类型 return $this->isCli() ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); } elseif (!$this->method) { - if (isset($_POST[$this->config->get('var_method')])) { - $this->method = strtoupper($_POST[$this->config->get('var_method')]); + if (isset($_POST[$this->config['var_method']])) { + $this->method = strtoupper($_POST[$this->config['var_method']]); $this->{$this->method}($_POST); } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); @@ -1514,7 +1518,7 @@ class Request return true; } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { return true; - } elseif ($this->config->get('https_agent_name') && isset($server[$this->config->get('https_agent_name')])) { + } elseif ($this->config['https_agent_name'] && isset($server[$this->config['https_agent_name']])) { return true; } @@ -1536,7 +1540,7 @@ class Request return $result; } - return $this->param($this->config->get('var_ajax')) ? true : $result; + return $this->param($this->config['var_ajax']) ? true : $result; } /** @@ -1553,7 +1557,7 @@ class Request return $result; } - return $this->param($this->config->get('var_pjax')) ? true : $result; + return $this->param($this->config['var_pjax']) ? true : $result; } /** @@ -1572,7 +1576,7 @@ class Request return $ip[$type]; } - $httpAgentIp = $this->config->get('http_agent_ip'); + $httpAgentIp = $this->config['http_agent_ip']; if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { $ip = $_SERVER[$httpAgentIp]; diff --git a/library/think/Route.php b/library/think/Route.php index a1c9f876..4460d174 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -47,12 +47,6 @@ class Route 'patch' => 'patch', ]; - /** - * 配置对象 - * @var Config - */ - protected $config; - /** * 请求对象 * @var Request diff --git a/library/think/Session.php b/library/think/Session.php index f52bbc62..e0fe8179 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -68,6 +68,11 @@ class Session } } + public static function __make(Config $config) + { + return (new static())->init($config->pull('session')); + } + /** * session初始化 * @access public @@ -77,12 +82,6 @@ class Session */ public function init(array $config = []) { - if (empty($config)) { - $config = Container::get('config')->pull('session'); - } - - // 记录初始化信息 - Container::get('app')->log('[ SESSION ] INIT ' . var_export($config, true)); $isDoStart = false; if (isset($config['use_trans_sid'])) { ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); @@ -161,6 +160,8 @@ class Session } else { $this->init = false; } + + return $this; } /** diff --git a/library/think/Template.php b/library/think/Template.php index a8c3ef7c..747dabef 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -104,6 +104,11 @@ class Template $this->storage = self::instanceFactory($type, null, '\\think\\template\\driver\\'); } + public static function __make(Config $config) + { + return new static($config->pull('template')); + } + /** * 模板变量赋值 * @access public diff --git a/library/think/View.php b/library/think/View.php index c7edc285..a2a02141 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -53,6 +53,11 @@ class View return $this; } + public static function __make(Config $config) + { + return (new static())->init($config->pull('template')); + } + /** * 模板变量静态赋值 * @access public diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index ea6b730f..bd2303f6 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -14,6 +14,7 @@ namespace think\db; use InvalidArgumentException; use PDO; use PDOStatement; +use think\Config; use think\Container; use think\Db; use think\db\exception\BindParamException; @@ -161,6 +162,11 @@ abstract class Connection $this->initialize(); } + public static function __make(Config $config) + { + return new static($config->pull('database')); + } + /** * 初始化 * @access protected -- Gitee From 29bcabe8947d63d9f3f81780c0049fde8e2104f4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 13 May 2018 22:06:08 +0800 Subject: [PATCH 1219/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0request=E5=92=8Crou?= =?UTF-8?q?te=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 155 ++++++++++---------- library/think/App.php | 35 +++-- library/think/Cache.php | 2 +- library/think/Cookie.php | 2 +- library/think/Request.php | 8 + library/think/Route.php | 44 +++++- library/think/route/AliasRule.php | 9 +- library/think/route/Dispatch.php | 8 +- library/think/route/Domain.php | 58 ++++---- library/think/route/Rule.php | 41 +++--- library/think/route/RuleGroup.php | 17 +-- library/think/route/RuleItem.php | 13 +- library/think/route/dispatch/Callback.php | 2 +- library/think/route/dispatch/Controller.php | 6 +- library/think/route/dispatch/Module.php | 56 ++++--- library/think/route/dispatch/Url.php | 29 ++-- library/think/route/dispatch/View.php | 2 +- 17 files changed, 261 insertions(+), 226 deletions(-) diff --git a/convention.php b/convention.php index 8606db1a..52e6fca3 100644 --- a/convention.php +++ b/convention.php @@ -6,123 +6,71 @@ return [ // +---------------------------------------------------------------------- 'app' => [ // 应用名称 - 'app_name' => '', + 'app_name' => '', // 应用地址 - 'app_host' => '', + 'app_host' => '', // 应用调试模式 - 'app_debug' => false, + 'app_debug' => false, // 应用Trace - 'app_trace' => false, + 'app_trace' => false, // 应用模式状态 - 'app_status' => '', - // 是否支持多模块 - 'app_multi_module' => true, + 'app_status' => '', // 入口自动绑定模块 - 'auto_bind_module' => false, + 'auto_bind_module' => false, // 注册的根命名空间 - 'root_namespace' => [], + 'root_namespace' => [], // 默认输出类型 - 'default_return_type' => 'html', + 'default_return_type' => 'html', // 默认AJAX 数据返回格式,可选json xml ... - 'default_ajax_return' => 'json', + 'default_ajax_return' => 'json', // 默认JSONP格式返回的处理方法 - 'default_jsonp_handler' => 'jsonpReturn', + 'default_jsonp_handler' => 'jsonpReturn', // 默认JSONP处理方法 - 'var_jsonp_handler' => 'callback', + 'var_jsonp_handler' => 'callback', // 默认时区 - 'default_timezone' => 'Asia/Shanghai', + 'default_timezone' => 'Asia/Shanghai', // 是否开启多语言 - 'lang_switch_on' => false, - - // 默认语言 - 'default_lang' => 'zh-cn', - // 应用类库后缀 - 'class_suffix' => false, - // 控制器类后缀 - 'controller_suffix' => false, - - // +---------------------------------------------------------------------- - // | 模块设置 - // +---------------------------------------------------------------------- - - // 默认模块名 - 'default_module' => 'index', - // 禁止访问模块 - 'deny_module_list' => ['common'], - // 默认控制器名 - 'default_controller' => 'Index', - // 默认操作名 - 'default_action' => 'index', + 'lang_switch_on' => false, // 默认验证器 - 'default_validate' => '', - // 默认的空模块名 - 'empty_module' => '', - // 默认的空控制器名 - 'empty_controller' => 'Error', - // 操作方法前缀 - 'use_action_prefix' => false, - // 操作方法后缀 - 'action_suffix' => '', - // 自动搜索控制器 - 'controller_auto_search' => false, - - // 默认跳转页面对应的模板文件 - 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', - 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'default_validate' => '', + // 默认语言 + 'default_lang' => 'zh-cn', // +---------------------------------------------------------------------- // | 异常及错误设置 // +---------------------------------------------------------------------- + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', // 异常页面的模板文件 - 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', - + 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', // 错误显示信息,非调试模式有效 - 'error_message' => '页面错误!请稍后再试~', + 'error_message' => '页面错误!请稍后再试~', // 显示错误信息 - 'show_error_msg' => false, + 'show_error_msg' => false, // 异常处理handle类 留空使用 \think\exception\Handle - 'exception_handle' => '', + 'exception_handle' => '', ], + // +---------------------------------------------------------------------- + // | URL请求设置 + // +---------------------------------------------------------------------- 'request' => [ - // +---------------------------------------------------------------------- - // | URL设置 - // +---------------------------------------------------------------------- // 默认全局过滤方法 用逗号分隔多个 'default_filter' => '', // PATHINFO变量名 用于兼容模式 'var_pathinfo' => 's', // 兼容PATH_INFO获取 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], - // pathinfo分隔符 - 'pathinfo_depr' => '/', // HTTPS代理标识 'https_agent_name' => '', // IP代理获取标识 'http_agent_ip' => 'X-REAL-IP', // URL伪静态后缀 'url_html_suffix' => 'html', - // 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, // 域名根,如thinkphp.cn 'url_domain_root' => '', - // 是否自动转换URL中的控制器和操作名 - 'url_convert' => true, - // 默认的访问控制器层 - 'url_controller_layer' => 'controller', // 表单请求类型伪装变量 'var_method' => '_method', // 表单ajax伪装变量 @@ -136,6 +84,57 @@ return [ // 全局请求缓存排除规则 'request_cache_except' => [], ], + + // +---------------------------------------------------------------------- + // | 路由设置 + // +---------------------------------------------------------------------- + 'route' => [ + // 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, + // 自动搜索控制器 + '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, + ], + // +---------------------------------------------------------------------- // | 模板设置 // +---------------------------------------------------------------------- diff --git a/library/think/App.php b/library/think/App.php index 03c6efe7..1648aa99 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -308,11 +308,21 @@ class App implements \ArrayAccess $this->config->load($filename, pathinfo($file, PATHINFO_FILENAME)); } } + + if ($module) { + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->request->cache( + $this->config->get('request.request_cache'), + $this->config->get('request.request_cache_expire'), + $this->config->get('request.request_cache_except') + ); + } } $this->setModulePath($path); - - $this->request->filter($this->config('app.default_filter')); } /** @@ -345,11 +355,6 @@ class App implements \ArrayAccess $dispatch = $this->dispatch; if (empty($dispatch)) { // 路由检测 - $this->route - ->lazy($this->config('app.url_lazy_route')) - ->autoSearchController($this->config('app.controller_auto_search')) - ->mergeRuleRegex($this->config('app.route_rule_merge')); - $dispatch = $this->routeCheck(); } @@ -368,9 +373,9 @@ class App implements \ArrayAccess // 请求缓存检查 $this->request->cache( - $this->config('app.request_cache'), - $this->config('app.request_cache_expire'), - $this->config('app.request_cache_except') + $this->config('request.request_cache'), + $this->config('request.request_cache_expire'), + $this->config('request.request_cache_except') ); $data = null; @@ -416,6 +421,7 @@ class App implements \ArrayAccess { // 读取默认语言 $this->lang->range($this->config('app.default_lang')); + if ($this->config('app.lang_switch_on')) { // 开启多语言机制 检测当前语言 $this->lang->detect(); @@ -473,7 +479,6 @@ class App implements \ArrayAccess public function routeCheck() { $path = $this->request->path(); - $depr = $this->config('app.pathinfo_depr'); // 路由检测 $files = scandir($this->routePath); @@ -488,10 +493,10 @@ class App implements \ArrayAccess } } - if ($this->config('app.route_annotation')) { + if ($this->config('route.route_annotation')) { // 自动生成路由定义 if ($this->debug) { - $this->build->buildRoute($this->config('app.controller_suffix')); + $this->build->buildRoute($this->config('route.controller_suffix')); } $filename = $this->runtimePath . 'build_route.php'; @@ -506,10 +511,10 @@ class App implements \ArrayAccess } // 是否强制路由模式 - $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); + $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); // 路由检测 返回一个Dispatch对象 - return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match')); + return $this->route->check($path, $must); } /** diff --git a/library/think/Cache.php b/library/think/Cache.php index c2b85ced..fc060257 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -86,7 +86,7 @@ class Cache if ('complex' == $options['type']) { $default = $options['default']; - $options = $options[$default['type']] ?: $default; + $options = isset($options[$default['type']]) ? $options[$default['type']] : $default; } $this->handler = $this->connect($options); diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 69402073..2fd8c31b 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -40,7 +40,7 @@ class Cookie */ public function __construct(array $config = []) { - $this->config = $config; + $this->init($config); } /** diff --git a/library/think/Request.php b/library/think/Request.php index af382e3e..6ceb11ff 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -279,6 +279,14 @@ class Request } } + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + return isset($this->config[$name]) ? $this->config[$name] : null; + } + public static function __make(Config $config) { return new static($config->pull('request')); diff --git a/library/think/Route.php b/library/think/Route.php index 4460d174..ea0d89a7 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -71,6 +71,12 @@ class Route */ protected $group; + /** + * 配置参数 + * @var array + */ + protected $config = []; + /** * 路由绑定 * @var array @@ -113,14 +119,36 @@ class Route */ protected $autoSearchController = true; - public function __construct(Request $request) + public function __construct(Request $request, array $config = []) { $this->request = $request; + $this->config = $config; $this->host = $this->request->host(true); $this->setDefaultDomain(); } + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + public static function __make(Request $request, Config $config) + { + $config = $config->pull('route'); + $route = new static($request, $config); + + $route->lazy($config['url_lazy_route']) + ->autoSearchController($config['controller_auto_search']) + ->mergeRuleRegex($config['route_rule_merge']); + + return $route; + } + /** * 设置路由域名及分组(包括资源路由)是否延迟解析 * @access public @@ -755,23 +783,23 @@ class Route * 检测URL路由 * @access public * @param string $url URL地址 - * @param string $depr URL分隔符 * @param bool $must 是否强制路由 - * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch * @throws RouteNotFoundException */ - public function check($url, $depr = '/', $must = false, $completeMatch = false) + public function check($url, $must = false) { // 自动检测域名路由 $domain = $this->checkDomain(); - $url = str_replace($depr, '|', $url); + $url = str_replace($this->config['pathinfo_depr'], '|', $url); + + $completeMatch = $this->config['route_complete_match']; - $result = $domain->check($this->request, $url, $depr, $completeMatch); + $result = $domain->check($this->request, $url, $completeMatch); if (false === $result && !empty($this->cross)) { // 检测跨域路由 - $result = $this->cross->check($this->request, $url, $depr, $completeMatch); + $result = $this->cross->check($this->request, $url, $completeMatch); } if (false !== $result) { @@ -783,7 +811,7 @@ class Route } // 默认路由解析 - return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->autoSearchController]); + return new UrlDispatch($this->request, $this, $url, ['auto_search' => $this->autoSearchController]); } /** diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index 21704659..5675e867 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -40,11 +40,10 @@ class AliasRule extends Domain * @access public * @param Request $request 请求对象 * @param string $url 访问地址 - * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/', $completeMatch = false) + public function check($request, $url, $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 @@ -82,13 +81,13 @@ class AliasRule extends Domain if (0 === strpos($this->route, '\\')) { // 路由到类 - return $this->bindToClass($bind, substr($this->route, 1)); + return $this->bindToClass($request, $bind, substr($this->route, 1)); } elseif (0 === strpos($this->route, '@')) { // 路由到控制器类 - return $this->bindToController($bind, substr($this->route, 1)); + return $this->bindToController($request, $bind, substr($this->route, 1)); } else { // 路由到模块/控制器 - return $this->bindToModule($bind, $this->route); + return $this->bindToModule($request, $bind, $this->route); } } diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index ec7e1549..22b46de1 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -12,11 +12,15 @@ namespace think\route; use think\Container; +use think\Request; +use think\Route; abstract class Dispatch { // 应用实例 protected $app; + protected $request; + protected $router; // 调度信息 protected $dispatch; // 调度参数 @@ -26,8 +30,10 @@ abstract class Dispatch // 是否进行大小写转换 protected $convert; - public function __construct($dispatch, $param = [], $code = null) + public function __construct(Request $request, Route $router, $dispatch, $param = [], $code = null) { + $this->request = $request; + $this->router = $router; $this->app = Container::get('app'); $this->dispatch = $dispatch; $this->param = $param; diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 9043017c..69edf946 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -45,21 +45,20 @@ class Domain extends RuleGroup * @access public * @param Request $request 请求对象 * @param string $url 访问地址 - * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/', $completeMatch = false) + public function check($request, $url, $completeMatch = false) { // 检测别名路由 - $result = $this->checkRouteAlias($request, $url, $depr); + $result = $this->checkRouteAlias($request, $url); if (false !== $result) { return $result; } // 检测URL绑定 - $result = $this->checkUrlBind($url, $depr); + $result = $this->checkUrlBind($request, $url); if (!empty($this->option['append'])) { $request->route($this->option['append']); @@ -76,7 +75,7 @@ class Domain extends RuleGroup unset($this->option['middleware']); } - return parent::check($request, $url, $depr, $completeMatch); + return parent::check($request, $url, $completeMatch); } /** @@ -98,26 +97,25 @@ class Domain extends RuleGroup * @access private * @param Request $request * @param string $url URL地址 - * @param string $depr URL分隔符 * @return Dispatch|false */ - private function checkRouteAlias($request, $url, $depr) + private function checkRouteAlias($request, $url) { $alias = strpos($url, '|') ? strstr($url, '|', true) : $url; $item = $this->router->getAlias($alias); - return $item ? $item->check($request, $url, $depr) : false; + return $item ? $item->check($request, $url) : false; } /** * 检测URL绑定 * @access private + * @param Request $request * @param string $url URL地址 - * @param string $depr URL分隔符 * @return Dispatch|false */ - private function checkUrlBind($url, $depr = '/') + private function checkUrlBind($request, $url) { if (!empty($this->bind)) { $bind = $this->bind; @@ -137,7 +135,7 @@ class Domain extends RuleGroup ]; if (isset($bindTo[$type])) { - return $this->{$bindTo[$type]}($url, $bind, $depr); + return $this->{$bindTo[$type]}($request, $url, $bind); } } @@ -156,78 +154,82 @@ class Domain extends RuleGroup /** * 绑定到类 * @access protected + * @param Request $request * @param string $url URL地址 * @param string $class 类名(带命名空间) * @return CallbackDispatch */ - protected function bindToClass($url, $class) + protected function bindToClass($request, $url, $class) { $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); if (!empty($array[1])) { - $this->parseUrlParams($array[1]); + $this->parseUrlParams($request, $array[1]); } - return new CallbackDispatch([$class, $action]); + return new CallbackDispatch($request, $this->router, [$class, $action]); } /** * 绑定到命名空间 * @access protected + * @param Request $request * @param string $url URL地址 * @param string $namespace 命名空间 * @return CallbackDispatch */ - protected function bindToNamespace($url, $namespace) + protected function bindToNamespace($request, $url, $namespace) { $array = explode('|', $url, 3); - $class = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_controller'); - $method = !empty($array[1]) ? $array[1] : Container::get('config')->get('default_action'); + $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller'); + $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action'); if (!empty($array[2])) { - $this->parseUrlParams($array[2]); + $this->parseUrlParams($request, $array[2]); } - return new CallbackDispatch([$namespace . '\\' . Loader::parseName($class, 1), $method]); + return new CallbackDispatch($request, $this->router, [$namespace . '\\' . Loader::parseName($class, 1), $method]); } /** * 绑定到控制器类 * @access protected + * @param Request $request * @param string $url URL地址 * @param string $controller 控制器名 (支持带模块名 index/user ) * @return ControllerDispatch */ - protected function bindToController($url, $controller) + protected function bindToController($request, $url, $controller) { $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); if (!empty($array[1])) { - $this->parseUrlParams($array[1]); + $this->parseUrlParams($request, $array[1]); } - return new ControllerDispatch($controller . '/' . $action); + return new ControllerDispatch($request, $this->router, $controller . '/' . $action); } /** * 绑定到模块/控制器 * @access protected + * @param Request $request * @param string $url URL地址 * @param string $controller 控制器类名(带命名空间) * @return ModuleDispatch */ - protected function bindToModule($url, $controller) + protected function bindToModule($request, $url, $controller) { $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action'); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); if (!empty($array[1])) { - $this->parseUrlParams($array[1]); + $this->parseUrlParams($request, $array[1]); } - return new ModuleDispatch($controller . '/' . $action); + return new ModuleDispatch($request, $this->router, $controller . '/' . $action); } } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index cb6fbffb..8e52660d 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -485,7 +485,7 @@ abstract class Rule $this->option['header'] = $header; if ($request->method(true) == 'OPTIONS') { - return new ResponseDispatch(Response::create()->code(204)->header($header)); + return new ResponseDispatch($request, $this->router, Response::create()->code(204)->header($header)); } } } @@ -629,7 +629,7 @@ abstract class Rule // 解析额外参数 $count = substr_count($rule, '/'); $url = array_slice(explode('|', $url), $count + 1); - $this->parseUrlParams(implode('|', $url), $matches); + $this->parseUrlParams($request, implode('|', $url), $matches); // 记录匹配的路由信息 $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); @@ -788,17 +788,17 @@ abstract class Rule { if ($route instanceof \Closure) { // 执行闭包 - $result = new CallbackDispatch($route); + $result = new CallbackDispatch($request, $this->router, $route); } elseif ($route instanceof Response) { - $result = new ResponseDispatch($route); + $result = new ResponseDispatch($request, $this->router, $route); } elseif (isset($option['view']) && false !== $option['view']) { - $result = new ViewDispatch($route, is_array($option['view']) ? $option['view'] : []); + $result = new ViewDispatch($request, $this->router, $route, is_array($option['view']) ? $option['view'] : []); } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 - $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301); + $result = new RedirectDispatch($request, $this->router, $route, [], isset($option['status']) ? $option['status'] : 301); } elseif (false !== strpos($route, '\\')) { // 路由到方法 - $result = $this->dispatchMethod($route); + $result = $this->dispatchMethod($request, $route); } elseif (0 === strpos($route, '@')) { // 路由到控制器 $result = $this->dispatchController($request, substr($route, 1)); @@ -813,17 +813,18 @@ abstract class Rule /** * 解析URL地址为 模块/控制器/操作 * @access protected + * @param Request $request Request对象 * @param string $route 路由地址 * @return CallbackDispatch */ - protected function dispatchMethod($route) + protected function dispatchMethod($request, $route) { list($path, $var) = $this->parseUrlPath($route); $route = str_replace('/', '@', implode('/', $path)); $method = strpos($route, '@') ? explode('@', $route) : $route; - return new CallbackDispatch($method, $var); + return new CallbackDispatch($request, $this->router, $method, $var); } /** @@ -837,13 +838,11 @@ abstract class Rule { list($route, $var) = $this->parseUrlPath($route); - $result = new ControllerDispatch(implode('/', $route), $var); + $result = new ControllerDispatch($request, $this->router, implode('/', $route), $var); $request->action(array_pop($route)); - $app = Container::get('app'); - $request->controller($route ? array_pop($route) : $app->config('default_controller')); - $request->module($route ? array_pop($route) : $app->config('default_module')); - $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : '')); + $request->controller($route ? array_pop($route) : $this->router->config('default_controller')); + $request->module($route ? array_pop($route) : $this->router->config('default_module')); return $result; } @@ -859,13 +858,12 @@ abstract class Rule { list($path, $var) = $this->parseUrlPath($route); - $config = Container::get('config'); $action = array_pop($path); $controller = !empty($path) ? array_pop($path) : null; - $module = $config->get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $module = $this->router->config('app_multi_module') && !empty($path) ? array_pop($path) : null; $method = $request->method(); - if ($config->get('use_action_prefix') && $this->router->getMethodPrefix($method)) { + if ($this->router->config('use_action_prefix') && $this->router->getMethodPrefix($method)) { $prefix = $this->router->getMethodPrefix($method); // 操作方法前缀支持 $action = 0 !== strpos($action, $prefix) ? $prefix . $action : $action; @@ -875,7 +873,7 @@ abstract class Rule $request->route($var); // 路由到模块/控制器/操作 - return new ModuleDispatch([$module, $controller, $action], [], false); + return new ModuleDispatch($request, $this->router, [$module, $controller, $action], [], false); } /** @@ -927,14 +925,15 @@ abstract class Rule /** * 解析URL地址中的参数Request对象 * @access protected + * @param Request $request * @param string $rule 路由规则 * @param array $var 变量 * @return void */ - protected function parseUrlParams($url, &$var = []) + protected function parseUrlParams($request, $url, &$var = []) { if ($url) { - if (Container::get('config')->get('url_param_type')) { + if ($this->router->config('url_param_type')) { $var += explode('|', $url); } else { preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { @@ -944,7 +943,7 @@ abstract class Rule } // 设置当前请求的参数 - Container::get('request')->route($var); + $request->route($var); } /** diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 1fe3b272..d063c078 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -111,11 +111,10 @@ class RuleGroup extends Rule * @access public * @param Request $request 请求对象 * @param string $url 访问地址 - * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/', $completeMatch = false) + public function check($request, $url, $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 跨域OPTIONS请求 @@ -132,7 +131,7 @@ class RuleGroup extends Rule $this->buildResourceRule($this->resource, $this->option); } elseif ($this->rule) { if ($this->rule instanceof Response) { - return new ResponseDispatch($this->rule); + return new ResponseDispatch($request, $this->router, $this->rule); } $this->parseGroupRule($this->rule); @@ -159,7 +158,7 @@ class RuleGroup extends Rule if (!empty($this->option['merge_rule_regex'])) { // 合并路由正则规则进行路由匹配检查 - $result = $this->checkMergeRuleRegex($request, $rules, $url, $depr, $completeMatch); + $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch); if (false !== $result) { return $result; @@ -168,7 +167,7 @@ class RuleGroup extends Rule // 检查分组路由 foreach ($rules as $key => $item) { - $result = $item->check($request, $url, $depr, $completeMatch); + $result = $item->check($request, $url, $completeMatch); if (false !== $result) { return $result; @@ -177,7 +176,7 @@ class RuleGroup extends Rule if ($this->auto) { // 自动解析URL地址 - $result = new UrlDispatch($this->auto . '/' . $url, ['depr' => $depr, 'auto_search' => false]); + $result = new UrlDispatch($request, $this->router, $this->auto . '/' . $url, ['auto_search' => false]); } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { // 未匹配所有路由的路由规则处理 $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); @@ -268,13 +267,13 @@ class RuleGroup extends Rule * @param Request $request 请求对象 * @param array $rules 路由规则 * @param string $url 访问地址 - * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - protected function checkMergeRuleRegex($request, &$rules, $url, $depr, $completeMatch) + protected function checkMergeRuleRegex($request, &$rules, $url, $completeMatch) { - $url = $depr . str_replace('|', $depr, $url); + $depr = $this->router->config('pathinfo_depr'); + $url = $depr . str_replace('|', $depr, $url); foreach ($rules as $key => $item) { if ($item instanceof RuleItem) { diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 117c1a9b..51058f4c 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -185,11 +185,10 @@ class RuleItem extends Rule * @param Request $request 请求对象 * @param string $url 访问地址 * @param array $match 匹配路由变量 - * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false) + public function checkRule($request, $url, $match = null, $completeMatch = false) { if ($dispatch = $this->checkCrossDomain($request)) { // 允许跨域 @@ -212,7 +211,7 @@ class RuleItem extends Rule $url = $this->urlSuffixCheck($request, $url, $option); if (is_null($match)) { - $match = $this->match($url, $option, $depr, $completeMatch); + $match = $this->match($url, $option, $completeMatch); } if (false !== $match) { @@ -231,9 +230,9 @@ class RuleItem extends Rule * @param bool $completeMatch 路由是否完全匹配 * @return Dispatch|false */ - public function check($request, $url, $depr = '/', $completeMatch = false) + public function check($request, $url, $completeMatch = false) { - return $this->checkRule($request, $url, null, $depr, $completeMatch); + return $this->checkRule($request, $url, null, $completeMatch); } /** @@ -265,11 +264,10 @@ class RuleItem extends Rule * @access private * @param string $url URL地址 * @param array $option 路由参数 - * @param string $depr URL分隔符(全局) * @param bool $completeMatch 路由是否完全匹配 * @return array|false */ - private function match($url, $option, $depr, $completeMatch) + private function match($url, $option, $completeMatch) { if (isset($option['complete_match'])) { $completeMatch = $option['complete_match']; @@ -283,6 +281,7 @@ class RuleItem extends Rule } $var = []; + $depr = $this->router->config('pathinfo_depr'); $url = $depr . str_replace('|', $depr, $url); $rule = $depr . str_replace('/', $depr, $this->rule); diff --git a/library/think/route/dispatch/Callback.php b/library/think/route/dispatch/Callback.php index 7168228c..d385cb24 100644 --- a/library/think/route/dispatch/Callback.php +++ b/library/think/route/dispatch/Callback.php @@ -19,7 +19,7 @@ class Callback extends Dispatch public function run() { // 执行回调方法 - $vars = array_merge($this->app['request']->param(), $this->param); + $vars = array_merge($this->request->param(), $this->param); return Container::getInstance()->invoke($this->dispatch, $vars); } diff --git a/library/think/route/dispatch/Controller.php b/library/think/route/dispatch/Controller.php index b48bccde..90f91e42 100644 --- a/library/think/route/dispatch/Controller.php +++ b/library/think/route/dispatch/Controller.php @@ -18,12 +18,12 @@ class Controller extends Dispatch public function run() { // 执行控制器的操作方法 - $vars = array_merge($this->app['request']->param(), $this->param); + $vars = array_merge($this->request->param(), $this->param); return $this->app->action( $this->dispatch, $vars, - $this->app->config('app.url_controller_layer'), - $this->app->config('app.controller_suffix') + $this->router->config('url_controller_layer'), + $this->router->config('controller_suffix') ); } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 66346a15..f2608c77 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -16,6 +16,8 @@ use think\Container; use think\exception\ClassNotFoundException; use think\exception\HttpException; use think\Loader; +use think\Request; +use think\Route; use think\route\Dispatch; class Module extends Dispatch @@ -23,9 +25,11 @@ class Module extends Dispatch protected $controller; protected $actionName; - public function __construct($dispatch, $param = [], $convert = null) + public function __construct(Request $request, Route $router, $dispatch, $param = [], $convert = null) { $this->app = Container::get('app'); + $this->request = $request; + $this->router = $router; $this->dispatch = $dispatch; $this->param = $param; $this->convert = $convert; @@ -40,10 +44,10 @@ class Module extends Dispatch $result = explode('/', $result); } - if ($this->app->config('app.app_multi_module')) { + if ($this->router->config('app_multi_module')) { // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $this->app->config('app.default_module'))); - $bind = $this->app['route']->getBind(); + $module = strip_tags(strtolower($result[0] ?: $this->router->config('default_module'))); + $bind = $this->router->getBind(); $available = false; if ($bind && preg_match('/^[a-z]/is', $bind)) { @@ -53,44 +57,34 @@ class Module extends Dispatch $module = $bindModule; } $available = true; - } elseif (!in_array($module, $this->app->config('app.deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { + } elseif (!in_array($module, $this->router->config('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { $available = true; - } elseif ($this->app->config('app.empty_module')) { - $module = $this->app->config('app.empty_module'); + } elseif ($this->router->config('empty_module')) { + $module = $this->router->config('empty_module'); $available = true; } // 模块初始化 if ($module && $available) { // 初始化模块 - $this->app['request']->module($module); + $this->request->module($module); $this->app->init($module); - - // 加载当前模块语言包 - $this->app['lang']->load($this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->app['request']->langset() . '.php'); - - // 模块请求缓存检查 - $this->app['request']->cache( - $this->app->config('app.request_cache'), - $this->app->config('app.request_cache_expire'), - $this->app->config('app.request_cache_except') - ); } else { throw new HttpException(404, 'module not exists:' . $module); } } // 是否自动转换控制器和操作名 - $convert = is_bool($this->convert) ? $this->convert : $this->app->config('app.url_convert'); + $convert = is_bool($this->convert) ? $this->convert : $this->router->config('url_convert'); // 获取控制器名 - $controller = strip_tags($result[1] ?: $this->app->config('app.default_controller')); + $controller = strip_tags($result[1] ?: $this->router->config('default_controller')); $this->controller = $convert ? strtolower($controller) : $controller; // 获取操作名 - $this->actionName = strip_tags($result[2] ?: $this->app->config('app.default_action')); + $this->actionName = strip_tags($result[2] ?: $this->router->config('default_action')); // 设置当前请求的控制器、操作 - $this->app['request']->controller(Loader::parseName($this->controller, 1))->action($this->actionName); + $this->request->controller(Loader::parseName($this->controller, 1))->action($this->actionName); } @@ -102,15 +96,15 @@ class Module extends Dispatch // 实例化控制器 try { $instance = $this->app->controller($this->controller, - $this->app->config('app.url_controller_layer'), - $this->app->config('app.controller_suffix'), - $this->app->config('app.empty_controller')); + $this->router->config('url_controller_layer'), + $this->router->config('controller_suffix'), + $this->router->config('empty_controller')); } catch (ClassNotFoundException $e) { throw new HttpException(404, 'controller not exists:' . $e->getClass()); } // 获取当前操作名 - $action = $this->actionName . $this->app->config('app.action_suffix'); + $action = $this->actionName . $this->router->config('action_suffix'); if (is_callable([$instance, $action])) { // 执行操作方法 @@ -119,14 +113,14 @@ class Module extends Dispatch // 严格获取当前操作方法名 $reflect = new ReflectionMethod($instance, $action); $methodName = $reflect->getName(); - $suffix = $this->app->config('app.action_suffix'); + $suffix = $this->router->config('action_suffix'); $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; - $this->app['request']->action($actionName); + $this->request->action($actionName); // 自动获取请求变量 - $vars = $this->app->config('app.url_param_type') - ? $this->app['request']->route() - : $this->app['request']->param(); + $vars = $this->router->config('url_param_type') + ? $this->request->route() + : $this->request->param(); } elseif (is_callable([$instance, '_empty'])) { // 空操作 $call = [$instance, '_empty']; diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index f26197aa..63b9c313 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -20,10 +20,11 @@ class Url extends Dispatch protected function init() { // 解析默认的URL规则 - $url = str_replace($this->param['depr'], '|', $this->dispatch); - $result = $this->parseUrl($url); + $depr = $this->router->config('pathinfo_depr'); + $url = str_replace($depr, '|', $this->dispatch); + $result = $this->parseUrl($url, $depr); - $this->dispatch = new Module($result); + $this->dispatch = new Module($this->request, $this->router, $result); } public function run() @@ -37,11 +38,9 @@ class Url extends Dispatch * @param string $url URL * @return array */ - protected function parseUrl($url) + protected function parseUrl($url, $depr) { - $router = $this->app['route']; - $bind = $router->getBind(); - $depr = $this->param['depr']; + $bind = $this->router->getBind(); if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { $bind = str_replace('/', $depr, $bind); @@ -55,7 +54,7 @@ class Url extends Dispatch } // 解析模块 - $module = $this->app->config('app_multi_module') ? array_shift($path) : null; + $module = $this->router->config('app_multi_module') ? array_shift($path) : null; if ($this->param['auto_search']) { $controller = $this->autoFindController($module, $path); } else { @@ -68,7 +67,7 @@ class Url extends Dispatch // 解析额外参数 if ($path) { - if ($this->app['config']->get('url_param_type')) { + if ($this->router->config('url_param_type')) { $var += $path; } else { preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { @@ -77,14 +76,14 @@ class Url extends Dispatch } } - $panDomain = $this->app['request']->panDomain(); + $panDomain = $this->request->panDomain(); if ($panDomain && $key = array_search('*', $var)) { // 泛域名赋值 $var[$key] = $panDomain; } // 设置当前请求的参数 - $this->app['request']->route($var); + $this->request->route($var); // 封装路由 $route = [$module, $controller, $action]; @@ -116,9 +115,7 @@ class Url extends Dispatch $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); } - $router = $this->app['route']; - - if ($router->getName($name) || $router->getName($name2)) { + if ($this->router->getName($name) || $this->router->getName($name2)) { return true; } @@ -134,8 +131,8 @@ class Url extends Dispatch */ protected function autoFindController($module, &$path) { - $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->app->config('url_controller_layer'); - $suffix = $this->app->getSuffix() || $this->app->config('controller_suffix') ? ucfirst($this->app->config('url_controller_layer')) : ''; + $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->router->config('url_controller_layer'); + $suffix = $this->app->getSuffix() || $this->router->config('controller_suffix') ? ucfirst($this->router->config('url_controller_layer')) : ''; $item = []; $find = false; diff --git a/library/think/route/dispatch/View.php b/library/think/route/dispatch/View.php index 852c8b9a..ae3071d4 100644 --- a/library/think/route/dispatch/View.php +++ b/library/think/route/dispatch/View.php @@ -19,7 +19,7 @@ class View extends Dispatch public function run() { // 渲染模板输出 - $vars = array_merge($this->app['request']->param(), $this->param); + $vars = array_merge($this->request->param(), $this->param); return Response::create($this->dispatch, 'view')->assign($vars); } -- Gitee From ee35b4e92f75225e258ba5c9728e11792f9d0bd0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 May 2018 15:43:42 +0800 Subject: [PATCH 1220/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cache.php | 14 ++++++++++---- library/think/Config.php | 2 +- library/think/Cookie.php | 5 +++++ library/think/Debug.php | 5 +++++ library/think/Middleware.php | 27 ++++++++++++++++++++++++--- library/think/Request.php | 2 +- library/think/db/Query.php | 2 +- library/think/facade/Session.php | 2 +- 8 files changed, 48 insertions(+), 11 deletions(-) diff --git a/library/think/Cache.php b/library/think/Cache.php index fc060257..cc4d1bd0 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -32,10 +32,10 @@ class Cache protected $instance = []; /** - * 应用对象 - * @var App + * 缓存配置 + * @var array */ - protected $app; + protected $config = []; /** * 操作句柄 @@ -46,6 +46,7 @@ class Cache public function __construct(array $config = []) { $this->config = $config; + $this->init($config); } /** @@ -97,7 +98,12 @@ class Cache public static function __make(Config $config) { - return (new static())->init($config->pull('cache')); + return new static($config->pull('cache')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); } /** diff --git a/library/think/Config.php b/library/think/Config.php index 62dbf0f2..ab565109 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -22,7 +22,7 @@ class Config implements \ArrayAccess private $config = []; /** - * 缓存前缀 + * 配置前缀 * @var string */ private $prefix = 'app'; diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 2fd8c31b..0b52a635 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -58,6 +58,11 @@ class Cookie } } + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + public static function __make(Config $config) { return new static($config->pull('cookie')); diff --git a/library/think/Debug.php b/library/think/Debug.php index 8b110f95..d74eef36 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -53,6 +53,11 @@ class Debug return new static($app, $config->pull('trace')); } + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + /** * 记录时间(微秒)和内存使用情况 * @access public diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 6c2d2211..a0fddeb3 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -17,7 +17,25 @@ use think\exception\HttpResponseException; class Middleware { - protected $queue = []; + protected $queue = []; + protected $config = [ + 'default_namespace' => 'app\http\middleware', + ]; + + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + public static function __make(Config $config) + { + return new static($config->pull('middleware')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } public function import(array $middlewares = []) { @@ -89,8 +107,11 @@ class Middleware } if (false === strpos($middleware, '\\')) { - $value = Container::get('config')->get('middleware.' . $middleware); - $middleware = $value ?: Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware; + if (isset($this->config[$middleware])) { + $middleware = $this->config[$middleware]; + } else { + $middleware = $this->config['default_namespace'] . $middleware; + } } if (is_array($middleware)) { diff --git a/library/think/Request.php b/library/think/Request.php index 6ceb11ff..3bd29f4c 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -272,7 +272,7 @@ class Request public function init(array $options = []) { - $this->config = $options; + $this->config = array_merge($this->config, $options); if (is_null($this->filter) && !empty($this->config['default_filter'])) { $this->filter = $this->config['default_filter']; diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 59b1086e..f51c8b2e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -653,7 +653,7 @@ class Query if (!empty($this->options['fetch_sql'])) { return $result; } elseif ($force) { - $result += 0; + $result = (float) $result; } return $result; diff --git a/library/think/facade/Session.php b/library/think/facade/Session.php index 9aad0b69..a68db382 100644 --- a/library/think/facade/Session.php +++ b/library/think/facade/Session.php @@ -19,7 +19,7 @@ use think\Facade; * @method void init(array $config = []) static session初始化 * @method bool has(string $name,string $prefix = null) static 判断session数据 * @method mixed prefix(string $prefix = '') static 设置或者获取session作用域(前缀) - * @method mixed get(string $name,string $prefix = null) static session获取 + * @method mixed get(string $name = '',string $prefix = null) static session获取 * @method mixed pull(string $name,string $prefix = null) static session获取并删除 * @method void push(string $key, mixed $value) static 添加数据到一个session数组 * @method void set(string $name, mixed $value , string $prefix = null) static 设置session数据 -- Gitee From 8b273c577bc5d48de4b785e363c67bde20a683ef Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 May 2018 15:55:12 +0800 Subject: [PATCH 1221/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 3bd29f4c..ce03f210 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -25,7 +25,7 @@ class Request * 配置参数 * @var array */ - protected $config; + protected $config = []; /** * 请求类型 -- Gitee From 049272ce7e5f2a2d5779be7e6db8e3e2e5346a46 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Mon, 14 May 2018 16:14:08 +0800 Subject: [PATCH 1222/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/facade/Route.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/facade/Route.php b/library/think/facade/Route.php index c197183b..ac458e0b 100644 --- a/library/think/facade/Route.php +++ b/library/think/facade/Route.php @@ -39,9 +39,9 @@ use think\Facade; * @method \think\Route alias(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册别名路由 * @method \think\Route setMethodPrefix(mixed $method, string $prefix = '') static 设置不同请求类型下面的方法前缀 * @method \think\Route rest(string $name, array $resource = []) static rest方法定义和修改 - * @method \think\RuleItem miss(string $route, string $method = '*', array $option = []) static 注册未匹配路由规则后的处理 - * @method \think\RuleItem auto(string $route) static 注册一个自动解析的URL路由 - * @method \think\Dispatch check(string $url, string $depr = '/', bool $must = false, bool $completeMatch = false) static 检测URL路由 + * @method \think\Route\RuleItem miss(string $route, string $method = '*', array $option = []) static 注册未匹配路由规则后的处理 + * @method \think\Route\RuleItem auto(string $route) static 注册一个自动解析的URL路由 + * @method \think\Route\Dispatch check(string $url, string $depr = '/', bool $must = false, bool $completeMatch = false) static 检测URL路由 */ class Route extends Facade { -- Gitee From 080b72c36a2a0c02756e51ffc50538d014a0e985 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 May 2018 19:41:44 +0800 Subject: [PATCH 1223/1384] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 161 ++++++++++++++++++++------------------ library/think/Request.php | 2 +- library/think/Route.php | 2 +- 3 files changed, 85 insertions(+), 80 deletions(-) diff --git a/convention.php b/convention.php index 52e6fca3..0c5f82ff 100644 --- a/convention.php +++ b/convention.php @@ -6,89 +6,104 @@ return [ // +---------------------------------------------------------------------- 'app' => [ // 应用名称 - 'app_name' => '', + 'app_name' => '', // 应用地址 - 'app_host' => '', + 'app_host' => '', // 应用调试模式 - 'app_debug' => false, + 'app_debug' => false, // 应用Trace - 'app_trace' => false, + 'app_trace' => false, // 应用模式状态 - 'app_status' => '', + 'app_status' => '', // 入口自动绑定模块 - 'auto_bind_module' => false, + 'auto_bind_module' => false, // 注册的根命名空间 - 'root_namespace' => [], + 'root_namespace' => [], // 默认输出类型 - 'default_return_type' => 'html', + 'default_return_type' => 'html', // 默认AJAX 数据返回格式,可选json xml ... - 'default_ajax_return' => 'json', + 'default_ajax_return' => 'json', // 默认JSONP格式返回的处理方法 - 'default_jsonp_handler' => 'jsonpReturn', + 'default_jsonp_handler' => 'jsonpReturn', // 默认JSONP处理方法 - 'var_jsonp_handler' => 'callback', + 'var_jsonp_handler' => 'callback', // 默认时区 - 'default_timezone' => 'Asia/Shanghai', + 'default_timezone' => 'Asia/Shanghai', // 是否开启多语言 - 'lang_switch_on' => false, + 'lang_switch_on' => false, // 默认验证器 - 'default_validate' => '', + 'default_validate' => '', // 默认语言 - 'default_lang' => 'zh-cn', + 'default_lang' => 'zh-cn', // +---------------------------------------------------------------------- - // | 异常及错误设置 + // | 模块设置 // +---------------------------------------------------------------------- - // 默认跳转页面对应的模板文件 - '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' => '', - ], + // 自动搜索控制器 + '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请求设置 - // +---------------------------------------------------------------------- - 'request' => [ // 默认全局过滤方法 用逗号分隔多个 - 'default_filter' => '', + 'default_filter' => '', // PATHINFO变量名 用于兼容模式 - 'var_pathinfo' => 's', + 'var_pathinfo' => 's', // 兼容PATH_INFO获取 - 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], // HTTPS代理标识 - 'https_agent_name' => '', + 'https_agent_name' => '', // IP代理获取标识 - 'http_agent_ip' => 'X-REAL-IP', + 'http_agent_ip' => 'X-REAL-IP', // URL伪静态后缀 - 'url_html_suffix' => 'html', + 'url_html_suffix' => 'html', // 域名根,如thinkphp.cn - 'url_domain_root' => '', + 'url_domain_root' => '', // 表单请求类型伪装变量 - 'var_method' => '_method', + 'var_method' => '_method', // 表单ajax伪装变量 - 'var_ajax' => '_ajax', + 'var_ajax' => '_ajax', // 表单pjax伪装变量 - 'var_pjax' => '_pjax', + 'var_pjax' => '_pjax', // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 - 'request_cache' => false, + 'request_cache' => false, // 请求缓存有效期 - 'request_cache_expire' => null, + 'request_cache_expire' => null, // 全局请求缓存排除规则 - 'request_cache_except' => [], - ], + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 路由设置 + // +---------------------------------------------------------------------- - // +---------------------------------------------------------------------- - // | 路由设置 - // +---------------------------------------------------------------------- - 'route' => [ // pathinfo分隔符 'pathinfo_depr' => '/', // URL普通方式参数 用于自动生成 @@ -105,34 +120,22 @@ return [ 'route_complete_match' => false, // 使用注解路由 'route_annotation' => 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, + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 默认跳转页面对应的模板文件 + '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' => '', ], // +---------------------------------------------------------------------- @@ -180,6 +183,7 @@ return [ // +---------------------------------------------------------------------- // | Trace设置 开启 app_trace 后 有效 // +---------------------------------------------------------------------- + 'trace' => [ // 内置Html Console 支持扩展 'type' => 'Html', @@ -222,6 +226,7 @@ return [ // +---------------------------------------------------------------------- // | Cookie设置 // +---------------------------------------------------------------------- + 'cookie' => [ // cookie 名称前缀 'prefix' => '', diff --git a/library/think/Request.php b/library/think/Request.php index ce03f210..12a857ae 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -289,7 +289,7 @@ class Request public static function __make(Config $config) { - return new static($config->pull('request')); + return new static($config->pull('app')); } public function __call($method, $args) diff --git a/library/think/Route.php b/library/think/Route.php index ea0d89a7..4f13bb34 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -139,7 +139,7 @@ class Route public static function __make(Request $request, Config $config) { - $config = $config->pull('route'); + $config = $config->pull('app'); $route = new static($request, $config); $route->lazy($config['url_lazy_route']) -- Gitee From 4e1402992fab05bd75a13c848cb7488d032ae6ef Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 May 2018 22:03:43 +0800 Subject: [PATCH 1224/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 12 ++++++------ library/think/db/Connection.php | 4 ++-- library/think/db/Query.php | 6 ------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 1648aa99..ccb559d8 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -315,9 +315,9 @@ class App implements \ArrayAccess // 模块请求缓存检查 $this->request->cache( - $this->config->get('request.request_cache'), - $this->config->get('request.request_cache_expire'), - $this->config->get('request.request_cache_except') + $this->config->get('request_cache'), + $this->config->get('request_cache_expire'), + $this->config->get('request_cache_except') ); } } @@ -373,9 +373,9 @@ class App implements \ArrayAccess // 请求缓存检查 $this->request->cache( - $this->config('request.request_cache'), - $this->config('request.request_cache_expire'), - $this->config('request.request_cache_except') + $this->config('request_cache'), + $this->config('request_cache_expire'), + $this->config('request_cache_except') ); $data = null; diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index bd2303f6..23463c84 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -144,10 +144,10 @@ abstract class Connection /** * 架构函数 读取数据库配置信息 - * @access public + * @access protected * @param array $config 数据库配置数组 */ - public function __construct(array $config = []) + protected function __construct(array $config = []) { if (!empty($config)) { $this->config = array_merge($this->config, $config); diff --git a/library/think/db/Query.php b/library/think/db/Query.php index f51c8b2e..1180b422 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -28,12 +28,6 @@ use think\Paginator; class Query { - /** - * 数据库连接对象列表 - * @var array - */ - protected static $connections = []; - /** * 当前数据库连接对象 * @var Connection -- Gitee From 91d8772b7188c3692b8b8cae9f4ecaf553f50ac2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 14 May 2018 22:56:22 +0800 Subject: [PATCH 1225/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/App.php | 34 +++++++++++++++++++---------- library/think/Cookie.php | 5 ----- library/think/Session.php | 26 +++++++++++++++++++++- library/think/Url.php | 45 +++++++++++++++++++++++++++++---------- 5 files changed, 84 insertions(+), 28 deletions(-) diff --git a/convention.php b/convention.php index 0c5f82ff..496192ff 100644 --- a/convention.php +++ b/convention.php @@ -15,6 +15,8 @@ return [ 'app_trace' => false, // 应用模式状态 'app_status' => '', + // 是否HTTPS + 'is_https' => false, // 入口自动绑定模块 'auto_bind_module' => false, // 注册的根命名空间 diff --git a/library/think/App.php b/library/think/App.php index ccb559d8..a9c5a733 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -308,23 +308,35 @@ class App implements \ArrayAccess $this->config->load($filename, pathinfo($file, PATHINFO_FILENAME)); } } + } - if ($module) { - // 加载当前模块语言包 - $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); - - // 模块请求缓存检查 - $this->request->cache( - $this->config->get('request_cache'), - $this->config->get('request_cache_expire'), - $this->config->get('request_cache_except') - ); - } + if ($module) { + $this->moduleContainerInit($module); } $this->setModulePath($path); } + protected function moduleContainerInit($module) + { + // 对容器中的对象实例进行配置更新 + $this->cookie->init($this->config->pull('cookie')); + $this->session->setConfig($this->config->pull('session')); + $this->view->init($this->config->pull('template')); + $this->log->init($this->config->pull('log')); + $this->__get('debug')->setConfig($this->config->pull('trace')); + + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->request->cache( + $this->config->get('request_cache'), + $this->config->get('request_cache_expire'), + $this->config->get('request_cache_except') + ); + } + /** * 执行应用程序 * @access public diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 0b52a635..2fd8c31b 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -58,11 +58,6 @@ class Cookie } } - public function setConfig(array $config) - { - $this->config = array_merge($this->config, $config); - } - public static function __make(Config $config) { return new static($config->pull('cookie')); diff --git a/library/think/Session.php b/library/think/Session.php index e0fe8179..90f0f650 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -15,6 +15,12 @@ use think\exception\ClassNotFoundException; class Session { + /** + * 配置参数 + * @var array + */ + protected $config = []; + /** * 前缀 * @var string @@ -51,6 +57,11 @@ class Session */ protected $lock = false; + public function __construct(array $config = []) + { + $this->config = $config; + } + /** * 设置或者获取session作用域(前缀) * @access public @@ -70,7 +81,18 @@ class Session public static function __make(Config $config) { - return (new static())->init($config->pull('session')); + 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)); } /** @@ -82,6 +104,8 @@ class Session */ public function init(array $config = []) { + $config = $config ?: $this->config; + $isDoStart = false; if (isset($config['use_trans_sid'])) { ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); diff --git a/library/think/Url.php b/library/think/Url.php index 89e69f83..7cbdfffa 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -13,6 +13,12 @@ namespace think; class Url { + /** + * 配置参数 + * @var array + */ + protected $config = []; + /** * ROOT地址 * @var string @@ -31,9 +37,10 @@ class Url */ protected $app; - public function __construct(App $app) + public function __construct(App $app, array $config = []) { - $this->app = $app; + $this->app = $app; + $this->config = $config; if (is_file($app->getRuntimePath() . 'route.php')) { // 读取路由映射文件 @@ -41,6 +48,22 @@ class Url } } + /** + * 初始化 + * @access public + * @param array $config + * @return void + */ + public function init(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('app')); + } + /** * URL生成 支持路由反射 * @access public @@ -105,7 +128,7 @@ class Url $url = $match[0]; if (!empty($match[1])) { - $host = $this->app['config']->get('app_host') ?: $this->app['request']->host(true); + $host = $this->config['app_host'] ?: $this->app['request']->host(true); if ($domain || $match[1] != $host) { $domain = $match[1]; } @@ -166,7 +189,7 @@ class Url } // 还原URL分隔符 - $depr = $this->app['config']->get('pathinfo_depr'); + $depr = $this->config['pathinfo_depr']; $url = str_replace('/', $depr, $url); // URL后缀 @@ -182,11 +205,11 @@ class Url // 参数组装 if (!empty($vars)) { // 添加参数 - if ($this->app['config']->get('url_common_param')) { + if ($this->config['url_common_param']) { $vars = http_build_query($vars); $url .= $suffix . '?' . $vars . $anchor; } else { - $paramType = $this->app['config']->get('url_param_type'); + $paramType = $this->config['url_param_type']; foreach ($vars as $var => $val) { if ('' !== trim($val)) { @@ -244,7 +267,7 @@ class Url $module = empty($path) ? $module : array_pop($path) . '/'; } - if ($this->app['config']->get('url_convert')) { + if ($this->config['url_convert']) { $action = strtolower($action); $controller = Loader::parseName($controller); } @@ -265,7 +288,7 @@ class Url $rootDomain = $this->app['request']->rootDomain(); if (true === $domain) { // 自动判断域名 - $domain = $this->app['config']->get('app_host') ?: $this->app['request']->host(true); + $domain = $this->config['app_host'] ?: $this->app['request']->host(true); $domains = $this->app['route']->getDomains(); @@ -302,7 +325,7 @@ class Url if (false !== strpos($domain, '://')) { $scheme = ''; } else { - $scheme = $this->app['request']->isSsl() || $this->app['config']->get('is_https') ? 'https://' : 'http://'; + $scheme = $this->app['request']->isSsl() || $this->config['is_https'] ? 'https://' : 'http://'; } @@ -313,7 +336,7 @@ class Url protected function parseSuffix($suffix) { if ($suffix) { - $suffix = true === $suffix ? $this->app['config']->get('url_html_suffix') : $suffix; + $suffix = true === $suffix ? $this->config['url_html_suffix'] : $suffix; if ($pos = strpos($suffix, '|')) { $suffix = substr($suffix, 0, $pos); @@ -332,7 +355,7 @@ class Url return [rtrim($url, '?/-'), $domain, $suffix]; } - $type = $this->app['config']->get('url_common_param'); + $type = $this->config['url_common_param']; foreach ($pattern as $key => $val) { if (isset($vars[$key])) { -- Gitee From 9971eef570da2ef6fe0a214c45f1087275da32e1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 10:13:44 +0800 Subject: [PATCH 1226/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=9A=84=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/Attribute.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index a32b85b7..2ff0a6ea 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -448,7 +448,7 @@ trait Attribute } elseif (isset($this->type[$name])) { // 类型转换 $value = $this->readTransform($value, $this->type[$name]); - } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ 'datetime', 'date', -- Gitee From 9f35b24d48bf517b167c9d1f8611b91ad068548f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 12:26:34 +0800 Subject: [PATCH 1227/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 5 ++ library/think/Db.php | 114 ++++++++++++++++++++++++++++++-- library/think/Model.php | 20 +++--- library/think/Session.php | 3 +- library/think/db/Connection.php | 74 ++------------------- library/think/db/Query.php | 23 ------- library/think/response/Jump.php | 5 +- library/think/response/View.php | 4 +- 8 files changed, 133 insertions(+), 115 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index a9c5a733..09519f48 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -11,6 +11,7 @@ namespace think; +use think\Db; use think\exception\ClassNotFoundException; use think\exception\HttpResponseException; use think\route\Dispatch; @@ -231,6 +232,9 @@ class App implements \ArrayAccess // 注册类库别名 Loader::addClassAlias($this->config->pull('alias')); + // 数据库配置初始化 + Db::init($this->config->pull('database')); + // 设置系统时区 date_default_timezone_set($this->config('app.default_timezone')); @@ -320,6 +324,7 @@ class App implements \ArrayAccess protected function moduleContainerInit($module) { // 对容器中的对象实例进行配置更新 + Db::init($this->config->pull('database')); $this->cookie->init($this->config->pull('cookie')); $this->session->setConfig($this->config->pull('session')); $this->view->init($this->config->pull('template')); diff --git a/library/think/Db.php b/library/think/Db.php index 911501c9..c67165d1 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -11,6 +11,8 @@ namespace think; +use think\db\Connection; + /** * Class Db * @package think @@ -57,6 +59,18 @@ namespace think; */ class Db { + /** + * 当前数据库连接对象 + * @var Connection + */ + protected static $connection; + + /** + * 数据库配置 + * @var array + */ + protected static $config = []; + /** * 查询次数 * @var integer @@ -69,12 +83,104 @@ class Db */ public static $executeTimes = 0; - public static function __callStatic($method, $args) + protected static $isInit = false; + + /** + * 配置 + * @access public + * @param mixed $config + * @return void + */ + public static function init($config = []) + { + self::$config = $config; + } + + public static function getConfig() + { + return self::$config; + } + + /** + * 切换数据库连接 + * @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 = '') { - $class = Container::get('config')->get('database.query') ?: '\\think\\db\\Query'; + // 解析配置参数 + $options = self::parseConfig($config ?: self::$config); + + // 创建数据库连接对象实例 + self::$connection = Connection::instance($options, $name); - $query = new $class(); + if (!$query) { + $query = self::$connection->getConfig('query') ?: '\\think\\db\\Query'; + } - return call_user_func_array([$query, $method], $args); + return new $query(self::$connection); + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config + * @return array + */ + private static function parseConfig($config) + { + if (is_string($config) && false === strpos($config, '/')) { + // 支持读取配置参数 + $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config; + } + + if (is_string($config)) { + return self::parseDsnConfig($config); + } else { + return $config; + } + } + + /** + * DSN解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr + * @return array + */ + private static function parseDsnConfig($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + public static function __callStatic($method, $args) + { + return call_user_func_array([static::connect(), $method], $args); } } diff --git a/library/think/Model.php b/library/think/Model.php index c1b2a625..3f322aef 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -11,6 +11,7 @@ namespace think; +use think\Db; use think\db\Query; /** @@ -141,13 +142,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录原始数据 $this->origin = $this->data; - $config = Container::get('config'); + $config = Db::getConfig(); if (empty($this->name)) { // 当前模型名 $name = str_replace('\\', '/', static::class); $this->name = basename($name); - if ($config->get('class_suffix')) { + if (Container::get('config')->get('class_suffix')) { $suffix = basename(dirname($name)); $this->name = substr($this->name, 0, -strlen($suffix)); } @@ -155,26 +156,26 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = $config->get('database.auto_timestamp'); + $this->autoWriteTimestamp = $config['auto_timestamp']; } if (is_null($this->dateFormat)) { // 设置时间戳格式 - $this->dateFormat = $config->get('database.datetime_format'); + $this->dateFormat = $config['datetime_format']; } if (is_null($this->resultSetType)) { - $this->resultSetType = $config->get('database.resultset_type'); + $this->resultSetType = $config['resultset_type']; } if (is_null($this->query)) { // 设置查询对象 - $this->query = $config->get('database.query'); + $this->query = $config['query']; } if (!empty($this->connection) && is_array($this->connection)) { // 设置模型的数据库连接 - $this->connection = array_merge($config->pull('database'), $this->connection); + $this->connection = array_merge($config, $this->connection); } if ($this->observerClass) { @@ -232,9 +233,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected function buildQuery() { // 设置当前模型 确保查询返回模型对象 - $class = $this->query; - $query = (new $class())->connect($this->connection) - ->model($this) + $query = Db::connect($this->connection, false, $this->query); + $query->model($this) ->json($this->json) ->setJsonFieldType($this->jsonType); diff --git a/library/think/Session.php b/library/think/Session.php index 90f0f650..2d05599c 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -278,8 +278,7 @@ class Session */ protected function initDriver() { - // 不在 init 方法中实例化lockDriver,是因为 init 方法不一定先于 set 或 get 方法调用 - $config = Container::get('config')->pull('session'); + $config = $this->config; if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) { // 读取session驱动 diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 23463c84..ae358821 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -144,10 +144,10 @@ abstract class Connection /** * 架构函数 读取数据库配置信息 - * @access protected + * @access public * @param array $config 数据库配置数组 */ - protected function __construct(array $config = []) + public function __construct(array $config = []) { if (!empty($config)) { $this->config = array_merge($this->config, $config); @@ -162,11 +162,6 @@ abstract class Connection $this->initialize(); } - public static function __make(Config $config) - { - return new static($config->pull('database')); - } - /** * 初始化 * @access protected @@ -190,21 +185,18 @@ abstract class Connection } if (true === $name || !isset(self::$instance[$name])) { - // 解析连接参数 支持数组和字符串 - $options = self::parseConfig($config); - - if (empty($options['type'])) { + if (empty($config['type'])) { throw new InvalidArgumentException('Undefined db type'); } // 记录初始化信息 - Container::get('app')->log('[ DB ] INIT ' . $options['type']); + Container::get('app')->log('[ DB ] INIT ' . $config['type']); if (true === $name) { $name = md5(serialize($config)); } - self::$instance[$name] = self::instanceFactory($options['type'], $options, '\\think\\db\\connector\\'); + self::$instance[$name] = self::instanceFactory($config['type'], $config, '\\think\\db\\connector\\'); } return self::$instance[$name]; @@ -2156,60 +2148,4 @@ abstract class Connection } } - /** - * 数据库连接参数解析 - * @access private - * @param mixed $config - * @return array - */ - private static function parseConfig($config) - { - if (empty($config)) { - $config = Container::get('config')->pull('database'); - } elseif (is_string($config) && false === strpos($config, '/')) { - // 支持读取配置参数 - $config = Container::get('config')->get('database.' . $config); - } - - if (is_string($config)) { - return self::parseDsnConfig($config); - } else { - return $config; - } - } - - /** - * DSN解析 - * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 - * @access private - * @param string $dsnStr - * @return array - */ - private static function parseDsnConfig($dsnStr) - { - $info = parse_url($dsnStr); - - if (!$info) { - return []; - } - - $dsn = [ - 'type' => $info['scheme'], - 'username' => isset($info['user']) ? $info['user'] : '', - 'password' => isset($info['pass']) ? $info['pass'] : '', - 'hostname' => isset($info['host']) ? $info['host'] : '', - 'hostport' => isset($info['port']) ? $info['port'] : '', - 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', - 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', - ]; - - if (isset($info['query'])) { - parse_str($info['query'], $dsn['params']); - } else { - $dsn['params'] = []; - } - - return $dsn; - } - } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1180b422..7819857a 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -296,29 +296,6 @@ class Query return $this->prefix . Loader::parseName($name); } - /** - * 切换数据库连接 - * @access public - * @param mixed $config 连接配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return $this|object - * @throws Exception - */ - public function connect($config = [], $name = false) - { - $this->connection = Connection::instance($config, $name); - - $query = $this->connection->getConfig('query'); - - if (__CLASS__ != trim($query, '\\')) { - return new $query($this->connection); - } - - $this->prefix = $this->connection->getConfig('prefix'); - - return $this; - } - /** * 执行查询 返回数据集 * @access public diff --git a/library/think/response/Jump.php b/library/think/response/Jump.php index 214afc7d..21ab2e9a 100644 --- a/library/think/response/Jump.php +++ b/library/think/response/Jump.php @@ -27,10 +27,7 @@ class Jump extends Response */ protected function output($data) { - $config = Container::get('config'); - $data = Container::get('view') - ->init($config->pull('template')) - ->fetch($this->options['jump_template'], $data); + $data = Container::get('view')->fetch($this->options['jump_template'], $data); return $data; } } diff --git a/library/think/response/View.php b/library/think/response/View.php index 0b4cb28e..5ddef47d 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -91,9 +91,7 @@ class View extends Response */ public function exists($name) { - return Container::get('view') - ->init(Container::get('config')->pull('template')) - ->exists($name); + return Container::get('view')->exists($name); } } -- Gitee From a0f81df355bc83b5d198628ec3771411e1fa253a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 12:30:36 +0800 Subject: [PATCH 1228/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index c67165d1..38c8a659 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -83,8 +83,6 @@ class Db */ public static $executeTimes = 0; - protected static $isInit = false; - /** * 配置 * @access public @@ -96,6 +94,11 @@ class Db self::$config = $config; } + /** + * 获取数据库配置 + * @access public + * @return array + */ public static function getConfig() { return self::$config; -- Gitee From 8a17e1a276f65950f113f7cbd957bde8e6e73894 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 13:34:38 +0800 Subject: [PATCH 1229/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B1=BB=E7=9A=84getScene=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index bb170045..7f491e4a 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -1447,12 +1447,12 @@ class Validate $scene = $this->currentScene; } - $this->only = $this->append = $this->remove = []; - if (empty($scene)) { return; } + $this->only = $this->append = $this->remove = []; + if (method_exists($this, 'scene' . $scene)) { call_user_func([$this, 'scene' . $scene]); } elseif (isset($this->scene[$scene])) { -- Gitee From 1737d36cca2c141754e22d1e6c1ebf356bf8ad77 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 14:44:19 +0800 Subject: [PATCH 1230/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=9A=84create?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=92=8Csave=E6=96=B9=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81replace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 3f322aef..67fb793b 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -33,6 +33,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ private $isUpdate = false; + /** + * 是否Replace + * @var bool + */ + private $replace = false; + /** * 是否强制更新所有数据 * @var bool @@ -355,6 +361,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this; } + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + /** * 保存当前数据对象 * @access public @@ -559,7 +577,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检查允许字段 $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); - $result = $this->db(false)->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence); + $result = $this->db(false)->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); // 获取自动增长主键 if ($result && $insertId = $this->db(false)->getLastInsID($sequence)) { @@ -780,9 +798,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @access public * @param array $data 数据数组 * @param array|true $field 允许字段 + * @param bool $replace 使用Replace * @return static */ - public static function create($data = [], $field = null) + public static function create($data = [], $field = null, $replace = false) { $model = new static(); @@ -790,7 +809,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $model->allowField($field); } - $model->isUpdate(false)->save($data, []); + $model->isUpdate(false)->replace($replace)->save($data, []); return $model; } -- Gitee From 566225b0c3f7ecf24e819141457e2793072e27c3 Mon Sep 17 00:00:00 2001 From: ThinkPHP Date: Tue, 15 May 2018 06:44:41 +0000 Subject: [PATCH 1231/1384] Apply fixes from StyleCI --- library/think/App.php | 1 - library/think/Debug.php | 2 +- library/think/Model.php | 1 - library/think/db/Connection.php | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 09519f48..77f59697 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -11,7 +11,6 @@ namespace think; -use think\Db; use think\exception\ClassNotFoundException; use think\exception\HttpResponseException; use think\route\Dispatch; diff --git a/library/think/Debug.php b/library/think/Debug.php index d74eef36..c441394d 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -239,7 +239,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); + echo($output); return; } return $output; diff --git a/library/think/Model.php b/library/think/Model.php index 67fb793b..d2baf999 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -11,7 +11,6 @@ namespace think; -use think\Db; use think\db\Query; /** diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index ae358821..8523d31a 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -14,7 +14,6 @@ namespace think\db; use InvalidArgumentException; use PDO; use PDOStatement; -use think\Config; use think\Container; use think\Db; use think\db\exception\BindParamException; -- Gitee From 38801d052eb19ddb654ed706f20e8e8acf5717f0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 15:59:21 +0800 Subject: [PATCH 1232/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDb=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index 38c8a659..690cb8ee 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -92,6 +92,9 @@ class Db public static function init($config = []) { self::$config = $config; + if (!empty($config['query'])) { + self::$config['query'] = '\\think\\db\\Query'; + } } /** @@ -117,14 +120,11 @@ class Db { // 解析配置参数 $options = self::parseConfig($config ?: self::$config); + $query = $query ?: self::$config['query']; // 创建数据库连接对象实例 self::$connection = Connection::instance($options, $name); - if (!$query) { - $query = self::$connection->getConfig('query') ?: '\\think\\db\\Query'; - } - return new $query(self::$connection); } -- Gitee From c5339cfe22dd2a019a8b45d0467898ddc0ab8949 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 18:43:21 +0800 Subject: [PATCH 1233/1384] =?UTF-8?q?App=E7=B1=BB=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=B8=BA=E5=AE=B9=E5=99=A8=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base.php | 28 ------- helper.php | 2 +- library/think/App.php | 114 ++++++++++++---------------- library/think/Config.php | 22 ++++-- library/think/Container.php | 57 +++++++++++++- library/think/Controller.php | 14 +--- library/think/Hook.php | 23 ++++-- library/think/Lang.php | 13 +++- library/think/Middleware.php | 12 +-- library/think/Request.php | 23 ++++-- library/think/Response.php | 21 +++-- library/think/Route.php | 19 +++-- library/think/Template.php | 13 ++-- library/think/response/Jsonp.php | 3 +- library/think/response/Jump.php | 3 +- library/think/response/Redirect.php | 13 ++-- library/think/response/View.php | 7 +- library/think/view/driver/Php.php | 13 ++-- library/think/view/driver/Think.php | 17 +++-- library/traits/controller/Jump.php | 24 ++++-- 20 files changed, 253 insertions(+), 188 deletions(-) diff --git a/base.php b/base.php index aad321d6..4a6c6d55 100644 --- a/base.php +++ b/base.php @@ -28,31 +28,6 @@ if (interface_exists('Psr\Log\LoggerInterface')) { {} } -// 注册核心类到容器 -Container::getInstance()->bind([ - 'app' => App::class, - 'build' => Build::class, - 'cache' => Cache::class, - 'config' => Config::class, - 'cookie' => Cookie::class, - 'debug' => Debug::class, - 'env' => Env::class, - 'hook' => Hook::class, - 'lang' => Lang::class, - 'log' => Log::class, - 'middleware' => Middleware::class, - 'request' => Request::class, - 'response' => Response::class, - 'route' => Route::class, - 'session' => Session::class, - 'url' => Url::class, - 'validate' => Validate::class, - 'view' => View::class, - 'rule_name' => route\RuleName::class, - // 接口依赖注入 - 'think\LoggerInterface' => Log::class, -]); - // 注册核心类的静态代理 Facade::bind([ facade\App::class => App::class, @@ -98,8 +73,5 @@ Loader::addClassAlias([ 'View' => facade\View::class, ]); -// 加载惯例配置文件 -facade\Config::set(include __DIR__ . '/convention.php'); - // 加载composer autofile文件 Loader::loadComposerAutoloadFiles(); diff --git a/helper.php b/helper.php index 8a965036..372357f1 100644 --- a/helper.php +++ b/helper.php @@ -101,7 +101,7 @@ if (!function_exists('bind')) { */ function bind($abstract, $concrete = null) { - return Container::getInstance()->bind($abstract, $concrete); + return Container::getInstance()->bindTo($abstract, $concrete); } } diff --git a/library/think/App.php b/library/think/App.php index 77f59697..f560c302 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -18,7 +18,7 @@ use think\route\Dispatch; /** * App 应用管理 */ -class App implements \ArrayAccess +class App extends Container { const VERSION = '5.1.13'; @@ -112,22 +112,15 @@ class App implements \ArrayAccess */ protected $dispatch; - /** - * 容器对象实例 - * @var Container - */ - protected $container; - /** * 绑定模块(控制器) * @var string */ - protected $bind; + protected $bindModule; public function __construct($appPath = '') { - $this->appPath = $appPath ? realpath($appPath) . DIRECTORY_SEPARATOR : $this->getAppPath(); - $this->container = Container::getInstance(); + $this->appPath = $appPath ? realpath($appPath) . DIRECTORY_SEPARATOR : $this->getAppPath(); $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; @@ -144,7 +137,7 @@ class App implements \ArrayAccess */ public function bind($bind) { - $this->bind = $bind; + $this->bindModule = $bind; return $this; } @@ -160,6 +153,39 @@ class App implements \ArrayAccess return $this; } + /** + * 注册核心容器实例 + * @access public + * @return void + */ + public function registerCoreContainer() + { + // 注册核心类到容器 + $this->bindTo([ + 'app' => App::class, + 'build' => Build::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'cookie' => Cookie::class, + 'debug' => Debug::class, + 'env' => Env::class, + 'hook' => Hook::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'url' => Url::class, + 'validate' => Validate::class, + 'view' => View::class, + 'rule_name' => route\RuleName::class, + // 接口依赖注入 + 'think\LoggerInterface' => Log::class, + ]); + } + /** * 初始化应用 * @access public @@ -170,6 +196,15 @@ class App implements \ArrayAccess $this->beginTime = microtime(true); $this->beginMem = memory_get_usage(); + static::setInstance($this); + + $this->registerCoreContainer(); + + $this->instance('app', $this); + + // 加载惯例配置文件 + $this->config->set(include $this->thinkPath . 'convention.php'); + // 设置路径环境变量 $this->env->set([ 'think_path' => $this->thinkPath, @@ -292,7 +327,7 @@ class App implements \ArrayAccess if (is_file($path . 'provider.php')) { $provider = include $path . 'provider.php'; if (is_array($provider)) { - $this->container->bind($provider); + $this->bindTo($provider); } } @@ -353,9 +388,9 @@ class App implements \ArrayAccess // 初始化应用 $this->initialize(); - if ($this->bind) { + if ($this->bindModule) { // 模块/控制器绑定 - $this->route->bind($this->bind); + $this->route->bind($this->bindModule); } elseif ($this->config('app.auto_bind_module')) { // 入口自动绑定 $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); @@ -703,7 +738,7 @@ class App implements \ArrayAccess } } - return $this->container->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); } /** @@ -893,53 +928,4 @@ class App implements \ArrayAccess return $this->beginMem; } - /** - * 获取容器实例 - * @access public - * @return Container - */ - public function container() - { - return $this->container; - } - - public function __set($name, $value) - { - $this->container->bind($name, $value); - } - - public function __get($name) - { - return $this->container->make($name); - } - - public function __isset($name) - { - return $this->container->bound($name); - } - - public function __unset($name) - { - $this->container->delete($name); - } - - public function offsetExists($key) - { - return $this->__isset($key); - } - - public function offsetGet($key) - { - return $this->__get($key); - } - - public function offsetSet($key, $value) - { - $this->__set($key, $value); - } - - public function offsetUnset($key) - { - $this->__unset($key); - } } diff --git a/library/think/Config.php b/library/think/Config.php index ab565109..48936031 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -27,6 +27,17 @@ class Config implements \ArrayAccess */ private $prefix = 'app'; + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + /** * 设置配置参数默认前缀 * @access public @@ -90,15 +101,14 @@ class Config implements \ArrayAccess protected function autoLoad($name) { // 如果尚未载入 则动态加载配置文件 - $module = Container::get('request')->module(); + $module = $this->app->request->module(); $module = $module ? $module . DIRECTORY_SEPARATOR : ''; - $app = Container::get('app'); - $path = $app->getAppPath() . $module; + $path = $this->app->getAppPath() . $module; if (is_dir($path . 'config')) { - $file = $path . 'config' . DIRECTORY_SEPARATOR . $name . $app->getConfigExt(); - } elseif (is_dir($app->getConfigPath() . $module)) { - $file = $app->getConfigPath() . $module . $name . $app->getConfigExt(); + $file = $path . 'config' . DIRECTORY_SEPARATOR . $name . $this->app->getConfigExt(); + } elseif (is_dir($this->app->getConfigPath() . $module)) { + $file = $this->app->getConfigPath() . $module . $name . $this->app->getConfigExt(); } if (isset($file) && is_file($file)) { diff --git a/library/think/Container.php b/library/think/Container.php index c292ceb3..cc144aba 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -19,7 +19,7 @@ use ReflectionFunction; use ReflectionMethod; use think\exception\ClassNotFoundException; -class Container +class Container implements \ArrayAccess { /** * 容器对象实例 @@ -59,6 +59,17 @@ class Container return static::$instance; } + /** + * 设置当前容器的实例 + * @access public + * @param object $instance + * @return void + */ + public static function setInstance($instance) + { + static::$instance = $instance; + } + /** * 获取容器中的对象实例 * @access public @@ -81,7 +92,7 @@ class Container */ public static function set($abstract, $concrete = null) { - return static::getInstance()->bind($abstract, $concrete); + return static::getInstance()->bindTo($abstract, $concrete); } /** @@ -112,7 +123,7 @@ class Container * @param mixed $concrete 要绑定的类、闭包或者实例 * @return $this */ - public function bind($abstract, $concrete = null) + public function bindTo($abstract, $concrete = null) { if (is_array($abstract)) { $this->bind = array_merge($this->bind, $abstract); @@ -413,4 +424,44 @@ class Container return $result; } + + public function __set($name, $value) + { + $this->bindTo($name, $value); + } + + public function __get($name) + { + return $this->make($name); + } + + public function __isset($name) + { + return $this->bound($name); + } + + public function __unset($name) + { + $this->delete($name); + } + + public function offsetExists($key) + { + return $this->__isset($key); + } + + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value) + { + $this->__set($key, $value); + } + + public function offsetUnset($key) + { + $this->__unset($key); + } } diff --git a/library/think/Controller.php b/library/think/Controller.php index 2a24e540..0220aee4 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -30,12 +30,6 @@ class Controller */ protected $request; - /** - * 应用实例 - * @var \think\App - */ - protected $app; - /** * 验证失败是否抛出异常 * @var bool @@ -58,11 +52,11 @@ class Controller * 构造方法 * @access public */ - public function __construct() + public function __construct(App $app = null) { - $this->request = Container::get('request'); - $this->app = Container::get('app'); - $this->view = Container::get('view'); + $this->app = $app ?: Container::get('app'); + $this->request = $this->app['request']; + $this->view = $this->app['view']; // 控制器初始化 $this->initialize(); diff --git a/library/think/Hook.php b/library/think/Hook.php index b82fb5a0..fefa0085 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -31,6 +31,17 @@ class Hook */ private static $portal = 'run'; + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + /** * 指定入口方法名称 * @access public @@ -163,7 +174,7 @@ class Hook $method = [$class, self::$portal]; } - return Container::getInstance()->invoke($method, [$params]); + return $this->app->invoke($method, [$params]); } /** @@ -176,9 +187,7 @@ class Hook */ protected function execTag($class, $tag = '', $params = null) { - $app = Container::get('app'); - - $app->isDebug() && $app['debug']->remark('behavior_start', 'time'); + $this->app->isDebug() && $this->app['debug']->remark('behavior_start', 'time'); $method = Loader::parseName($tag, 1, false); @@ -198,10 +207,10 @@ class Hook $class = $class . '->' . $method; } - $result = Container::getInstance()->invoke($call, [$params]); + $result = $this->app->invoke($call, [$params]); - if ($app->isDebug()) { - $debug = $app['debug']; + if ($this->app->isDebug()) { + $debug = $this->app['debug']; $debug->remark('behavior_end', 'time'); $app->log('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . $debug->getRangeTime('behavior_start', 'behavior_end') . 's ]'); } diff --git a/library/think/Lang.php b/library/think/Lang.php index 2de0e474..5d8d9d99 100644 --- a/library/think/Lang.php +++ b/library/think/Lang.php @@ -51,6 +51,17 @@ class Lang 'zh-hans-cn' => 'zh-cn', ]; + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + // 设定当前的语言 public function range($range = '') { @@ -108,7 +119,7 @@ class Lang foreach ($file as $_file) { if (is_file($_file)) { // 记录加载信息 - Container::get('app')->log('[ LANG ] ' . $_file); + $this->app->log('[ LANG ] ' . $_file); $_lang = include $_file; if (is_array($_lang)) { $lang = array_change_key_case($_lang) + $lang; diff --git a/library/think/Middleware.php b/library/think/Middleware.php index a0fddeb3..d7f9a90c 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -17,19 +17,21 @@ use think\exception\HttpResponseException; class Middleware { - protected $queue = []; + protected $queue = []; + protected $app; protected $config = [ 'default_namespace' => 'app\http\middleware', ]; - public function __construct(array $config = []) + public function __construct(App $app, array $config = []) { + $this->app = $app; $this->config = array_merge($this->config, $config); } - public static function __make(Config $config) + public static function __make(App $app, Config $config) { - return new static($config->pull('middleware')); + return new static($app, $config->pull('middleware')); } public function setConfig(array $config) @@ -122,7 +124,7 @@ class Middleware list($middleware, $param) = explode(':', $middleware, 2); } - return [[Container::get($middleware), 'handle'], isset($param) ? $param : null]; + return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null]; } protected function resolve() diff --git a/library/think/Request.php b/library/think/Request.php index 12a857ae..6a25c99f 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -21,6 +21,12 @@ class Request */ protected $instance; + /** + * 应用对象实例 + * @var App + */ + protected $app; + /** * 配置参数 * @var array @@ -262,8 +268,9 @@ class Request * @access public * @param array $options 参数 */ - public function __construct(array $options = []) + public function __construct(App $app, array $options = []) { + $this->app = $app; $this->init($options); // 保存 php://input @@ -287,9 +294,9 @@ class Request return isset($this->config[$name]) ? $this->config[$name] : null; } - public static function __make(Config $config) + public static function __make(App $app, Config $config) { - return new static($config->pull('app')); + return new static($app, $config->pull('app')); } public function __call($method, $args) @@ -1045,7 +1052,7 @@ class Request public function session($name = '', $default = null, $filter = '') { if (empty($this->session)) { - $this->session = Container::get('session')->get(); + $this->session = $this->app['session']->get(); } if (is_array($name)) { @@ -1065,7 +1072,7 @@ class Request */ public function cookie($name = '', $default = null, $filter = '') { - $cookie = Container::get('cookie'); + $cookie = $this->app['cookie']; if (empty($this->cookie)) { $this->cookie = $cookie->get(); @@ -1202,7 +1209,7 @@ class Request public function env($name = '', $default = null, $filter = '') { if (empty($this->env)) { - $this->env = Container::get('env')->get(); + $this->env = $this->app['env']->get(); } if (is_array($name)) { @@ -1878,7 +1885,7 @@ class Request header($name . ': ' . $token); } - Container::get('session')->set($name, $token); + $this->app['session']->set($name, $token); return $token; } @@ -1946,7 +1953,7 @@ class Request if (isset($fun)) { $key = $fun($key); } - $cache = Container::get('cache'); + $cache = $this->app['cache']; if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { // 读取缓存 diff --git a/library/think/Response.php b/library/think/Response.php index de8feaac..09cb6a92 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -21,6 +21,12 @@ class Response */ protected $data; + /** + * 应用对象实例 + * @var App + */ + protected $app; + /** * 当前contentType * @var string @@ -82,6 +88,7 @@ class Response $this->contentType($this->contentType, $this->charset); $this->code = $code; + $this->app = Container::get('app'); $this->header = array_merge($this->header, $header); } @@ -115,24 +122,24 @@ class Response public function send() { // 监听response_send - Container::get('hook')->listen('response_send', $this); + $this->app['hook']->listen('response_send', $this); // 处理输出数据 $data = $this->getContent(); // Trace调试注入 - if ('cli' != PHP_SAPI && Container::get('env')->get('app_trace', Container::get('app')->config('app.app_trace'))) { - Container::get('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 && $this->allowCache) { - $cache = Container::get('request')->getCache(); + $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'; - Container::get('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]); } } @@ -153,11 +160,11 @@ class Response } // 监听response_end - Container::get('hook')->listen('response_end', $this); + $this->app['hook']->listen('response_end', $this); // 清空当次请求有效的数据 if (!($this instanceof RedirectResponse)) { - Container::get('session')->flush(); + $this->app['session']->flush(); } } diff --git a/library/think/Route.php b/library/think/Route.php index 4f13bb34..0bf76739 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -47,6 +47,12 @@ class Route 'patch' => 'patch', ]; + /** + * 应用对象 + * @var App + */ + protected $app; + /** * 请求对象 * @var Request @@ -119,9 +125,10 @@ class Route */ protected $autoSearchController = true; - public function __construct(Request $request, array $config = []) + public function __construct(App $app, array $config = []) { - $this->request = $request; + $this->app = $app; + $this->request = $app['request']; $this->config = $config; $this->host = $this->request->host(true); @@ -137,10 +144,10 @@ class Route return isset($this->config[$name]) ? $this->config[$name] : null; } - public static function __make(Request $request, Config $config) + public static function __make(App $app, Config $config) { $config = $config->pull('app'); - $route = new static($request, $config); + $route = new static($app, $config); $route->lazy($config['url_lazy_route']) ->autoSearchController($config['controller_auto_search']) @@ -368,7 +375,7 @@ class Route */ public function getName($name = null) { - return Container::get('rule_name')->get($name); + return $this->app['rule_name']->get($name); } /** @@ -379,7 +386,7 @@ class Route */ public function setName($name) { - Container::get('rule_name')->import($name); + $this->app['rule_name']->import($name); return $this; } diff --git a/library/think/Template.php b/library/think/Template.php index 747dabef..67084702 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -22,6 +22,7 @@ class Template { use Factory; + protected $app; /** * 模板变量 * @var array @@ -85,9 +86,9 @@ class Template * @access public * @param array $config */ - public function __construct(array $config = []) + public function __construct(App $app, array $config = []) { - $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/'; + $this->config['cache_path'] = $app->getRuntimePath() . 'temp/'; $this->config = array_merge($this->config, $config); $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; @@ -195,7 +196,7 @@ class Template $this->config($config); } - $cache = Container::get('cache'); + $cache = $this->app['cache']; if (!empty($this->config['cache_id']) && $this->config['display_cache']) { // 读取渲染缓存 @@ -343,7 +344,7 @@ class Template { if ($cacheId && $this->config['display_cache']) { // 缓存页面输出 - return Container::get('cache')->has($cacheId); + return $this->app['cache']->has($cacheId); } return false; @@ -1224,10 +1225,10 @@ class Template } if ($this->config['view_base']) { - $module = isset($module) ? $module : Container::get('request')->module(); + $module = isset($module) ? $module : $this->app['request']->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; } $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); diff --git a/library/think/response/Jsonp.php b/library/think/response/Jsonp.php index 9e53da2d..f69e88e1 100644 --- a/library/think/response/Jsonp.php +++ b/library/think/response/Jsonp.php @@ -11,7 +11,6 @@ namespace think\response; -use think\Container; use think\Response; class Jsonp extends Response @@ -36,7 +35,7 @@ class Jsonp extends Response { try { // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Container::get('request')->param($this->options['var_jsonp_handler'], ""); + $var_jsonp_handler = $this->app['request']->param($this->options['var_jsonp_handler'], ""); $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; $data = json_encode($data, $this->options['json_encode_param']); diff --git a/library/think/response/Jump.php b/library/think/response/Jump.php index 21ab2e9a..258448ca 100644 --- a/library/think/response/Jump.php +++ b/library/think/response/Jump.php @@ -11,7 +11,6 @@ namespace think\response; -use think\Container; use think\Response; class Jump extends Response @@ -27,7 +26,7 @@ class Jump extends Response */ protected function output($data) { - $data = Container::get('view')->fetch($this->options['jump_template'], $data); + $data = $this->app['view']->fetch($this->options['jump_template'], $data); return $data; } } diff --git a/library/think/response/Redirect.php b/library/think/response/Redirect.php index d6e5101a..481b26f3 100644 --- a/library/think/response/Redirect.php +++ b/library/think/response/Redirect.php @@ -11,7 +11,6 @@ namespace think\response; -use think\Container; use think\Response; class Redirect extends Response @@ -22,9 +21,9 @@ class Redirect extends Response // URL参数 protected $params = []; - public function __construct($data = '', $code = 302, array $header = [], array $options = []) + public function __construct(App $app, $data = '', $code = 302, array $header = [], array $options = []) { - parent::__construct($data, $code, $header, $options); + parent::__construct($app, $data, $code, $header, $options); $this->cacheControl('no-cache,must-revalidate'); } @@ -51,7 +50,7 @@ class Redirect extends Response */ public function with($name, $value = null) { - $session = Container::get('session'); + $session = $this->app['session']; if (is_array($name)) { foreach ($name as $key => $val) { @@ -74,7 +73,7 @@ class Redirect extends Response if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { return $this->data; } else { - return Container::get('url')->build($this->data, $this->params); + return $this->app['url']->build($this->data, $this->params); } } @@ -92,7 +91,7 @@ class Redirect extends Response */ public function remember() { - Container::get('session')->set('redirect_url', Container::get('request')->url()); + $this->app['session']->set('redirect_url', $this->app['request']->url()); return $this; } @@ -104,7 +103,7 @@ class Redirect extends Response */ public function restore() { - $session = Container::get('session'); + $session = $this->app['session']; if ($session->has('redirect_url')) { $this->data = $session->get('redirect_url'); diff --git a/library/think/response/View.php b/library/think/response/View.php index 5ddef47d..c836ccb5 100644 --- a/library/think/response/View.php +++ b/library/think/response/View.php @@ -11,7 +11,6 @@ namespace think\response; -use think\Container; use think\Response; class View extends Response @@ -31,9 +30,7 @@ class View extends Response protected function output($data) { // 渲染模板输出 - $config = Container::get('config'); - return Container::get('view') - ->init($config->pull('template')) + return $this->app['view'] ->filter($this->filter) ->fetch($data, $this->vars); } @@ -91,7 +88,7 @@ class View extends Response */ public function exists($name) { - return Container::get('view')->exists($name); + return $this->app['view']->exists($name); } } diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index a1a03ebd..93dbd903 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -11,7 +11,6 @@ namespace think\view\driver; -use think\Container; use think\exception\TemplateNotFoundException; use think\Loader; @@ -32,10 +31,12 @@ class Php ]; protected $template; + protected $app; protected $content; - public function __construct($config = []) + public function __construct(App $app, $config = []) { + $this->app = $app; $this->config = array_merge($this->config, (array) $config); } @@ -77,7 +78,7 @@ class Php $this->template = $template; // 记录视图信息 - Container::get('app') + $this->app ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); extract($data, EXTR_OVERWRITE); @@ -108,10 +109,10 @@ class Php private function parseTemplate($template) { if (empty($this->config['view_path'])) { - $this->config['view_path'] = Container::get('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + $this->config['view_path'] = $this->app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } - $request = Container::get('request'); + $request = $this->app['request']; // 获取视图根目录 if (strpos($template, '@')) { @@ -124,7 +125,7 @@ class Php $module = isset($module) ? $module : $request->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 834a8c03..5901a918 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -11,7 +11,6 @@ namespace think\view\driver; -use think\Container; use think\exception\TemplateNotFoundException; use think\Loader; use think\Template; @@ -20,6 +19,8 @@ class Think { // 模板引擎实例 private $template; + private $app; + // 模板引擎参数 protected $config = [ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 @@ -36,14 +37,16 @@ class Think 'tpl_cache' => true, ]; - public function __construct($config = []) + public function __construct(App $app, $config = []) { + $this->app = $app; $this->config = array_merge($this->config, (array) $config); + if (empty($this->config['view_path'])) { - $this->config['view_path'] = Container::get('app')->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + $this->config['view_path'] = $app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; } - $this->template = new Template($this->config); + $this->template = new Template($app, $this->config); } /** @@ -83,7 +86,7 @@ class Think } // 记录视图信息 - Container::get('app') + $this->app ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); $this->template->fetch($template, $data, $config); @@ -111,7 +114,7 @@ class Think private function parseTemplate($template) { // 分析模板文件规则 - $request = Container::get('request'); + $request = $this->app['request']; // 获取视图根目录 if (strpos($template, '@')) { @@ -124,7 +127,7 @@ class Think $module = isset($module) ? $module : $request->module(); $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); } else { - $path = isset($module) ? Container::get('app')->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; } $depr = $this->config['view_depr']; diff --git a/library/traits/controller/Jump.php b/library/traits/controller/Jump.php index 8ea2ee4a..41f7e930 100644 --- a/library/traits/controller/Jump.php +++ b/library/traits/controller/Jump.php @@ -20,6 +20,12 @@ use think\response\Redirect; trait Jump { + /** + * 应用实例 + * @var \think\App + */ + protected $app; + /** * 操作成功跳转的快捷方法 * @access protected @@ -52,7 +58,7 @@ trait Jump $type = 'jump'; } - $response = Response::create($result, $type)->header($header)->options(['jump_template' => Container::get('config')->get('dispatch_success_tmpl')]); + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_success_tmpl')]); throw new HttpResponseException($response); } @@ -69,10 +75,11 @@ trait Jump */ protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) { + $type = $this->getResponseType(); if (is_null($url)) { - $url = Container::get('request')->isAjax() ? '' : 'javascript:history.back(-1);'; + $url = $this->app['request']->isAjax() ? '' : 'javascript:history.back(-1);'; } elseif ('' !== $url) { - $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app['url']->build($url); } $result = [ @@ -83,12 +90,11 @@ trait Jump 'wait' => $wait, ]; - $type = $this->getResponseType(); if ('html' == strtolower($type)) { $type = 'jump'; } - $response = Response::create($result, $type)->header($header)->options(['jump_template' => Container::get('config')->get('dispatch_error_tmpl')]); + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_error_tmpl')]); throw new HttpResponseException($response); } @@ -148,8 +154,12 @@ trait Jump */ protected function getResponseType() { - $isAjax = Container::get('request')->isAjax(); - $config = Container::get('config'); + if (!$this->app) { + $this->app = Container::get('app'); + } + + $isAjax = $this->app['request']->isAjax(); + $config = $this->app['config']; return $isAjax ? $config->get('default_ajax_return') -- Gitee From 39e57096351766e2276cb4730d80dc14b460decb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 15 May 2018 18:47:08 +0800 Subject: [PATCH 1234/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 2 +- library/think/view/driver/Php.php | 1 + library/think/view/driver/Think.php | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/Container.php b/library/think/Container.php index cc144aba..26a4daee 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -37,7 +37,7 @@ class Container implements \ArrayAccess * 容器绑定标识 * @var array */ - protected $bind = []; + protected $bind = ['app' => 'think\App']; /** * 容器标识别名 diff --git a/library/think/view/driver/Php.php b/library/think/view/driver/Php.php index 93dbd903..b6f6a3d0 100644 --- a/library/think/view/driver/Php.php +++ b/library/think/view/driver/Php.php @@ -11,6 +11,7 @@ namespace think\view\driver; +use think\App; use think\exception\TemplateNotFoundException; use think\Loader; diff --git a/library/think/view/driver/Think.php b/library/think/view/driver/Think.php index 5901a918..9ea06826 100644 --- a/library/think/view/driver/Think.php +++ b/library/think/view/driver/Think.php @@ -11,6 +11,7 @@ namespace think\view\driver; +use think\App; use think\exception\TemplateNotFoundException; use think\Loader; use think\Template; -- Gitee From f01ee44a5f7a73febb30fe3db6ec72fde6b1b769 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 10:42:13 +0800 Subject: [PATCH 1235/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AE=B9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/think/Container.php b/library/think/Container.php index 26a4daee..43ae7f7b 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -37,7 +37,12 @@ class Container implements \ArrayAccess * 容器绑定标识 * @var array */ - protected $bind = ['app' => 'think\App']; + protected $bind = [ + 'app' => 'think\App', + 'config' => 'think\Config', + 'lang' => 'think\Lang', + 'log' => 'think\Log', + ]; /** * 容器标识别名 -- Gitee From e129092c3843bae5e1326359e50e24fdabadce48 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 12:00:23 +0800 Subject: [PATCH 1236/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BFacade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Facade.php | 8 +++----- library/think/Validate.php | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/library/think/Facade.php b/library/think/Facade.php index 28fe667b..f467e0f8 100644 --- a/library/think/Facade.php +++ b/library/think/Facade.php @@ -85,15 +85,13 @@ class Facade /** * 带参数实例化当前Facade类 * @access public - * @return object + * @return mixed */ public static function instance(...$args) { if (__CLASS__ != static::class) { - return self::__callStatic('instance', $args); + return self::createFacade('', $args); } - - return self::createFacade('', $args); } /** @@ -102,7 +100,7 @@ class Facade * @param string $class 类名或者标识 * @param array|true $args 变量 * @param bool $newInstance 是否每次创建新的实例 - * @return object + * @return mixed */ public static function make($class, $args = [], $newInstance = false) { diff --git a/library/think/Validate.php b/library/think/Validate.php index 7f491e4a..bddbc69d 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -1078,7 +1078,7 @@ class Validate */ public function requireCallback($value, $rule, $data) { - $result = call_user_func_array($rule, [$value, $data]); + $result = call_user_func_array([$this, $rule], [$value, $data]); if ($result) { return !empty($value) || '0' == $value; -- Gitee From 5c35b6b7c1298e2a3dfc66479d57a7fa9e9df9d7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 12:34:58 +0800 Subject: [PATCH 1237/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index f560c302..9f7d5dc7 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -348,11 +348,12 @@ class App extends Container } } + $this->setModulePath($path); + if ($module) { $this->moduleContainerInit($module); } - $this->setModulePath($path); } protected function moduleContainerInit($module) -- Gitee From 0a69784d95dc8a81b063ebfac64619704b38f968 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 14:40:14 +0800 Subject: [PATCH 1238/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3RedirectResponse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/response/Redirect.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/response/Redirect.php b/library/think/response/Redirect.php index 481b26f3..62fa83a7 100644 --- a/library/think/response/Redirect.php +++ b/library/think/response/Redirect.php @@ -21,9 +21,9 @@ class Redirect extends Response // URL参数 protected $params = []; - public function __construct(App $app, $data = '', $code = 302, array $header = [], array $options = []) + public function __construct($data = '', $code = 302, array $header = [], array $options = []) { - parent::__construct($app, $data, $code, $header, $options); + parent::__construct($data, $code, $header, $options); $this->cacheControl('no-cache,must-revalidate'); } -- Gitee From 00938155d5e02c939888a4e6fab8dfaea9954b1f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 15:05:41 +0800 Subject: [PATCH 1239/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 44 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 9f7d5dc7..03c6775d 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -32,7 +32,7 @@ class App extends Container * 应用调试模式 * @var bool */ - protected $debug = true; + protected $appDebug = true; /** * 应用开始时间 @@ -237,10 +237,10 @@ class App extends Container $this->suffix = $this->config('app.class_suffix'); // 应用调试模式 - $this->debug = $this->env->get('app_debug', $this->config('app.app_debug')); - $this->env->set('app_debug', $this->debug); + $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->appDebug); - if (!$this->debug) { + if (!$this->appDebug) { ini_set('display_errors', 'Off'); } elseif (PHP_SAPI != 'cli') { //重新申请一块比较大的buffer @@ -351,29 +351,33 @@ class App extends Container $this->setModulePath($path); if ($module) { - $this->moduleContainerInit($module); + // 对容器中的对象实例进行配置更新 + $this->containerConfigUpdate($module); } } - protected function moduleContainerInit($module) + protected function containerConfigUpdate($module) { - // 对容器中的对象实例进行配置更新 - Db::init($this->config->pull('database')); - $this->cookie->init($this->config->pull('cookie')); - $this->session->setConfig($this->config->pull('session')); - $this->view->init($this->config->pull('template')); - $this->log->init($this->config->pull('log')); - $this->__get('debug')->setConfig($this->config->pull('trace')); + $config = $this->config->get(); + + Db::init($config['database']); + + $this->request->init($config['app']); + $this->cookie->init($config['cookie']); + $this->view->init($config['template']); + $this->log->init($config['log']); + $this->session->setConfig($config['session']); + $this->debug->setConfig($config['trace']); // 加载当前模块语言包 $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); // 模块请求缓存检查 $this->request->cache( - $this->config->get('request_cache'), - $this->config->get('request_cache_expire'), - $this->config->get('request_cache_except') + $config['app']['request_cache'], + $config['app']['request_cache_expire'], + $config['app']['request_cache_except'] ); } @@ -414,7 +418,7 @@ class App extends Container $this->request->dispatch($dispatch); // 记录路由和请求信息 - if ($this->debug) { + if ($this->appDebug) { $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); @@ -509,7 +513,7 @@ class App extends Container */ public function log($msg, $type = 'info') { - $this->debug && $this->log->record($msg, $type); + $this->appDebug && $this->log->record($msg, $type); } /** @@ -547,7 +551,7 @@ class App extends Container if ($this->config('route.route_annotation')) { // 自动生成路由定义 - if ($this->debug) { + if ($this->appDebug) { $this->build->buildRoute($this->config('route.controller_suffix')); } @@ -778,7 +782,7 @@ class App extends Container */ public function isDebug() { - return $this->debug; + return $this->appDebug; } /** -- Gitee From 73622228cf0459e4ea6fb20226cb876bccf41b79 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 15:29:38 +0800 Subject: [PATCH 1240/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index 43ae7f7b..94cf449e 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -38,10 +38,12 @@ class Container implements \ArrayAccess * @var array */ protected $bind = [ - 'app' => 'think\App', - 'config' => 'think\Config', - 'lang' => 'think\Lang', - 'log' => 'think\Log', + 'app' => 'think\App', + 'config' => 'think\Config', + 'lang' => 'think\Lang', + 'log' => 'think\Log', + 'request' => 'think\Request', + 'response' => 'think\Response', ]; /** -- Gitee From 238d3783107930d7b89964396ced832b421944ae Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 16:14:25 +0800 Subject: [PATCH 1241/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base.php | 3 --- library/think/App.php | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base.php b/base.php index 4a6c6d55..084dd74d 100644 --- a/base.php +++ b/base.php @@ -72,6 +72,3 @@ Loader::addClassAlias([ 'Validate' => facade\Validate::class, 'View' => facade\View::class, ]); - -// 加载composer autofile文件 -Loader::loadComposerAutoloadFiles(); diff --git a/library/think/App.php b/library/think/App.php index 03c6775d..cf1f8656 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -228,6 +228,9 @@ class App extends Container // 注册应用命名空间 Loader::addNamespace($this->namespace, $this->appPath); + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + $this->configExt = $this->env->get('config_ext', '.php'); // 初始化应用 -- Gitee From 11e0440a9a4a63e2416a896491e4eb85b2512022 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 16 May 2018 23:56:44 +0800 Subject: [PATCH 1242/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Template.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/Template.php b/library/think/Template.php index 67084702..f2540507 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -88,6 +88,7 @@ class Template */ public function __construct(App $app, array $config = []) { + $this->app = $app; $this->config['cache_path'] = $app->getRuntimePath() . 'temp/'; $this->config = array_merge($this->config, $config); -- Gitee From e2eaee57ec3b5735a205aad2539a0ad8f23051bf Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 May 2018 12:32:35 +0800 Subject: [PATCH 1243/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E9=BB=98=E8=AE=A4=E8=A7=84=E5=88=99=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/route/Rule.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/convention.php b/convention.php index 496192ff..c643a0bc 100644 --- a/convention.php +++ b/convention.php @@ -122,6 +122,8 @@ return [ 'route_complete_match' => false, // 使用注解路由 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '\w+', // +---------------------------------------------------------------------- // | 异常及错误设置 diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 8e52660d..eec0a3e9 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -1052,7 +1052,7 @@ abstract class Rule $nameRule = substr($nameRule, 1, -1); } } else { - $nameRule = '\w+'; + $nameRule = $this->router->config('default_route_pattern'); } return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; -- Gitee From f9367817a448a1d89383c33e08245868239ea1d2 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 May 2018 18:56:39 +0800 Subject: [PATCH 1244/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 4 + library/think/App.php | 20 ++ library/think/Route.php | 4 +- library/think/route/Dispatch.php | 271 +++++++++++++++++++- library/think/route/Rule.php | 243 +++--------------- library/think/route/RuleGroup.php | 3 +- library/think/route/RuleItem.php | 16 ++ library/think/route/dispatch/Callback.php | 3 +- library/think/route/dispatch/Controller.php | 6 +- library/think/route/dispatch/Module.php | 48 ++-- library/think/route/dispatch/Redirect.php | 2 +- library/think/route/dispatch/Response.php | 2 +- library/think/route/dispatch/Url.php | 24 +- 13 files changed, 382 insertions(+), 264 deletions(-) diff --git a/convention.php b/convention.php index c643a0bc..fbce075e 100644 --- a/convention.php +++ b/convention.php @@ -124,6 +124,10 @@ return [ 'route_annotation' => false, // 默认的路由变量规则 'default_route_pattern' => '\w+', + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 + 'route_check_cache_key' => '', // +---------------------------------------------------------------------- // | 异常及错误设置 diff --git a/library/think/App.php b/library/think/App.php index cf1f8656..6b8dc21a 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -411,10 +411,30 @@ class App extends Container $this->hook->listen('app_dispatch'); // 获取应用调度信息 + if ($this->config->get('route_check_cache')) { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->url(true) . ':' . $this->request->method()); + } + + if ($this->cache->has($routeKey)) { + $this->dispatch = $this->cache->get($routeKey); + } + } + $dispatch = $this->dispatch; + if (empty($dispatch)) { // 路由检测 $dispatch = $this->routeCheck(); + + try { + if (isset($routeKey)) { + $this->cache->set($routeKey, $dispatch); + } + } catch (\Exception $e) {} } // 记录当前调度信息 diff --git a/library/think/Route.php b/library/think/Route.php index 0bf76739..4b403a15 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -818,7 +818,9 @@ class Route } // 默认路由解析 - return new UrlDispatch($this->request, $this, $url, ['auto_search' => $this->autoSearchController]); + $ruleItem = new RuleItem($this, $this->group, '', '', $url); + + return new UrlDispatch($this->request, $ruleItem, $url, ['auto_search' => $this->autoSearchController]); } /** diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 22b46de1..a47a093a 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -12,25 +12,57 @@ namespace think\route; use think\Container; +use think\exception\ValidateException; use think\Request; -use think\Route; +use think\Response; +use think\route\dispatch\ResponseDispatch; +use think\route\RuleItem; abstract class Dispatch { - // 应用实例 + /** + * 应用对象 + * @var App + */ protected $app; + + /** + * 请求对象 + * @var Request + */ protected $request; + + /** + * 路由规则 + * @var RuleItem + */ protected $router; - // 调度信息 + + /** + * 调度信息 + * @var mixed + */ protected $dispatch; - // 调度参数 + + /** + * 调度参数 + * @var array + */ protected $param; - // 状态码 + + /** + * 状态码 + * @var string + */ protected $code; - // 是否进行大小写转换 + + /** + * 是否进行大小写转换 + * @var bool + */ protected $convert; - public function __construct(Request $request, Route $router, $dispatch, $param = [], $code = null) + public function __construct(Request $request, RuleItem $router, $dispatch, $param = [], $code = null) { $this->request = $request; $this->router = $router; @@ -38,12 +70,235 @@ abstract class Dispatch $this->dispatch = $dispatch; $this->param = $param; $this->code = $code; + + if (isset($param['convert'])) { + $this->convert = $param['convert']; + } + + // 设置请求的路由信息 + $this->request->routeInfo([ + 'rule' => $this->router->getRule(), + 'route' => $this->router->getRoute(), + 'option' => $this->router->getOption(), + 'var' => $this->router->getVars(), + ]); + + // 初始化 $this->init(); } protected function init() {} + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run() + { + $result = $this->routeAfter(); + + if ($result instanceof Response) { + return $result; + } + + return $this->exec(); + } + + /** + * 检查路由后置操作 + * @access protected + * @return mixed + */ + protected function routeAfter() + { + // 记录匹配的路由信息 + $option = $this->router->getOption(); + $matches = $this->router->getVars(); + + // 添加中间件 + if (!empty($option['middleware'])) { + $this->app['middleware']->import($option['middleware']); + } + + // 绑定模型数据 + if (!empty($option['model'])) { + $this->createBindModel($option['model'], $matches); + } + + // 指定Header数据 + if (!empty($option['header'])) { + $header = $option['header']; + $this->app['hook']->add('response_send', function ($response) use ($header) { + $response->header($header); + }); + } + + // 指定Response响应数据 + if (!empty($option['response'])) { + $this->app['hook']->add('response_send', $option['response']); + } + + // 开启请求缓存 + if (isset($option['cache']) && $request->isGet()) { + $this->parseRequestCache($option['cache']); + } + + if (!empty($option['append'])) { + $this->request->route($option['append']); + } + + // 检测路由after行为 + if (!empty($option['after'])) { + $dispatch = $this->checkAfter($option['after']); + + if (false !== $dispatch) { + return $dispatch; + } + } + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate'], $request); + } + } + + /** + * 检查路由后置行为 + * @access protected + * @param mixed $after 后置行为 + * @return mixed + */ + protected function checkAfter($after) + { + $this->app['log']->notice('路由后置行为建议使用中间件替代!'); + + $hook = $this->app['hook']; + + $result = null; + + foreach ((array) $after as $behavior) { + $result = $hook->exec($behavior); + + if (!is_null($result)) { + break; + } + } + + // 路由规则重定向 + if ($result instanceof Response) { + return new ResponseDispatch($result, $this->router); + } + + return false; + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @param \think\Request $request + * @return void + * @throws ValidateException + */ + protected function autoValidate($option, $request) + { + list($validate, $scene, $message, $batch) = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = $this->app->validate(); + $v->rule($validate); + } else { + // 调用验证器 + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + if (!empty($message)) { + $v->message($message); + } + + // 批量验证 + if ($batch) { + $v->batch(true); + } + + if (!$v->check($request->param())) { + throw new ValidateException($v->getError()); + } + } + + /** + * 处理路由请求缓存 + * @access protected + * @param Request $request 请求对象 + * @param string|array $cache 路由缓存 + * @return void + */ + protected function parseRequestCache($cache) + { + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $this->request->url()); + $expire = $cache; + $tag = null; + } + + $this->request->cache($key, $expire, $tag); + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array|\Clousre $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel($bindModel, $matches) + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = $this->app->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + list($model, $exception) = $val; + } else { + $model = $val; + $exception = true; + } + + $where = []; + $match = true; + + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[] = [$field, '=', $matches[$field]]; + } + } + + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : $this->app->model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + $this->app->instance(get_class($result), $result); + } + } + } + public function convert($convert) { $this->convert = $convert; @@ -61,6 +316,6 @@ abstract class Dispatch return $this->param; } - abstract public function run(); + abstract public function exec(); } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index eec0a3e9..455c1a9a 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -12,7 +12,6 @@ namespace think\route; use think\Container; -use think\exception\ValidateException; use think\Request; use think\Response; use think\route\dispatch\Callback as CallbackDispatch; @@ -122,6 +121,27 @@ abstract class Rule return isset($this->pattern[$name]) ? $this->pattern[$name] : null; } + /** + * 获取路由参数 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getConfig($name = '') + { + return $this->router->config($name); + } + + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter() + { + return $this->router; + } + /** * 获取路由参数定义 * @access public @@ -485,7 +505,7 @@ abstract class Rule $this->option['header'] = $header; if ($request->method(true) == 'OPTIONS') { - return new ResponseDispatch($request, $this->router, Response::create()->code(204)->header($header)); + return new ResponseDispatch($request, $this, Response::create()->code(204)->header($header)); } } } @@ -528,73 +548,6 @@ abstract class Rule return $this->option; } - /** - * 路由绑定模型实例 - * @access protected - * @param array|\Clousre $bindModel 绑定模型 - * @param array $matches 路由变量 - * @return void - */ - protected function createBindModel($bindModel, $matches) - { - foreach ($bindModel as $key => $val) { - if ($val instanceof \Closure) { - $result = Container::getInstance()->invokeFunction($val, $matches); - } else { - $fields = explode('&', $key); - - if (is_array($val)) { - list($model, $exception) = $val; - } else { - $model = $val; - $exception = true; - } - - $where = []; - $match = true; - - foreach ($fields as $field) { - if (!isset($matches[$field])) { - $match = false; - break; - } else { - $where[] = [$field, '=', $matches[$field]]; - } - } - - if ($match) { - $query = strpos($model, '\\') ? $model::where($where) : Container::get('app')->model($model)->where($where); - $result = $query->failException($exception)->find(); - } - } - - if (!empty($result)) { - // 注入容器 - Container::getInstance()->instance(get_class($result), $result); - } - } - } - - /** - * 处理路由请求缓存 - * @access protected - * @param Request $request 请求对象 - * @param string|array $cache 路由缓存 - * @return void - */ - protected function parseRequestCache($request, $cache) - { - if (is_array($cache)) { - list($key, $expire, $tag) = array_pad($cache, 3, null); - } else { - $key = str_replace('|', '/', $request->url()); - $expire = $cache; - $tag = null; - } - - $request->cache($key, $expire, $tag); - } - /** * 解析匹配到的规则路由 * @access public @@ -624,107 +577,19 @@ abstract class Rule } } - $this->afterMatchRule($request, $option, $matches); - // 解析额外参数 $count = substr_count($rule, '/'); $url = array_slice(explode('|', $url), $count + 1); $this->parseUrlParams($request, implode('|', $url), $matches); - // 记录匹配的路由信息 - $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); - - // 检测路由after行为 - if (!empty($option['after'])) { - $dispatch = $this->checkAfter($option['after']); - - if (false !== $dispatch) { - return $dispatch; - } - } - - // 数据自动验证 - if (isset($option['validate'])) { - $this->autoValidate($option['validate'], $request); - } + $this->route = $route; + $this->vars = $matches; + $this->option = $option; // 发起路由调度 return $this->dispatch($request, $route, $option); } - protected function afterMatchRule($request, $option = [], $matches = []) - { - // 添加中间件 - if (!empty($option['middleware'])) { - Container::get('middleware')->import($option['middleware']); - } - - // 绑定模型数据 - if (!empty($option['model'])) { - $this->createBindModel($option['model'], $matches); - } - - // 指定Header数据 - if (!empty($option['header'])) { - $header = $option['header']; - Container::get('hook')->add('response_send', function ($response) use ($header) { - $response->header($header); - }); - } - - // 指定Response响应数据 - if (!empty($option['response'])) { - Container::get('hook')->add('response_send', $option['response']); - } - - // 开启请求缓存 - if (isset($option['cache']) && $request->isGet()) { - $this->parseRequestCache($request, $option['cache']); - } - - if (!empty($option['append'])) { - $request->route($option['append']); - } - } - - /** - * 验证数据 - * @access protected - * @param array $option - * @param \think\Request $request - * @return void - * @throws ValidateException - */ - protected function autoValidate($option, $request) - { - list($validate, $scene, $message, $batch) = $option; - - if (is_array($validate)) { - // 指定验证规则 - $v = Container::get('app')->validate(); - $v->rule($validate); - } else { - // 调用验证器 - $v = Container::get('app')->validate($validate); - if (!empty($scene)) { - $v->scene($scene); - } - } - - if (!empty($message)) { - $v->message($message); - } - - // 批量验证 - if ($batch) { - $v->batch(true); - } - - if (!$v->check($request->param())) { - throw new ValidateException($v->getError()); - } - } - /** * 检查路由前置行为 * @access protected @@ -744,38 +609,6 @@ abstract class Rule } } - /** - * 检查路由后置行为 - * @access protected - * @param mixed $after 后置行为 - * @return mixed - */ - protected function checkAfter($after) - { - Container::get('log')->notice('路由后置行为建议使用中间件替代!'); - - $hook = Container::get('hook'); - - $result = null; - - foreach ((array) $after as $behavior) { - $result = $hook->exec($behavior); - - if (!is_null($result)) { - break; - } - } - - // 路由规则重定向 - if ($result instanceof Response) { - return new ResponseDispatch($result); - } elseif ($result instanceof Dispatch) { - return $result; - } - - return false; - } - /** * 发起路由调度 * @access protected @@ -788,14 +621,14 @@ abstract class Rule { if ($route instanceof \Closure) { // 执行闭包 - $result = new CallbackDispatch($request, $this->router, $route); + $result = new CallbackDispatch($request, $this, $route); } elseif ($route instanceof Response) { - $result = new ResponseDispatch($request, $this->router, $route); + $result = new ResponseDispatch($request, $this, $route); } elseif (isset($option['view']) && false !== $option['view']) { - $result = new ViewDispatch($request, $this->router, $route, is_array($option['view']) ? $option['view'] : []); + $result = new ViewDispatch($request, $this, $route, is_array($option['view']) ? $option['view'] : []); } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) { // 路由到重定向地址 - $result = new RedirectDispatch($request, $this->router, $route, [], isset($option['status']) ? $option['status'] : 301); + $result = new RedirectDispatch($request, $this, $route, [], isset($option['status']) ? $option['status'] : 301); } elseif (false !== strpos($route, '\\')) { // 路由到方法 $result = $this->dispatchMethod($request, $route); @@ -824,7 +657,7 @@ abstract class Rule $route = str_replace('/', '@', implode('/', $path)); $method = strpos($route, '@') ? explode('@', $route) : $route; - return new CallbackDispatch($request, $this->router, $method, $var); + return new CallbackDispatch($request, $this, $method, $var); } /** @@ -838,11 +671,11 @@ abstract class Rule { list($route, $var) = $this->parseUrlPath($route); - $result = new ControllerDispatch($request, $this->router, implode('/', $route), $var); + $result = new ControllerDispatch($request, $this, implode('/', $route), $var); $request->action(array_pop($route)); - $request->controller($route ? array_pop($route) : $this->router->config('default_controller')); - $request->module($route ? array_pop($route) : $this->router->config('default_module')); + $request->controller($route ? array_pop($route) : $this->getConfig('default_controller')); + $request->module($route ? array_pop($route) : $this->getConfig('default_module')); return $result; } @@ -860,10 +693,10 @@ abstract class Rule $action = array_pop($path); $controller = !empty($path) ? array_pop($path) : null; - $module = $this->router->config('app_multi_module') && !empty($path) ? array_pop($path) : null; + $module = $this->getConfig('app_multi_module') && !empty($path) ? array_pop($path) : null; $method = $request->method(); - if ($this->router->config('use_action_prefix') && $this->router->getMethodPrefix($method)) { + if ($this->getConfig('use_action_prefix') && $this->router->getMethodPrefix($method)) { $prefix = $this->router->getMethodPrefix($method); // 操作方法前缀支持 $action = 0 !== strpos($action, $prefix) ? $prefix . $action : $action; @@ -873,7 +706,7 @@ abstract class Rule $request->route($var); // 路由到模块/控制器/操作 - return new ModuleDispatch($request, $this->router, [$module, $controller, $action], [], false); + return new ModuleDispatch($request, $this, [$module, $controller, $action], ['convert' => false]); } /** @@ -933,7 +766,7 @@ abstract class Rule protected function parseUrlParams($request, $url, &$var = []) { if ($url) { - if ($this->router->config('url_param_type')) { + if ($this->getConfig('url_param_type')) { $var += explode('|', $url); } else { preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { @@ -1052,7 +885,7 @@ abstract class Rule $nameRule = substr($nameRule, 1, -1); } } else { - $nameRule = $this->router->config('default_route_pattern'); + $nameRule = $this->getConfig('default_route_pattern'); } return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index d063c078..ef40fa6f 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -176,7 +176,8 @@ class RuleGroup extends Rule if ($this->auto) { // 自动解析URL地址 - $result = new UrlDispatch($request, $this->router, $this->auto . '/' . $url, ['auto_search' => false]); + $ruleItem = new RuleItem($this->router, $this, '', '', $this->auto . '/' . $url); + $result = new UrlDispatch($request, $ruleItem, $this->auto . '/' . $url, ['auto_search' => false]); } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { // 未匹配所有路由的路由规则处理 $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 51058f4c..954a2b02 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -35,6 +35,12 @@ class RuleItem extends Rule */ protected $method; + /** + * 路由变量 + * @var array + */ + protected $vars = []; + /** * 架构函数 * @access public @@ -125,6 +131,16 @@ class RuleItem extends Rule return strtolower($this->method); } + /** + * 获取当前路由的变量 + * @access public + * @return array + */ + public function getVars() + { + return $this->vars; + } + /** * 检查后缀 * @access public diff --git a/library/think/route/dispatch/Callback.php b/library/think/route/dispatch/Callback.php index d385cb24..aab783f6 100644 --- a/library/think/route/dispatch/Callback.php +++ b/library/think/route/dispatch/Callback.php @@ -11,7 +11,6 @@ namespace think\route\dispatch; -use think\Container; use think\route\Dispatch; class Callback extends Dispatch @@ -21,7 +20,7 @@ class Callback extends Dispatch // 执行回调方法 $vars = array_merge($this->request->param(), $this->param); - return Container::getInstance()->invoke($this->dispatch, $vars); + return $this->app->invoke($this->dispatch, $vars); } } diff --git a/library/think/route/dispatch/Controller.php b/library/think/route/dispatch/Controller.php index 90f91e42..3d8a1f0e 100644 --- a/library/think/route/dispatch/Controller.php +++ b/library/think/route/dispatch/Controller.php @@ -15,15 +15,15 @@ use think\route\Dispatch; class Controller extends Dispatch { - public function run() + public function exec() { // 执行控制器的操作方法 $vars = array_merge($this->request->param(), $this->param); return $this->app->action( $this->dispatch, $vars, - $this->router->config('url_controller_layer'), - $this->router->config('controller_suffix') + $this->router->getConfig('url_controller_layer'), + $this->router->getConfig('controller_suffix') ); } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index f2608c77..8068fb09 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -12,12 +12,10 @@ namespace think\route\dispatch; use ReflectionMethod; -use think\Container; use think\exception\ClassNotFoundException; use think\exception\HttpException; use think\Loader; use think\Request; -use think\Route; use think\route\Dispatch; class Module extends Dispatch @@ -25,17 +23,6 @@ class Module extends Dispatch protected $controller; protected $actionName; - public function __construct(Request $request, Route $router, $dispatch, $param = [], $convert = null) - { - $this->app = Container::get('app'); - $this->request = $request; - $this->router = $router; - $this->dispatch = $dispatch; - $this->param = $param; - $this->convert = $convert; - $this->init(); - } - protected function init() { $result = $this->dispatch; @@ -44,10 +31,10 @@ class Module extends Dispatch $result = explode('/', $result); } - if ($this->router->config('app_multi_module')) { + if ($this->router->getConfig('app_multi_module')) { // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $this->router->config('default_module'))); - $bind = $this->router->getBind(); + $module = strip_tags(strtolower($result[0] ?: $this->router->getConfig('default_module'))); + $bind = $this->router->getRouter()->getBind(); $available = false; if ($bind && preg_match('/^[a-z]/is', $bind)) { @@ -57,10 +44,10 @@ class Module extends Dispatch $module = $bindModule; } $available = true; - } elseif (!in_array($module, $this->router->config('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { + } elseif (!in_array($module, $this->router->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { $available = true; - } elseif ($this->router->config('empty_module')) { - $module = $this->router->config('empty_module'); + } elseif ($this->router->getConfig('empty_module')) { + $module = $this->router->getConfig('empty_module'); $available = true; } @@ -75,20 +62,20 @@ class Module extends Dispatch } // 是否自动转换控制器和操作名 - $convert = is_bool($this->convert) ? $this->convert : $this->router->config('url_convert'); + $convert = is_bool($this->convert) ? $this->convert : $this->router->getConfig('url_convert'); // 获取控制器名 - $controller = strip_tags($result[1] ?: $this->router->config('default_controller')); + $controller = strip_tags($result[1] ?: $this->router->getConfig('default_controller')); $this->controller = $convert ? strtolower($controller) : $controller; // 获取操作名 - $this->actionName = strip_tags($result[2] ?: $this->router->config('default_action')); + $this->actionName = strip_tags($result[2] ?: $this->router->getConfig('default_action')); // 设置当前请求的控制器、操作 $this->request->controller(Loader::parseName($this->controller, 1))->action($this->actionName); } - public function run() + public function exec() { // 监听module_init $this->app['hook']->listen('module_init'); @@ -96,15 +83,15 @@ class Module extends Dispatch // 实例化控制器 try { $instance = $this->app->controller($this->controller, - $this->router->config('url_controller_layer'), - $this->router->config('controller_suffix'), - $this->router->config('empty_controller')); + $this->router->getConfig('url_controller_layer'), + $this->router->getConfig('controller_suffix'), + $this->router->getConfig('empty_controller')); } catch (ClassNotFoundException $e) { throw new HttpException(404, 'controller not exists:' . $e->getClass()); } // 获取当前操作名 - $action = $this->actionName . $this->router->config('action_suffix'); + $action = $this->actionName . $this->router->getConfig('action_suffix'); if (is_callable([$instance, $action])) { // 执行操作方法 @@ -113,12 +100,12 @@ class Module extends Dispatch // 严格获取当前操作方法名 $reflect = new ReflectionMethod($instance, $action); $methodName = $reflect->getName(); - $suffix = $this->router->config('action_suffix'); + $suffix = $this->router->getConfig('action_suffix'); $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; $this->request->action($actionName); // 自动获取请求变量 - $vars = $this->router->config('url_param_type') + $vars = $this->router->getConfig('url_param_type') ? $this->request->route() : $this->request->param(); } elseif (is_callable([$instance, '_empty'])) { @@ -132,6 +119,7 @@ class Module extends Dispatch } $this->app['hook']->listen('action_begin', $call); - return Container::getInstance()->invokeReflectMethod($instance, $reflect, $vars); + + return $this->app->invokeReflectMethod($instance, $reflect, $vars); } } diff --git a/library/think/route/dispatch/Redirect.php b/library/think/route/dispatch/Redirect.php index 1a9208b0..fae2c9a6 100644 --- a/library/think/route/dispatch/Redirect.php +++ b/library/think/route/dispatch/Redirect.php @@ -16,7 +16,7 @@ use think\route\Dispatch; class Redirect extends Dispatch { - public function run() + public function exec() { return Response::create($this->dispatch, 'redirect')->code($this->code); } diff --git a/library/think/route/dispatch/Response.php b/library/think/route/dispatch/Response.php index 4b5fa16e..66f4e5ab 100644 --- a/library/think/route/dispatch/Response.php +++ b/library/think/route/dispatch/Response.php @@ -15,7 +15,7 @@ use think\route\Dispatch; class Response extends Dispatch { - public function run() + public function exec() { return $this->dispatch; } diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 63b9c313..f0241ab2 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -20,27 +20,27 @@ class Url extends Dispatch protected function init() { // 解析默认的URL规则 - $depr = $this->router->config('pathinfo_depr'); - $url = str_replace($depr, '|', $this->dispatch); - $result = $this->parseUrl($url, $depr); + $depr = $this->router->getConfig('pathinfo_depr'); + $result = $this->parseUrl($this->dispatch, $depr); $this->dispatch = new Module($this->request, $this->router, $result); } - public function run() + public function exec() { - return $this->dispatch->run(); + return $this->dispatch->exec(); } /** * 解析URL地址 * @access protected - * @param string $url URL + * @param string $url URL + * @param string $depr 分隔符 * @return array */ protected function parseUrl($url, $depr) { - $bind = $this->router->getBind(); + $bind = $this->router->getRouter()->getBind(); if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { $bind = str_replace('/', $depr, $bind); @@ -54,7 +54,7 @@ class Url extends Dispatch } // 解析模块 - $module = $this->router->config('app_multi_module') ? array_shift($path) : null; + $module = $this->router->getConfig('app_multi_module') ? array_shift($path) : null; if ($this->param['auto_search']) { $controller = $this->autoFindController($module, $path); } else { @@ -67,7 +67,7 @@ class Url extends Dispatch // 解析额外参数 if ($path) { - if ($this->router->config('url_param_type')) { + if ($this->router->getConfig('url_param_type')) { $var += $path; } else { preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { @@ -115,7 +115,7 @@ class Url extends Dispatch $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); } - if ($this->router->getName($name) || $this->router->getName($name2)) { + if ($this->router->getRouter()->getName($name) || $this->router->getRouter()->getName($name2)) { return true; } @@ -131,8 +131,8 @@ class Url extends Dispatch */ protected function autoFindController($module, &$path) { - $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->router->config('url_controller_layer'); - $suffix = $this->app->getSuffix() || $this->router->config('controller_suffix') ? ucfirst($this->router->config('url_controller_layer')) : ''; + $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->router->getConfig('url_controller_layer'); + $suffix = $this->app->getSuffix() || $this->router->getConfig('controller_suffix') ? ucfirst($this->router->getConfig('url_controller_layer')) : ''; $item = []; $find = false; -- Gitee From f559ae99905a4de14e7f5a32a18c9971ab69f758 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 May 2018 19:16:03 +0800 Subject: [PATCH 1245/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=B8=AAB?= =?UTF-8?q?UG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 6b8dc21a..810270e0 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -228,9 +228,6 @@ class App extends Container // 注册应用命名空间 Loader::addNamespace($this->namespace, $this->appPath); - // 加载composer autofile文件 - Loader::loadComposerAutoloadFiles(); - $this->configExt = $this->env->get('config_ext', '.php'); // 初始化应用 @@ -266,6 +263,9 @@ class App extends Container Loader::addNamespace($this->config('app.root_namespace')); } + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + // 注册类库别名 Loader::addClassAlias($this->config->pull('alias')); -- Gitee From 02a17b8333fbaa04e0a1786c32891001fe85a34c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 May 2018 22:37:41 +0800 Subject: [PATCH 1246/1384] =?UTF-8?q?=E5=B7=A5=E5=8E=82=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E5=8C=96=E6=96=B9=E6=B3=95=E7=BA=B3=E5=85=A5Loader=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cache.php | 4 +--- library/think/Config.php | 2 +- library/think/Debug.php | 2 +- library/think/Factory.php | 36 ------------------------------------ library/think/Loader.php | 21 +++++++++++++++++++++ library/think/Log.php | 2 +- library/think/View.php | 2 +- 7 files changed, 26 insertions(+), 43 deletions(-) delete mode 100644 library/think/Factory.php diff --git a/library/think/Cache.php b/library/think/Cache.php index cc4d1bd0..a4e285dd 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -23,8 +23,6 @@ use think\cache\Driver; */ class Cache { - use Factory; - /** * 缓存实例 * @var array @@ -69,7 +67,7 @@ class Cache $name = md5(serialize($options)); } - $this->instance[$name] = self::instanceFactory($type, $options, '\\think\\cache\\driver\\'); + $this->instance[$name] = Loader::factory($type, $options, '\\think\\cache\\driver\\'); } return $this->instance[$name]; diff --git a/library/think/Config.php b/library/think/Config.php index 48936031..b7d0d078 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -63,7 +63,7 @@ class Config implements \ArrayAccess $type = pathinfo($config, PATHINFO_EXTENSION); } - $object = self::instanceFactory($type, $config, '\\think\\config\\driver\\'); + $object = Loader::factory($type, $config, '\\think\\config\\driver\\'); return $this->set($object->parse(), $name); } diff --git a/library/think/Debug.php b/library/think/Debug.php index c441394d..6311d747 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -252,7 +252,7 @@ class Debug unset($config['type']); - $trace = self::instanceFactory($type, $config, '\\think\\debug\\'); + $trace = Loader::factory($type, $config, '\\think\\debug\\'); if ($response instanceof Redirect) { //TODO 记录 diff --git a/library/think/Factory.php b/library/think/Factory.php deleted file mode 100644 index 20a2bf1d..00000000 --- a/library/think/Factory.php +++ /dev/null @@ -1,36 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; - -trait Factory -{ - /** - * 创建工厂对象实例 - * @access protected - * @param string $name 工厂类名 - * @param mixed $option 实例化参数 - * @param string $namespace 默认命名空间 - * @return mixed - */ - protected static function instanceFactory($name, $option = null, $namespace = '') - { - $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); - - if (class_exists($class)) { - return Container::getInstance()->invokeClass($class, is_null($option) ? [] : [$option]); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } -} diff --git a/library/think/Loader.php b/library/think/Loader.php index b048ca74..66c1edc1 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -11,6 +11,8 @@ namespace think; +use think\exception\ClassNotFoundException; + class Loader { /** @@ -378,6 +380,25 @@ class Loader return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); } + + /** + * 创建工厂对象实例 + * @access public + * @param string $name 工厂类名 + * @param mixed $option 实例化参数 + * @param string $namespace 默认命名空间 + * @return mixed + */ + public static function factory($name, $option = null, $namespace = '') + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + if (class_exists($class)) { + return Container::getInstance()->invokeClass($class, is_null($option) ? [] : [$option]); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } } /** diff --git a/library/think/Log.php b/library/think/Log.php index 3edb83c2..7fc4e05e 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -89,7 +89,7 @@ class Log implements LoggerInterface $this->allowWrite = false; } - $this->driver = self::instanceFactory($type, $config, '\\think\\log\\driver\\'); + $this->driver = Loader::factory($type, $config, '\\think\\log\\driver\\'); return $this; } diff --git a/library/think/View.php b/library/think/View.php index a2a02141..8141a459 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -113,7 +113,7 @@ class View unset($options['type']); } - $this->engine = self::instanceFactory($type, $options, '\\think\\view\\driver\\'); + $this->engine = Loader::factory($type, $options, '\\think\\view\\driver\\'); return $this; } -- Gitee From d8c4fc95bc9cac3a0a24d2b44778227a2003e358 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 May 2018 22:42:55 +0800 Subject: [PATCH 1247/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 2 -- library/think/Debug.php | 2 -- library/think/Log.php | 2 -- library/think/Template.php | 4 +--- library/think/View.php | 2 -- library/think/db/Connection.php | 4 +--- 6 files changed, 2 insertions(+), 14 deletions(-) diff --git a/library/think/Config.php b/library/think/Config.php index b7d0d078..85403e94 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -13,8 +13,6 @@ namespace think; class Config implements \ArrayAccess { - use Factory; - /** * 配置参数 * @var array diff --git a/library/think/Debug.php b/library/think/Debug.php index 6311d747..1c5b75e7 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -16,8 +16,6 @@ use think\response\Redirect; class Debug { - use Factory; - /** * 配置参数 * @var array diff --git a/library/think/Log.php b/library/think/Log.php index 7fc4e05e..936d701f 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -13,8 +13,6 @@ namespace think; class Log implements LoggerInterface { - use Factory; - const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; diff --git a/library/think/Template.php b/library/think/Template.php index f2540507..4aab04f1 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -20,8 +20,6 @@ use think\exception\TemplateNotFoundException; */ class Template { - use Factory; - protected $app; /** * 模板变量 @@ -103,7 +101,7 @@ class Template // 初始化模板编译存储器 $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $this->storage = self::instanceFactory($type, null, '\\think\\template\\driver\\'); + $this->storage = Loader::factory($type, null, '\\think\\template\\driver\\'); } public static function __make(Config $config) diff --git a/library/think/View.php b/library/think/View.php index 8141a459..a1d3ad90 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -13,8 +13,6 @@ namespace think; class View { - use Factory; - /** * 模板引擎实例 * @var object diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 8523d31a..998ca7f7 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -20,11 +20,9 @@ use think\db\exception\BindParamException; use think\Debug; use think\Exception; use think\exception\PDOException; -use think\Factory; abstract class Connection { - use Factory; protected static $instance = []; /** @var PDOStatement PDO操作实例 */ protected $PDOStatement; @@ -195,7 +193,7 @@ abstract class Connection $name = md5(serialize($config)); } - self::$instance[$name] = self::instanceFactory($config['type'], $config, '\\think\\db\\connector\\'); + self::$instance[$name] = Loader::factory($config['type'], $config, '\\think\\db\\connector\\'); } return self::$instance[$name]; -- Gitee From bb40c299e8ee06cd564e52deaea3c6ec86f27aac Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 17 May 2018 23:14:47 +0800 Subject: [PATCH 1248/1384] =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 810270e0..a5f64610 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -364,8 +364,12 @@ class App extends Container { $config = $this->config->get(); - Db::init($config['database']); + // 注册异常处理类 + if ($config['app']['exception_handle']) { + Error::setExceptionHandler($config['app']['exception_handle']); + } + Db::init($config['database']); $this->request->init($config['app']); $this->cookie->init($config['cookie']); $this->view->init($config['template']); -- Gitee From 51d8d6894ec7b9b3b2f06562f5c6efe5661e2dde Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 07:22:28 +0800 Subject: [PATCH 1249/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 998ca7f7..9f9cb182 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -20,6 +20,7 @@ use think\db\exception\BindParamException; use think\Debug; use think\Exception; use think\exception\PDOException; +use think\Loader; abstract class Connection { -- Gitee From 1b7bb0d83957301fc7e190bb314ad7d02c23a22b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 10:31:46 +0800 Subject: [PATCH 1250/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=BC=93=E5=AD=98=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/console/command/Clear.php | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index a5f64610..975cd5d4 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -436,7 +436,7 @@ class App extends Container try { if (isset($routeKey)) { - $this->cache->set($routeKey, $dispatch); + $this->cache->tag('route_cache')->set($routeKey, $dispatch); } } catch (\Exception $e) {} } diff --git a/library/think/console/command/Clear.php b/library/think/console/command/Clear.php index 7ee31ea7..14425759 100644 --- a/library/think/console/command/Clear.php +++ b/library/think/console/command/Clear.php @@ -15,6 +15,7 @@ use think\console\Input; use think\console\input\Option; use think\console\Output; use think\facade\App; +use think\facade\Cache; class Clear extends Command { @@ -25,6 +26,7 @@ class Clear extends Command ->setName('clear') ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('route', 'u', Option::VALUE_NONE, 'clear route cache') ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') ->setDescription('Clear runtime file'); @@ -32,16 +34,21 @@ class Clear extends Command protected function execute(Input $input, Output $output) { - if ($input->getOption('cache')) { - $path = App::getRuntimePath() . 'cache'; - } elseif ($input->getOption('log')) { - $path = App::getRuntimePath() . 'log'; + if ($input->getOption('route')) { + Cache::clear('route_cache'); } else { - $path = $input->getOption('path') ?: App::getRuntimePath(); + 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); } - $rmdir = $input->getOption('dir') ? true : false; - $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); $output->writeln("Clear Successed"); } -- Gitee From 5659f269dbb31f9d643746ecde1172b65c527f63 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 10:43:31 +0800 Subject: [PATCH 1251/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=BC=93=E5=AD=98=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 975cd5d4..39130204 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -416,12 +416,7 @@ class App extends Container // 获取应用调度信息 if ($this->config->get('route_check_cache')) { - if ($this->config->get('route_check_cache_key')) { - $closure = $this->config->get('route_check_cache_key'); - $routeKey = $closure($this->request); - } else { - $routeKey = md5($this->request->url(true) . ':' . $this->request->method()); - } + $routeKey = $this->getRouteCacheKey(); if ($this->cache->has($routeKey)) { $this->dispatch = $this->cache->get($routeKey); @@ -500,6 +495,18 @@ class App extends Container return $response; } + protected function getRouteCacheKey() + { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); + } + + return $routeKey; + } + protected function loadLangPack() { // 读取默认语言 -- Gitee From b74dbcafa7dee5bbb48eb9eb1cbfe429b0cc535b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 11:13:48 +0800 Subject: [PATCH 1252/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=A8=A1=E5=BC=8F=E6=97=A0=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 39130204..246a5b69 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -415,7 +415,7 @@ class App extends Container $this->hook->listen('app_dispatch'); // 获取应用调度信息 - if ($this->config->get('route_check_cache')) { + if (!$this->appDebug && $this->config->get('route_check_cache')) { $routeKey = $this->getRouteCacheKey(); if ($this->cache->has($routeKey)) { -- Gitee From df4a7ca0615172f6a6d148cb3b875f5bd2bdee32 Mon Sep 17 00:00:00 2001 From: ThinkPHP Date: Thu, 17 May 2018 23:22:51 +0000 Subject: [PATCH 1253/1384] Apply fixes from StyleCI --- library/think/route/Dispatch.php | 1 - library/think/route/dispatch/Module.php | 1 - 2 files changed, 2 deletions(-) diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index a47a093a..3e7d5f74 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -16,7 +16,6 @@ use think\exception\ValidateException; use think\Request; use think\Response; use think\route\dispatch\ResponseDispatch; -use think\route\RuleItem; abstract class Dispatch { diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 8068fb09..ad7a7904 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -15,7 +15,6 @@ use ReflectionMethod; use think\exception\ClassNotFoundException; use think\exception\HttpException; use think\Loader; -use think\Request; use think\route\Dispatch; class Module extends Dispatch -- Gitee From 1cf742169b428bbeaa13b1f0d7dccf6995bb27b7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 12:00:07 +0800 Subject: [PATCH 1254/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 246a5b69..e62e7d39 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App extends Container { - const VERSION = '5.1.13'; + const VERSION = '5.1.14'; /** * 当前模块路径 -- Gitee From ef9c6d69b2c285521013e2376b60ed8865c702f1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 12:20:09 +0800 Subject: [PATCH 1255/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Hook.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Hook.php b/library/think/Hook.php index fefa0085..58791c81 100644 --- a/library/think/Hook.php +++ b/library/think/Hook.php @@ -212,7 +212,7 @@ class Hook if ($this->app->isDebug()) { $debug = $this->app['debug']; $debug->remark('behavior_end', 'time'); - $app->log('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . $debug->getRangeTime('behavior_start', 'behavior_end') . 's ]'); + $this->app->log('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . $debug->getRangeTime('behavior_start', 'behavior_end') . 's ]'); } return $result; -- Gitee From afc1fd91e6ad3ff896a1fa1b60e75fab8e7f1fd0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 12:42:04 +0800 Subject: [PATCH 1256/1384] =?UTF-8?q?loader=E7=B1=BBfactory=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cache.php | 2 +- library/think/Config.php | 2 +- library/think/Debug.php | 4 ++-- library/think/Loader.php | 5 ++--- library/think/Log.php | 2 +- library/think/Template.php | 2 +- library/think/View.php | 2 +- library/think/db/Connection.php | 2 +- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/library/think/Cache.php b/library/think/Cache.php index a4e285dd..d6bd46be 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -67,7 +67,7 @@ class Cache $name = md5(serialize($options)); } - $this->instance[$name] = Loader::factory($type, $options, '\\think\\cache\\driver\\'); + $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options); } return $this->instance[$name]; diff --git a/library/think/Config.php b/library/think/Config.php index 85403e94..3fa9b136 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -61,7 +61,7 @@ class Config implements \ArrayAccess $type = pathinfo($config, PATHINFO_EXTENSION); } - $object = Loader::factory($type, $config, '\\think\\config\\driver\\'); + $object = Loader::factory($type, '\\think\\config\\driver\\', $config); return $this->set($object->parse(), $name); } diff --git a/library/think/Debug.php b/library/think/Debug.php index 1c5b75e7..91a25d96 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -237,7 +237,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo($output); + echo ($output); return; } return $output; @@ -250,7 +250,7 @@ class Debug unset($config['type']); - $trace = Loader::factory($type, $config, '\\think\\debug\\'); + $trace = Loader::factory($type, '\\think\\debug\\', $config); if ($response instanceof Redirect) { //TODO 记录 diff --git a/library/think/Loader.php b/library/think/Loader.php index 66c1edc1..d807db64 100644 --- a/library/think/Loader.php +++ b/library/think/Loader.php @@ -385,16 +385,15 @@ class Loader * 创建工厂对象实例 * @access public * @param string $name 工厂类名 - * @param mixed $option 实例化参数 * @param string $namespace 默认命名空间 * @return mixed */ - public static function factory($name, $option = null, $namespace = '') + public static function factory($name, $namespace = '', ...$args) { $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); if (class_exists($class)) { - return Container::getInstance()->invokeClass($class, is_null($option) ? [] : [$option]); + return Container::getInstance()->invokeClass($class, $args); } else { throw new ClassNotFoundException('class not exists:' . $class, $class); } diff --git a/library/think/Log.php b/library/think/Log.php index 936d701f..1c2e9d5c 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -87,7 +87,7 @@ class Log implements LoggerInterface $this->allowWrite = false; } - $this->driver = Loader::factory($type, $config, '\\think\\log\\driver\\'); + $this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config); return $this; } diff --git a/library/think/Template.php b/library/think/Template.php index 4aab04f1..50dad097 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -101,7 +101,7 @@ class Template // 初始化模板编译存储器 $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $this->storage = Loader::factory($type, null, '\\think\\template\\driver\\'); + $this->storage = Loader::factory($type, '\\think\\template\\driver\\', null); } public static function __make(Config $config) diff --git a/library/think/View.php b/library/think/View.php index a1d3ad90..4212438f 100644 --- a/library/think/View.php +++ b/library/think/View.php @@ -111,7 +111,7 @@ class View unset($options['type']); } - $this->engine = Loader::factory($type, $options, '\\think\\view\\driver\\'); + $this->engine = Loader::factory($type, '\\think\\view\\driver\\', $options); return $this; } diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 9f9cb182..67a4958f 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -194,7 +194,7 @@ abstract class Connection $name = md5(serialize($config)); } - self::$instance[$name] = Loader::factory($config['type'], $config, '\\think\\db\\connector\\'); + self::$instance[$name] = Loader::factory($config['type'], '\\think\\db\\connector\\', $config); } return self::$instance[$name]; -- Gitee From e6c0c586e1a6b8ecf9838bdef99ebd3dbe45095a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 13:30:47 +0800 Subject: [PATCH 1257/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Middleware.php | 2 +- library/think/route/Dispatch.php | 35 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/library/think/Middleware.php b/library/think/Middleware.php index d7f9a90c..229ceaa4 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -20,7 +20,7 @@ class Middleware protected $queue = []; protected $app; protected $config = [ - 'default_namespace' => 'app\http\middleware', + 'default_namespace' => 'app\\http\\middleware\\', ]; public function __construct(App $app, array $config = []) diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 3e7d5f74..21830648 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -82,6 +82,9 @@ abstract class Dispatch 'var' => $this->router->getVars(), ]); + // 执行路由后置操作 + $this->routeAfter(); + // 初始化 $this->init(); } @@ -89,26 +92,10 @@ abstract class Dispatch protected function init() {} - /** - * 执行路由调度 - * @access public - * @return mixed - */ - public function run() - { - $result = $this->routeAfter(); - - if ($result instanceof Response) { - return $result; - } - - return $this->exec(); - } - /** * 检查路由后置操作 * @access protected - * @return mixed + * @return void */ protected function routeAfter() { @@ -147,12 +134,22 @@ abstract class Dispatch if (!empty($option['append'])) { $this->request->route($option['append']); } + } + + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run() + { + $option = $this->router->getOption(); // 检测路由after行为 if (!empty($option['after'])) { $dispatch = $this->checkAfter($option['after']); - if (false !== $dispatch) { + if ($dispatch instanceof Response) { return $dispatch; } } @@ -161,6 +158,8 @@ abstract class Dispatch if (isset($option['validate'])) { $this->autoValidate($option['validate'], $request); } + + return $this->exec(); } /** -- Gitee From d2f39195bd986bf44e1fb849753eacbfc3d4e8db Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 14:12:26 +0800 Subject: [PATCH 1258/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Dispatch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 21830648..c08f2c2d 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -127,7 +127,7 @@ abstract class Dispatch } // 开启请求缓存 - if (isset($option['cache']) && $request->isGet()) { + if (isset($option['cache']) && $this->request->isGet()) { $this->parseRequestCache($option['cache']); } -- Gitee From 5197cb801a29ddfe52e3c5710b44adf7dd11f7bf Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 17:01:15 +0800 Subject: [PATCH 1259/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/dispatch/Callback.php | 2 +- library/think/route/dispatch/View.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/dispatch/Callback.php b/library/think/route/dispatch/Callback.php index aab783f6..ca76fc99 100644 --- a/library/think/route/dispatch/Callback.php +++ b/library/think/route/dispatch/Callback.php @@ -15,7 +15,7 @@ use think\route\Dispatch; class Callback extends Dispatch { - public function run() + public function exec() { // 执行回调方法 $vars = array_merge($this->request->param(), $this->param); diff --git a/library/think/route/dispatch/View.php b/library/think/route/dispatch/View.php index ae3071d4..ea3ef11b 100644 --- a/library/think/route/dispatch/View.php +++ b/library/think/route/dispatch/View.php @@ -16,7 +16,7 @@ use think\route\Dispatch; class View extends Dispatch { - public function run() + public function exec() { // 渲染模板输出 $vars = array_merge($this->request->param(), $this->param); -- Gitee From f7c7fb2439e0fef0dc91c17bec73a9759b5453ea Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 17:07:28 +0800 Subject: [PATCH 1260/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BCache=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=E6=A8=A1=E5=9D=97=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + library/think/Cache.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index e62e7d39..e70f7007 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -376,6 +376,7 @@ class App extends Container $this->log->init($config['log']); $this->session->setConfig($config['session']); $this->debug->setConfig($config['trace']); + $this->cache->init($config['cache'], true); // 加载当前模块语言包 $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); diff --git a/library/think/Cache.php b/library/think/Cache.php index d6bd46be..c7a7aa6e 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -77,11 +77,12 @@ class Cache * 自动初始化缓存 * @access public * @param array $options 配置数组 + * @param bool $force 强制更新 * @return Driver */ - public function init(array $options = []) + public function init(array $options = [], $force = false) { - if (is_null($this->handler)) { + if (is_null($this->handler) || $force) { if ('complex' == $options['type']) { $default = $options['default']; -- Gitee From 58dc729a2aa7784a0cdd86d6e41026252e8a759f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 18 May 2018 17:24:40 +0800 Subject: [PATCH 1261/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/Db.php b/library/think/Db.php index 690cb8ee..e86e6e03 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -92,7 +92,8 @@ class Db public static function init($config = []) { self::$config = $config; - if (!empty($config['query'])) { + + if (empty($config['query'])) { self::$config['query'] = '\\think\\db\\Query'; } } -- Gitee From 625d8f3b69daffafb988f8871c382db10828c69c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 08:35:18 +0800 Subject: [PATCH 1262/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3MISS=E8=B7=AF?= =?UTF-8?q?=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index ef40fa6f..90cf5e66 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -180,7 +180,8 @@ class RuleGroup extends Rule $result = new UrlDispatch($request, $ruleItem, $this->auto . '/' . $url, ['auto_search' => false]); } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { // 未匹配所有路由的路由规则处理 - $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); + $ruleItem = new RuleItem($this->router, $this, '', '', $this->miss->getRoute()); + $result = $ruleItem->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); } else { $result = false; } -- Gitee From 1adc81f2d59cd99e69d38c058527cd759531e8a8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 12:16:33 +0800 Subject: [PATCH 1263/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BQuery=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E7=9A=84=E7=9B=B4=E6=8E=A5=E5=AE=9E=E4=BE=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 7819857a..2860359e 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -14,6 +14,7 @@ namespace think\db; use PDO; use think\Collection; use think\Container; +use think\Db; use think\db\exception\BindParamException; use think\db\exception\DataNotFoundException; use think\db\exception\ModelNotFoundException; @@ -116,7 +117,7 @@ class Query public function __construct(Connection $connection = null) { if (is_null($connection)) { - $this->connection = Connection::instance(); + $this->connection = Db::connect(); } else { $this->connection = $connection; } -- Gitee From 3b25e2ac7d898477a74d67354424fbb272ca6e72 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 12:23:58 +0800 Subject: [PATCH 1264/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 954a2b02..d69e4e1d 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -48,8 +48,8 @@ class RuleItem extends Rule * @param RuleGroup $parent 上级对象 * @param string $name 路由标识 * @param string|array $rule 路由规则 - * @param string $method 请求类型 * @param string|\Closure $route 路由地址 + * @param string $method 请求类型 * @param array $option 路由参数 * @param array $pattern 变量规则 */ -- Gitee From 5007dc984f621805ab90a17ab80c4347d9db7d60 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 13:55:46 +0800 Subject: [PATCH 1265/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1D?= =?UTF-8?q?ispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/AliasRule.php | 13 +- library/think/route/Dispatch.php | 39 ++-- library/think/route/Domain.php | 8 +- library/think/route/Resource.php | 3 +- library/think/route/Rule.php | 209 +++++++++++++++----- library/think/route/RuleGroup.php | 10 +- library/think/route/dispatch/Controller.php | 4 +- library/think/route/dispatch/Module.php | 30 +-- library/think/route/dispatch/Url.php | 16 +- 9 files changed, 211 insertions(+), 121 deletions(-) diff --git a/library/think/route/AliasRule.php b/library/think/route/AliasRule.php index 5675e867..daf59fe1 100644 --- a/library/think/route/AliasRule.php +++ b/library/think/route/AliasRule.php @@ -15,8 +15,6 @@ use think\Route; class AliasRule extends Domain { - protected $route; - /** * 架构函数 * @access public @@ -36,7 +34,7 @@ class AliasRule extends Domain } /** - * 检测域名路由 + * 检测路由别名 * @access public * @param Request $request 请求对象 * @param string $url 访问地址 @@ -113,13 +111,4 @@ class AliasRule extends Domain return $this->option('except', $action); } - /** - * 获取当前的路由 - * @access public - * @return string - */ - public function getRoute() - { - return $this->route; - } } diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index c08f2c2d..d19309d6 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -15,7 +15,6 @@ use think\Container; use think\exception\ValidateException; use think\Request; use think\Response; -use think\route\dispatch\ResponseDispatch; abstract class Dispatch { @@ -33,9 +32,9 @@ abstract class Dispatch /** * 路由规则 - * @var RuleItem + * @var Rule */ - protected $router; + protected $rule; /** * 调度信息 @@ -61,10 +60,10 @@ abstract class Dispatch */ protected $convert; - public function __construct(Request $request, RuleItem $router, $dispatch, $param = [], $code = null) + public function __construct(Request $request, Rule $rule, $dispatch, $param = [], $code = null) { $this->request = $request; - $this->router = $router; + $this->rule = $rule; $this->app = Container::get('app'); $this->dispatch = $dispatch; $this->param = $param; @@ -74,16 +73,18 @@ abstract class Dispatch $this->convert = $param['convert']; } - // 设置请求的路由信息 - $this->request->routeInfo([ - 'rule' => $this->router->getRule(), - 'route' => $this->router->getRoute(), - 'option' => $this->router->getOption(), - 'var' => $this->router->getVars(), - ]); - // 执行路由后置操作 - $this->routeAfter(); + if ($this->rule->doAfter()) { + // 设置请求的路由信息 + $this->request->routeInfo([ + 'rule' => $this->rule->getRule(), + 'route' => $this->rule->getRoute(), + 'option' => $this->rule->getOption(), + 'var' => $this->rule->getVars(), + ]); + + $this->doRouteAfter(); + } // 初始化 $this->init(); @@ -97,11 +98,11 @@ abstract class Dispatch * @access protected * @return void */ - protected function routeAfter() + protected function doRouteAfter() { // 记录匹配的路由信息 - $option = $this->router->getOption(); - $matches = $this->router->getVars(); + $option = $this->rule->getOption(); + $matches = $this->rule->getVars(); // 添加中间件 if (!empty($option['middleware'])) { @@ -143,7 +144,7 @@ abstract class Dispatch */ public function run() { - $option = $this->router->getOption(); + $option = $this->rule->getOption(); // 检测路由after行为 if (!empty($option['after'])) { @@ -186,7 +187,7 @@ abstract class Dispatch // 路由规则重定向 if ($result instanceof Response) { - return new ResponseDispatch($result, $this->router); + return $result; } return false; diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 69edf946..4c3f0a35 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -168,7 +168,7 @@ class Domain extends RuleGroup $this->parseUrlParams($request, $array[1]); } - return new CallbackDispatch($request, $this->router, [$class, $action]); + return new CallbackDispatch($request, $this, [$class, $action]); } /** @@ -189,7 +189,7 @@ class Domain extends RuleGroup $this->parseUrlParams($request, $array[2]); } - return new CallbackDispatch($request, $this->router, [$namespace . '\\' . Loader::parseName($class, 1), $method]); + return new CallbackDispatch($request, $this, [$namespace . '\\' . Loader::parseName($class, 1), $method]); } /** @@ -209,7 +209,7 @@ class Domain extends RuleGroup $this->parseUrlParams($request, $array[1]); } - return new ControllerDispatch($request, $this->router, $controller . '/' . $action); + return new ControllerDispatch($request, $this, $controller . '/' . $action); } /** @@ -229,7 +229,7 @@ class Domain extends RuleGroup $this->parseUrlParams($request, $array[1]); } - return new ModuleDispatch($request, $this->router, $controller . '/' . $action); + return new ModuleDispatch($request, $this, $controller . '/' . $action); } } diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index d280cb0b..0d2f4dab 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -17,8 +17,7 @@ class Resource extends RuleGroup { // 资源路由名称 protected $resource; - // 资源路由地址 - protected $route; + // REST路由方法定义 protected $rest = []; diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 455c1a9a..8ae13121 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -23,83 +23,148 @@ use think\route\dispatch\View as ViewDispatch; abstract class Rule { + /** + * 路由标识 + * @var string + */ protected $name; - // 路由对象实例 + + /** + * 路由对象 + * @var Route + */ protected $router; - // 路由父对象 + + /** + * 路由所属分组 + * @var RuleGroup + */ protected $parent; - // 路由参数 + + /** + * 路由规则 + * @var mixed + */ + protected $rule; + + /** + * 路由地址 + * @var string|\Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 路由变量 + * @var array + */ + protected $vars = []; + + /** + * 路由参数 + * @var array + */ protected $option = []; - // 路由变量规则 + + /** + * 路由变量规则 + * @var array + */ protected $pattern = []; - // 需要合并的路由参数 + + /** + * 需要和分组合并的路由参数 + * @var array + */ protected $mergeOptions = ['after', 'before', 'model', 'header', 'response', 'append', 'middleware']; + /** + * 是否需要后置操作 + * @var bool + */ + protected $doAfter; + abstract public function check($request, $url, $depr = '/'); /** - * 注册路由参数 + * 获取Name * @access public - * @param string|array $name 参数名 - * @param mixed $value 值 - * @return $this + * @return string */ - public function option($name, $value = '') + public function getName() { - if (is_array($name)) { - $this->option = array_merge($this->option, $name); - } else { - $this->option[$name] = $value; - } + return $this->name; + } - return $this; + /** + * 获取当前路由规则 + * @access public + * @return string + */ + public function getRule() + { + return $this->rule; } /** - * 注册变量规则 + * 获取当前路由地址 * @access public - * @param string|array $name 变量名 - * @param string $rule 变量规则 - * @return $this + * @return mixed */ - public function pattern($name, $rule = '') + public function getRoute() { - if (is_array($name)) { - $this->pattern = array_merge($this->pattern, $name); - } else { - $this->pattern[$name] = $rule; - } + return $this->route; + } - return $this; + /** + * 获取当前路由的请求类型 + * @access public + * @return string + */ + public function getMethod() + { + return strtolower($this->method); } /** - * 设置标识 + * 获取当前路由的变量 * @access public - * @param string $name 标识名 - * @return $this + * @return array */ - public function name($name) + public function getVars() { - $this->name = $name; + return $this->vars; + } - return $this; + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter() + { + return $this->router; } /** - * 获取Name + * 获取路由对象 * @access public - * @return string + * @return Route */ - public function getName() + public function doAfter() { - return $this->name; + return $this->doAfter; } /** - * 获取Parent对象 + * 获取路由分组 * @access public - * @return $this|null + * @return RuleGroup|null */ public function getParent() { @@ -132,16 +197,6 @@ abstract class Rule return $this->router->config($name); } - /** - * 获取路由对象 - * @access public - * @return Route - */ - public function getRouter() - { - return $this->router; - } - /** * 获取路由参数定义 * @access public @@ -157,6 +212,55 @@ abstract class Rule return isset($this->option[$name]) ? $this->option[$name] : null; } + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + if (is_array($name)) { + $this->option = array_merge($this->option, $name); + } else { + $this->option[$name] = $value; + } + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + if (is_array($name)) { + $this->pattern = array_merge($this->pattern, $name); + } else { + $this->pattern[$name] = $rule; + } + + return $this; + } + + /** + * 设置标识 + * @access public + * @param string $name 标识名 + * @return $this + */ + public function name($name) + { + $this->name = $name; + + return $this; + } + /** * 设置路由请求类型 * @access public @@ -582,9 +686,10 @@ abstract class Rule $url = array_slice(explode('|', $url), $count + 1); $this->parseUrlParams($request, implode('|', $url), $matches); - $this->route = $route; - $this->vars = $matches; - $this->option = $option; + $this->route = $route; + $this->vars = $matches; + $this->option = $option; + $this->doAfter = true; // 发起路由调度 return $this->dispatch($request, $route, $option); diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 90cf5e66..fc1ed612 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -33,8 +33,6 @@ class RuleGroup extends Rule 'options' => [], ]; - protected $rule; - // MISS路由 protected $miss; @@ -131,7 +129,7 @@ class RuleGroup extends Rule $this->buildResourceRule($this->resource, $this->option); } elseif ($this->rule) { if ($this->rule instanceof Response) { - return new ResponseDispatch($request, $this->router, $this->rule); + return new ResponseDispatch($request, $this, $this->rule); } $this->parseGroupRule($this->rule); @@ -176,12 +174,10 @@ class RuleGroup extends Rule if ($this->auto) { // 自动解析URL地址 - $ruleItem = new RuleItem($this->router, $this, '', '', $this->auto . '/' . $url); - $result = new UrlDispatch($request, $ruleItem, $this->auto . '/' . $url, ['auto_search' => false]); + $result = new UrlDispatch($request, $this, $this->auto . '/' . $url, ['auto_search' => false]); } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { // 未匹配所有路由的路由规则处理 - $ruleItem = new RuleItem($this->router, $this, '', '', $this->miss->getRoute()); - $result = $ruleItem->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); + $result = $this->miss->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); } else { $result = false; } diff --git a/library/think/route/dispatch/Controller.php b/library/think/route/dispatch/Controller.php index 3d8a1f0e..1de82992 100644 --- a/library/think/route/dispatch/Controller.php +++ b/library/think/route/dispatch/Controller.php @@ -22,8 +22,8 @@ class Controller extends Dispatch return $this->app->action( $this->dispatch, $vars, - $this->router->getConfig('url_controller_layer'), - $this->router->getConfig('controller_suffix') + $this->rule->getConfig('url_controller_layer'), + $this->rule->getConfig('controller_suffix') ); } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index ad7a7904..acbfff24 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -30,10 +30,10 @@ class Module extends Dispatch $result = explode('/', $result); } - if ($this->router->getConfig('app_multi_module')) { + if ($this->rule->getConfig('app_multi_module')) { // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $this->router->getConfig('default_module'))); - $bind = $this->router->getRouter()->getBind(); + $module = strip_tags(strtolower($result[0] ?: $this->rule->getConfig('default_module'))); + $bind = $this->rule->getRouter()->getBind(); $available = false; if ($bind && preg_match('/^[a-z]/is', $bind)) { @@ -43,10 +43,10 @@ class Module extends Dispatch $module = $bindModule; } $available = true; - } elseif (!in_array($module, $this->router->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { + } elseif (!in_array($module, $this->rule->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { $available = true; - } elseif ($this->router->getConfig('empty_module')) { - $module = $this->router->getConfig('empty_module'); + } elseif ($this->rule->getConfig('empty_module')) { + $module = $this->rule->getConfig('empty_module'); $available = true; } @@ -61,13 +61,13 @@ class Module extends Dispatch } // 是否自动转换控制器和操作名 - $convert = is_bool($this->convert) ? $this->convert : $this->router->getConfig('url_convert'); + $convert = is_bool($this->convert) ? $this->convert : $this->rule->getConfig('url_convert'); // 获取控制器名 - $controller = strip_tags($result[1] ?: $this->router->getConfig('default_controller')); + $controller = strip_tags($result[1] ?: $this->rule->getConfig('default_controller')); $this->controller = $convert ? strtolower($controller) : $controller; // 获取操作名 - $this->actionName = strip_tags($result[2] ?: $this->router->getConfig('default_action')); + $this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action')); // 设置当前请求的控制器、操作 $this->request->controller(Loader::parseName($this->controller, 1))->action($this->actionName); @@ -82,15 +82,15 @@ class Module extends Dispatch // 实例化控制器 try { $instance = $this->app->controller($this->controller, - $this->router->getConfig('url_controller_layer'), - $this->router->getConfig('controller_suffix'), - $this->router->getConfig('empty_controller')); + $this->rule->getConfig('url_controller_layer'), + $this->rule->getConfig('controller_suffix'), + $this->rule->getConfig('empty_controller')); } catch (ClassNotFoundException $e) { throw new HttpException(404, 'controller not exists:' . $e->getClass()); } // 获取当前操作名 - $action = $this->actionName . $this->router->getConfig('action_suffix'); + $action = $this->actionName . $this->rule->getConfig('action_suffix'); if (is_callable([$instance, $action])) { // 执行操作方法 @@ -99,12 +99,12 @@ class Module extends Dispatch // 严格获取当前操作方法名 $reflect = new ReflectionMethod($instance, $action); $methodName = $reflect->getName(); - $suffix = $this->router->getConfig('action_suffix'); + $suffix = $this->rule->getConfig('action_suffix'); $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; $this->request->action($actionName); // 自动获取请求变量 - $vars = $this->router->getConfig('url_param_type') + $vars = $this->rule->getConfig('url_param_type') ? $this->request->route() : $this->request->param(); } elseif (is_callable([$instance, '_empty'])) { diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index f0241ab2..8d5c85d6 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -20,10 +20,10 @@ class Url extends Dispatch protected function init() { // 解析默认的URL规则 - $depr = $this->router->getConfig('pathinfo_depr'); + $depr = $this->rule->getConfig('pathinfo_depr'); $result = $this->parseUrl($this->dispatch, $depr); - $this->dispatch = new Module($this->request, $this->router, $result); + $this->dispatch = new Module($this->request, $this->rule, $result); } public function exec() @@ -40,7 +40,7 @@ class Url extends Dispatch */ protected function parseUrl($url, $depr) { - $bind = $this->router->getRouter()->getBind(); + $bind = $this->rule->getRouter()->getBind(); if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { $bind = str_replace('/', $depr, $bind); @@ -54,7 +54,7 @@ class Url extends Dispatch } // 解析模块 - $module = $this->router->getConfig('app_multi_module') ? array_shift($path) : null; + $module = $this->rule->getConfig('app_multi_module') ? array_shift($path) : null; if ($this->param['auto_search']) { $controller = $this->autoFindController($module, $path); } else { @@ -67,7 +67,7 @@ class Url extends Dispatch // 解析额外参数 if ($path) { - if ($this->router->getConfig('url_param_type')) { + if ($this->rule->getConfig('url_param_type')) { $var += $path; } else { preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { @@ -115,7 +115,7 @@ class Url extends Dispatch $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); } - if ($this->router->getRouter()->getName($name) || $this->router->getRouter()->getName($name2)) { + if ($this->rule->getRouter()->getName($name) || $this->rule->getRouter()->getName($name2)) { return true; } @@ -131,8 +131,8 @@ class Url extends Dispatch */ protected function autoFindController($module, &$path) { - $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->router->getConfig('url_controller_layer'); - $suffix = $this->app->getSuffix() || $this->router->getConfig('controller_suffix') ? ucfirst($this->router->getConfig('url_controller_layer')) : ''; + $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->rule->getConfig('url_controller_layer'); + $suffix = $this->app->getSuffix() || $this->rule->getConfig('controller_suffix') ? ucfirst($this->rule->getConfig('url_controller_layer')) : ''; $item = []; $find = false; -- Gitee From e2b24967434579c713b414d0c61c8956f94a77a9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 14:00:06 +0800 Subject: [PATCH 1266/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 8ae13121..430f9594 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -152,9 +152,9 @@ abstract class Rule } /** - * 获取路由对象 + * 路由是否有后置操作 * @access public - * @return Route + * @return bool */ public function doAfter() { -- Gitee From 843834f347493b9aa970fba1c0b9069882390a9a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 17:12:48 +0800 Subject: [PATCH 1267/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 64 -------------------------------- 1 file changed, 64 deletions(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index d69e4e1d..6a785577 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -17,30 +17,6 @@ use think\Route; class RuleItem extends Rule { - /** - * 路由规则 - * @var string - */ - protected $rule; - - /** - * 路由地址 - * @var string|\Closure - */ - protected $route; - - /** - * 请求类型 - * @var string - */ - protected $method; - - /** - * 路由变量 - * @var array - */ - protected $vars = []; - /** * 架构函数 * @access public @@ -101,46 +77,6 @@ class RuleItem extends Rule $this->setRuleName(); } - /** - * 获取当前路由规则 - * @access public - * @return string - */ - public function getRule() - { - return $this->rule; - } - - /** - * 获取当前路由地址 - * @access public - * @return mixed - */ - public function getRoute() - { - return $this->route; - } - - /** - * 获取当前路由的请求类型 - * @access public - * @return string - */ - public function getMethod() - { - return strtolower($this->method); - } - - /** - * 获取当前路由的变量 - * @access public - * @return array - */ - public function getVars() - { - return $this->vars; - } - /** * 检查后缀 * @access public -- Gitee From 0e356f4c2679dfa9dc4b5b86e4a59bcb3eaffd53 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 18:12:14 +0800 Subject: [PATCH 1268/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BApp=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index e70f7007..e704658d 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -415,26 +415,11 @@ class App extends Container // 监听app_dispatch $this->hook->listen('app_dispatch'); - // 获取应用调度信息 - if (!$this->appDebug && $this->config->get('route_check_cache')) { - $routeKey = $this->getRouteCacheKey(); - - if ($this->cache->has($routeKey)) { - $this->dispatch = $this->cache->get($routeKey); - } - } - $dispatch = $this->dispatch; if (empty($dispatch)) { // 路由检测 $dispatch = $this->routeCheck(); - - try { - if (isset($routeKey)) { - $this->cache->tag('route_cache')->set($routeKey, $dispatch); - } - } catch (\Exception $e) {} } // 记录当前调度信息 @@ -569,6 +554,15 @@ class App extends Container */ public function routeCheck() { + // 获取应用调度信息 + if (!$this->appDebug && $this->config->get('route_check_cache')) { + $routeKey = $this->getRouteCacheKey(); + + if ($this->cache->has($routeKey)) { + return $this->cache->get($routeKey); + } + } + $path = $this->request->path(); // 路由检测 @@ -605,7 +599,19 @@ class App extends Container $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); // 路由检测 返回一个Dispatch对象 - return $this->route->check($path, $must); + $dispatch = $this->route->check($path, $must); + + if (!empty($routeKey)) { + try { + $this->cache + ->tag('route_cache') + ->set($routeKey, $dispatch); + } catch (\Exception $e) { + // 存在闭包的时候缓存无效 + } + } + + return $dispatch; } /** -- Gitee From 1205fe62c7c71f7c96c05f9e967d34e8fb795852 Mon Sep 17 00:00:00 2001 From: ThinkPHP Date: Sat, 19 May 2018 10:12:38 +0000 Subject: [PATCH 1269/1384] Apply fixes from StyleCI --- library/think/Debug.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Debug.php b/library/think/Debug.php index 91a25d96..8aec3c3b 100644 --- a/library/think/Debug.php +++ b/library/think/Debug.php @@ -237,7 +237,7 @@ class Debug $output = '
' . $label . $output . '
'; } if ($echo) { - echo ($output); + echo($output); return; } return $output; -- Gitee From 6448c4b42599d58125ba1307aa6d3defa044aeb9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 19 May 2018 21:37:11 +0800 Subject: [PATCH 1270/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Dispatch.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index d19309d6..4106c0da 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -157,7 +157,7 @@ abstract class Dispatch // 数据自动验证 if (isset($option['validate'])) { - $this->autoValidate($option['validate'], $request); + $this->autoValidate($option['validate']); } return $this->exec(); @@ -197,11 +197,10 @@ abstract class Dispatch * 验证数据 * @access protected * @param array $option - * @param \think\Request $request * @return void * @throws ValidateException */ - protected function autoValidate($option, $request) + protected function autoValidate($option) { list($validate, $scene, $message, $batch) = $option; @@ -226,7 +225,7 @@ abstract class Dispatch $v->batch(true); } - if (!$v->check($request->param())) { + if (!$v->check($this->request->param())) { throw new ValidateException($v->getError()); } } -- Gitee From abeb5febc466b22a48805764e7ac0c564235bdf1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 20 May 2018 10:28:42 +0800 Subject: [PATCH 1271/1384] =?UTF-8?q?=E5=AE=B9=E5=99=A8=E7=B1=BB=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0exists=E6=96=B9=E6=B3=95=20=E4=BB=85=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=AD=98=E5=9C=A8=E5=AF=B9=E8=B1=A1=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/library/think/Container.php b/library/think/Container.php index 94cf449e..4dc7601c 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -178,6 +178,21 @@ class Container implements \ArrayAccess return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); } + /** + * 判断容器中是否存在对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function exists($abstract) + { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + + return isset($this->instances[$abstract]); + } + /** * 判断容器中是否存在类及标识 * @access public -- Gitee From 5d0be8cf1778ed24e383694a2b42ef28ac28b0be Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 20 May 2018 10:38:41 +0800 Subject: [PATCH 1272/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=9A=84autoload=E5=8A=A0=E8=BD=BD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 45 ---------------------------------------- 1 file changed, 45 deletions(-) diff --git a/library/think/Config.php b/library/think/Config.php index 3fa9b136..8d4c1a82 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -25,17 +25,6 @@ class Config implements \ArrayAccess */ private $prefix = 'app'; - /** - * 应用对象 - * @var App - */ - protected $app; - - public function __construct(App $app) - { - $this->app = $app; - } - /** * 设置配置参数默认前缀 * @access public @@ -90,30 +79,6 @@ class Config implements \ArrayAccess return $this->config; } - /** - * 自动加载配置文件(PHP格式) - * @access public - * @param string $name 配置名 - * @return void - */ - protected function autoLoad($name) - { - // 如果尚未载入 则动态加载配置文件 - $module = $this->app->request->module(); - $module = $module ? $module . DIRECTORY_SEPARATOR : ''; - $path = $this->app->getAppPath() . $module; - - if (is_dir($path . 'config')) { - $file = $path . 'config' . DIRECTORY_SEPARATOR . $name . $this->app->getConfigExt(); - } elseif (is_dir($this->app->getConfigPath() . $module)) { - $file = $this->app->getConfigPath() . $module . $name . $this->app->getConfigExt(); - } - - if (isset($file) && is_file($file)) { - $this->load($file, $name); - } - } - /** * 检测配置是否存在 * @access public @@ -139,11 +104,6 @@ class Config implements \ArrayAccess { $name = strtolower($name); - if (!isset($this->config[$name])) { - // 如果尚未载入 则动态加载配置文件 - $this->autoLoad($name); - } - return isset($this->config[$name]) ? $this->config[$name] : []; } @@ -170,11 +130,6 @@ class Config implements \ArrayAccess $name[0] = strtolower($name[0]); $config = $this->config; - if (!isset($config[$name[0]])) { - // 如果尚未载入 则动态加载配置文件 - $this->autoLoad($name[0]); - } - // 按.拆分成多维数组进行判断 foreach ($name as $val) { if (isset($config[$val])) { -- Gitee From b9b57c25ba094515a8bfc142c5067611bf686ccb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 20 May 2018 15:12:13 +0800 Subject: [PATCH 1273/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=A4=A7=E5=B0=8F=E6=8F=90=E9=AB=98=E6=80=A7?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + library/think/route/Dispatch.php | 22 +++++++++++++++------ library/think/route/Rule.php | 26 ++++++++++++++++++++++--- library/think/route/dispatch/Module.php | 4 +++- library/think/route/dispatch/Url.php | 4 +++- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index e704658d..800f561e 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -420,6 +420,7 @@ class App extends Container if (empty($dispatch)) { // 路由检测 $dispatch = $this->routeCheck(); + $dispatch->init(); } // 记录当前调度信息 diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 4106c0da..ab59bac2 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -72,10 +72,16 @@ abstract class Dispatch if (isset($param['convert'])) { $this->convert = $param['convert']; } + } + public function init() + { // 执行路由后置操作 if ($this->rule->doAfter()) { // 设置请求的路由信息 + + // 设置当前请求的参数 + $this->request->route($this->rule->getVars()); $this->request->routeInfo([ 'rule' => $this->rule->getRule(), 'route' => $this->rule->getRoute(), @@ -85,14 +91,8 @@ abstract class Dispatch $this->doRouteAfter(); } - - // 初始化 - $this->init(); } - protected function init() - {} - /** * 检查路由后置操作 * @access protected @@ -316,4 +316,14 @@ abstract class Dispatch abstract public function exec(); + public function __sleep() + { + return ['rule', 'dispatch', 'convert', 'param', 'code', 'controller', 'actionName']; + } + + public function __wakeup() + { + $this->app = Container::get('app'); + $this->request = $this->app['request']; + } } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 430f9594..ec2d89f0 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -261,6 +261,19 @@ abstract class Rule return $this; } + /** + * 设置变量 + * @access public + * @param array $vars 变量 + * @return $this + */ + public function vars($vars) + { + $this->vars = $vars; + + return $this; + } + /** * 设置路由请求类型 * @access public @@ -879,9 +892,6 @@ abstract class Rule }, $url); } } - - // 设置当前请求的参数 - $request->route($var); } /** @@ -1041,4 +1051,14 @@ abstract class Rule return call_user_func_array([$this, 'option'], $args); } + + public function __sleep() + { + return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern', 'doAfter']; + } + + public function __wakeup() + { + $this->router = Container::get('route'); + } } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index acbfff24..937eaf59 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -22,8 +22,10 @@ class Module extends Dispatch protected $controller; protected $actionName; - protected function init() + public function init() { + parent::init(); + $result = $this->dispatch; if (is_string($result)) { diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 8d5c85d6..4c7e85a1 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -17,13 +17,15 @@ use think\route\Dispatch; class Url extends Dispatch { - protected function init() + public function init() { + parent::init(); // 解析默认的URL规则 $depr = $this->rule->getConfig('pathinfo_depr'); $result = $this->parseUrl($this->dispatch, $depr); $this->dispatch = new Module($this->request, $this->rule, $result); + $this->dispatch->init(); } public function exec() -- Gitee From b09ec3ccdb9fb29d913cd5c11fc6f3069e7cd02f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 20 May 2018 15:27:21 +0800 Subject: [PATCH 1274/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDispatch=E7=B1=BBin?= =?UTF-8?q?it=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 3 +-- library/think/route/Dispatch.php | 2 ++ library/think/route/dispatch/Module.php | 1 + library/think/route/dispatch/Url.php | 6 ++---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 800f561e..7589ab12 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -419,8 +419,7 @@ class App extends Container if (empty($dispatch)) { // 路由检测 - $dispatch = $this->routeCheck(); - $dispatch->init(); + $dispatch = $this->routeCheck()->init(); } // 记录当前调度信息 diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index ab59bac2..05983978 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -91,6 +91,8 @@ abstract class Dispatch $this->doRouteAfter(); } + + return $this; } /** diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 937eaf59..091f2f6e 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -74,6 +74,7 @@ class Module extends Dispatch // 设置当前请求的控制器、操作 $this->request->controller(Loader::parseName($this->controller, 1))->action($this->actionName); + return $this; } public function exec() diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 4c7e85a1..c100e045 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -25,13 +25,11 @@ class Url extends Dispatch $result = $this->parseUrl($this->dispatch, $depr); $this->dispatch = new Module($this->request, $this->rule, $result); - $this->dispatch->init(); + return $this->dispatch->init(); } public function exec() - { - return $this->dispatch->exec(); - } + {} /** * 解析URL地址 -- Gitee From 3c09a30fde0342dc7004e8187e43243daffeb77e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 20 May 2018 15:29:25 +0800 Subject: [PATCH 1275/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/dispatch/Url.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index c100e045..8f3edce8 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -20,12 +20,11 @@ class Url extends Dispatch public function init() { parent::init(); + // 解析默认的URL规则 - $depr = $this->rule->getConfig('pathinfo_depr'); - $result = $this->parseUrl($this->dispatch, $depr); + $result = $this->parseUrl($this->dispatch); - $this->dispatch = new Module($this->request, $this->rule, $result); - return $this->dispatch->init(); + return (new Module($this->request, $this->rule, $result))->init(); } public function exec() @@ -35,11 +34,11 @@ class Url extends Dispatch * 解析URL地址 * @access protected * @param string $url URL - * @param string $depr 分隔符 * @return array */ - protected function parseUrl($url, $depr) + protected function parseUrl($url) { + $depr = $this->rule->getConfig('pathinfo_depr'); $bind = $this->rule->getRouter()->getBind(); if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { -- Gitee From b1ed30120f948875f378ec29e4b1bd610b2e9cc4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 21 May 2018 15:19:03 +0800 Subject: [PATCH 1276/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0make:validate?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E7=94=9F=E6=88=90=E9=AA=8C=E8=AF=81=E5=99=A8?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 7 +--- library/think/Console.php | 1 + library/think/Route.php | 6 +-- .../think/console/command/make/Validate.php | 40 +++++++++++++++++++ .../console/command/make/stubs/validate.stub | 24 +++++++++++ library/think/route/dispatch/Url.php | 2 - 6 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 library/think/console/command/make/Validate.php create mode 100644 library/think/console/command/make/stubs/validate.stub diff --git a/library/think/App.php b/library/think/App.php index 7589ab12..152153fa 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -554,7 +554,7 @@ class App extends Container */ public function routeCheck() { - // 获取应用调度信息 + // 检测路由缓存 if (!$this->appDebug && $this->config->get('route_check_cache')) { $routeKey = $this->getRouteCacheKey(); @@ -563,6 +563,7 @@ class App extends Container } } + // 获取应用调度信息 $path = $this->request->path(); // 路由检测 @@ -591,10 +592,6 @@ class App extends Container } } - if (is_file($this->runtimePath . 'rule_regex.php')) { - $this->route->setRuleRegexs(include $this->runtimePath . 'rule_regex.php'); - } - // 是否强制路由模式 $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); diff --git a/library/think/Console.php b/library/think/Console.php index 8782aa92..9757e476 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -42,6 +42,7 @@ class Console "think\\console\\command\\make\\Controller", "think\\console\\command\\make\\Model", "think\\console\\command\\make\\Middleware", + "think\\console\\command\\make\\Validate", "think\\console\\command\\optimize\\Autoload", "think\\console\\command\\optimize\\Config", "think\\console\\command\\optimize\\Schema", diff --git a/library/think/Route.php b/library/think/Route.php index 4b403a15..af5d2191 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -818,9 +818,9 @@ class Route } // 默认路由解析 - $ruleItem = new RuleItem($this, $this->group, '', '', $url); - - return new UrlDispatch($this->request, $ruleItem, $url, ['auto_search' => $this->autoSearchController]); + return new UrlDispatch($this->request, $this->group, $url, [ + 'auto_search' => $this->autoSearchController, + ]); } /** diff --git a/library/think/console/command/make/Validate.php b/library/think/console/command/make/Validate.php new file mode 100644 index 00000000..e7e39e63 --- /dev/null +++ b/library/think/console/command/make/Validate.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Validate extends Make +{ + + protected $type = "Validate"; + + protected function configure() + { + parent::configure(); + $this->setName('make:validate') + ->setDescription('Create a validate class'); + } + + protected function getStub() + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + return $stubPath . 'validate.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\validate'; + } + +} diff --git a/library/think/console/command/make/stubs/validate.stub b/library/think/console/command/make/stubs/validate.stub new file mode 100644 index 00000000..0775bfa4 --- /dev/null +++ b/library/think/console/command/make/stubs/validate.stub @@ -0,0 +1,24 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = []; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = []; +} diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 8f3edce8..1329cb4c 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -19,8 +19,6 @@ class Url extends Dispatch { public function init() { - parent::init(); - // 解析默认的URL规则 $result = $this->parseUrl($this->dispatch); -- Gitee From eee539321edd5681b31f6feb3e5cdfaa8b4663ab Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Tue, 22 May 2018 11:09:00 +0800 Subject: [PATCH 1277/1384] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/facade/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/facade/Route.php b/library/think/facade/Route.php index ac458e0b..3e4dcaae 100644 --- a/library/think/facade/Route.php +++ b/library/think/facade/Route.php @@ -27,7 +27,7 @@ use think\Facade; * @method void import(array $rules, string $type = '*') static 导入配置文件的路由规则 * @method \think\route\RuleItem rule(string $rule, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由规则 * @method void rules(string $rules, string $method = '*', array $option = [], array $pattern = []) static 批量注册路由规则 - * @method \think\route\RuleGroup group(string $name, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由分组 + * @method \think\route\RuleGroup group(string|array $name, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由分组 * @method \think\route\RuleItem any(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 * @method \think\route\RuleItem get(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 * @method \think\route\RuleItem post(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 -- Gitee From 7a7216299d86a4b631d14d469e16364e0e7fe99d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 22 May 2018 18:10:13 +0800 Subject: [PATCH 1278/1384] =?UTF-8?q?Config=E7=B1=BBget=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=BB=98=E8=AE=A4=E5=80=BC=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Config.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/think/Config.php b/library/think/Config.php index 8d4c1a82..1e60ac9d 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -110,10 +110,11 @@ class Config implements \ArrayAccess /** * 获取配置参数 为空则获取所有配置 * @access public - * @param string $name 配置参数名(支持多级配置 .号分割) + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 * @return mixed */ - public function get($name = null) + public function get($name = null, $default = null) { // 无参数时获取所有 if (empty($name)) { @@ -135,7 +136,7 @@ class Config implements \ArrayAccess if (isset($config[$val])) { $config = $config[$val]; } else { - return; + return $default; } } -- Gitee From 346ce96b93bce1f5557d17b10906bde1c8f41274 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 22 May 2018 21:17:43 +0800 Subject: [PATCH 1279/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index e86e6e03..4a13e5cb 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -101,11 +101,16 @@ class Db /** * 获取数据库配置 * @access public - * @return array + * @param string $config 配置名称 + * @return mixed */ - public static function getConfig() + public static function getConfig($name = '') { - return self::$config; + if ('' === $name) { + return self::$config; + } + + return isset(self::$config[$name]) ? self::$config[$name] : null; } /** -- Gitee From 0f9f61f775172ee3b386e5838b3e9a31d1fd2086 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 23 May 2018 17:20:52 +0800 Subject: [PATCH 1280/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BApp=E7=B1=BB?= =?UTF-8?q?=E5=AF=B9null=E6=95=B0=E6=8D=AE=E7=9A=84=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 152153fa..e8643ac2 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -468,7 +468,9 @@ class App extends Container $response = Response::create($data, $type); } else { - $response = Response::create(); + $data = ob_get_clean(); + $status = empty($data) ? 204 : 200; + $response = Response::create($data, '', $status); } return $response; }); -- Gitee From ea9853df2299353afdf2e658ea94a02d373c3432 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 23 May 2018 17:58:43 +0800 Subject: [PATCH 1281/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=B1=BB=E7=9A=84=5F=5Fisset=E6=96=B9=E6=B3=95=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 14 ++++++++++---- library/think/route/Rule.php | 2 +- library/think/route/RuleItem.php | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index d2baf999..753af028 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -11,6 +11,7 @@ namespace think; +use InvalidArgumentException; use think\db\Query; /** @@ -1015,11 +1016,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function __isset($name) { - if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { - return true; + try { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { + $this->getAttr($name); + return true; + } + } catch (InvalidArgumentException $e) { + return false; } - - return false; } /** diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index ec2d89f0..affd328f 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -89,7 +89,7 @@ abstract class Rule */ protected $doAfter; - abstract public function check($request, $url, $depr = '/'); + abstract public function check($request, $url, $completeMatch = false); /** * 获取Name diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 6a785577..1db41d21 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -226,6 +226,7 @@ class RuleItem extends Rule } $pattern = array_merge($this->parent->getPattern(), $this->pattern); + $depr = $this->router->config('pathinfo_depr'); // 检查完整规则定义 if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { @@ -233,7 +234,6 @@ class RuleItem extends Rule } $var = []; - $depr = $this->router->config('pathinfo_depr'); $url = $depr . str_replace('|', $depr, $url); $rule = $depr . str_replace('/', $depr, $this->rule); -- Gitee From 69a4ab8d92175d5c4f37e0f2b05c356a49f00f6d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 23 May 2018 18:05:44 +0800 Subject: [PATCH 1282/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=5F=5Fisset=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 753af028..9497c815 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -1017,12 +1017,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public function __isset($name) { try { - if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { - return true; - } else { - $this->getAttr($name); - return true; - } + return !is_null($this->getAttr($name)); } catch (InvalidArgumentException $e) { return false; } -- Gitee From b3dbccd72de6e620fd71497c12ff1a402cfe746c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 23 May 2018 18:42:28 +0800 Subject: [PATCH 1283/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index e7dfb475..0d30267b 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -951,7 +951,7 @@ abstract class Builder protected function parseComment(Query $query, $comment) { if (false !== strpos($comment, '*/')) { - $comment = strstr($coment, '*/', true); + $comment = strstr($comment, '*/', true); } return !empty($comment) ? ' /* ' . $comment . ' */' : ''; -- Gitee From 8b894b0887c0e48146734e9f77a16786e5216a3b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 24 May 2018 17:06:41 +0800 Subject: [PATCH 1284/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Query=E7=B1=BB?= =?UTF-8?q?=E7=9A=84withAggregate=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 2860359e..23476a32 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2450,10 +2450,19 @@ class Query if ($relation instanceof \Closure) { $closure = $relation; $relation = $key; + } elseif (!is_int($key)) { + $aggregateField = $relation; + $relation = $key; } + + if (!isset($aggregateField)) { + $aggregateField = Loader::parseName($relation) . '_' . $aggregate; + } + $relation = Loader::parseName($relation, 1, false); $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field) . ')'; - $this->field([$count => Loader::parseName($relation) . '_' . $aggregate]); + + $this->field([$count => $aggregateField]); } } -- Gitee From 9c2df6f96247fd8f0c50d11c3be521452f3a1230 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 24 May 2018 22:38:00 +0800 Subject: [PATCH 1285/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRuleItem=E7=B1=BB?= =?UTF-8?q?=E7=9A=84setRuleName=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 1db41d21..31b58408 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -125,7 +125,7 @@ class RuleItem extends Rule $suffix = null; } - $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix]; + $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method]; Container::get('rule_name')->set($name, $value, $first); } -- Gitee From a32d149f75125c1b198897c0c4827be64174be7c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 25 May 2018 12:04:25 +0800 Subject: [PATCH 1286/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E5=92=8C=E5=8F=82=E6=95=B0=E7=9A=84=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Container.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/think/Container.php b/library/think/Container.php index 4dc7601c..743a88d3 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -435,12 +435,13 @@ class Container implements \ArrayAccess */ protected function getObjectParam($className, &$vars) { - $value = array_shift($vars); + $array = $vars; + $value = array_shift($array); if ($value instanceof $className) { $result = $value; + array_shift($vars); } else { - array_unshift($vars, $value); $result = $this->make($className); } -- Gitee From 23051c63f184c958d2cba5aeeb17c95116456ec4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 25 May 2018 17:59:45 +0800 Subject: [PATCH 1287/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Db=E7=B1=BB?= =?UTF-8?q?=E5=AF=B9=E7=AC=AC=E4=B8=89=E6=96=B9=E9=A9=B1=E5=8A=A8=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Db.php b/library/think/Db.php index 4a13e5cb..8b7705a1 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -126,7 +126,7 @@ class Db { // 解析配置参数 $options = self::parseConfig($config ?: self::$config); - $query = $query ?: self::$config['query']; + $query = $query ?: ($options['query'] ?: self::$config['query']); // 创建数据库连接对象实例 self::$connection = Connection::instance($options, $name); -- Gitee From b3c0698f6ba6443a02d6c4c6d6d0e72c6a05fc2f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 25 May 2018 18:04:42 +0800 Subject: [PATCH 1288/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 9497c815..27e3ad78 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -174,11 +174,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->resultSetType = $config['resultset_type']; } - if (is_null($this->query)) { - // 设置查询对象 - $this->query = $config['query']; - } - if (!empty($this->connection) && is_array($this->connection)) { // 设置模型的数据库连接 $this->connection = array_merge($config, $this->connection); -- Gitee From 44fcad888830d7c648e4f7d840be9b697976f88b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 25 May 2018 18:14:02 +0800 Subject: [PATCH 1289/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3File=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E9=A9=B1=E5=8A=A8=E7=9A=84has=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/cache/driver/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 5f10200b..7c5661e3 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -110,7 +110,7 @@ class File extends Driver */ public function has($name) { - return $this->get($name) ? true : false; + return false !== $this->get($name) ? true : false; } /** -- Gitee From 7aa857bd62791aab1c943f1c1232b87360fa1cde Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 25 May 2018 23:44:10 +0800 Subject: [PATCH 1290/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=B5=8C=E5=A5=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 0d2f4dab..1e8521f5 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -80,6 +80,8 @@ class Resource extends RuleGroup $rule = implode('/', $item) . '/' . $last; } + $prefix = substr($rule, strlen($this->name) + 1); + // 注册资源路由 foreach ($this->rest as $key => $val) { if ((isset($option['only']) && !in_array($key, $option['only'])) @@ -95,7 +97,7 @@ class Resource extends RuleGroup $option['rest'] = $key; - $this->addRule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); + $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); } $this->router->setGroup($origin); -- Gitee From d851cb2a433aff4e9cf10f1e52420a991ee6b86b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 26 May 2018 22:25:13 +0800 Subject: [PATCH 1291/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 187 +++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 94 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 6a25c99f..656fcc0f 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -21,17 +21,32 @@ class Request */ protected $instance; - /** - * 应用对象实例 - * @var App - */ - protected $app; - /** * 配置参数 * @var array */ - protected $config = []; + protected $config = [ + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'X-REAL-IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + ]; /** * 请求类型 @@ -268,9 +283,8 @@ class Request * @access public * @param array $options 参数 */ - public function __construct(App $app, array $options = []) + public function __construct(array $options = []) { - $this->app = $app; $this->init($options); // 保存 php://input @@ -296,7 +310,13 @@ class Request public static function __make(App $app, Config $config) { - return new static($app, $config->pull('app')); + $request = new static($config->pull('app')); + $request->session($app['session']->get()); + $request->cookie($app['cookie']->get()); + $request->server($_SERVER); + $request->env($app['env']->get()); + + return $request; } public function __call($method, $args) @@ -411,13 +431,14 @@ class Request $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; $options['content'] = $content; + $request = new static(); foreach ($options as $name => $item) { - if (property_exists($this, $name)) { - $this->$name = $item; + if (property_exists($request, $name)) { + $request->$name = $item; } } - return $this; + return $request; } /** @@ -514,13 +535,13 @@ class Request return $this; } elseif (!$this->url) { if ($this->isCli()) { - $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; - } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { - $this->url = $_SERVER['HTTP_X_REWRITE_URL']; - } elseif (isset($_SERVER['REQUEST_URI'])) { - $this->url = $_SERVER['REQUEST_URI']; - } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { - $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + $this->url = $this->server('argv')[1] ?: ''; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $this->url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $this->url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $this->url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); } else { $this->url = ''; } @@ -562,17 +583,17 @@ class Request } elseif (!$this->baseFile) { $url = ''; if (!$this->isCli()) { - $script_name = basename($_SERVER['SCRIPT_FILENAME']); - if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['SCRIPT_NAME']; - } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { - $url = $_SERVER['PHP_SELF']; - } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['ORIG_SCRIPT_NAME']; - } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { - $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; - } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { - $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif ($this->server('ORIG_SCRIPT_NAME') && basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); } } $this->baseFile = $url; @@ -630,27 +651,29 @@ class Request if (is_null($this->pathinfo)) { if (isset($_GET[$this->config['var_pathinfo']])) { // 判断URL里面是否有兼容模式参数 - $_SERVER['PATH_INFO'] = $_GET[$this->config['var_pathinfo']]; + $pathinfo = $_GET[$this->config['var_pathinfo']]; unset($_GET[$this->config['var_pathinfo']]); } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... - $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + $pathinfo = isset($this->server('argv')[1]) ? $this->server('argv')[1] : ''; } elseif ('cli-server' == PHP_SAPI) { - $_SERVER['PATH_INFO'] = strpos($_SERVER['REQUEST_URI'], '?') ? strstr($_SERVER['REQUEST_URI'], '?', true) : $_SERVER['REQUEST_URI']; + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } elseif ($this->server('PATH_INFO')) { + $pathinfo = $this->server('PATH_INFO'); } // 分析PATHINFO信息 - if (!isset($_SERVER['PATH_INFO'])) { + if (!isset($pathinfo)) { foreach ($this->config['pathinfo_fetch'] as $type) { - if (!empty($_SERVER[$type])) { - $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? - substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); break; } } } - $this->pathinfo = empty($_SERVER['PATH_INFO']) || '/' == $_SERVER['PATH_INFO'] ? '' : ltrim($_SERVER['PATH_INFO'], '/'); + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); } return $this->pathinfo; @@ -699,7 +722,7 @@ class Request */ public function time($float = false) { - return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); } /** @@ -753,15 +776,15 @@ class Request { if (true === $method) { // 获取原始请求类型 - return $this->isCli() ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); + return $this->isCli() ? 'GET' : $this->server('REQUEST_METHOD'); } elseif (!$this->method) { if (isset($_POST[$this->config['var_method']])) { $this->method = strtoupper($_POST[$this->config['var_method']]); $this->{$this->method}($_POST); - } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { - $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); } else { - $this->method = $this->isCli() ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); + $this->method = $this->isCli() ? 'GET' : $this->server('REQUEST_METHOD'); } } @@ -1051,10 +1074,6 @@ class Request */ public function session($name = '', $default = null, $filter = '') { - if (empty($this->session)) { - $this->session = $this->app['session']->get(); - } - if (is_array($name)) { return $this->session = array_merge($this->session, $name); } @@ -1072,16 +1091,10 @@ class Request */ public function cookie($name = '', $default = null, $filter = '') { - $cookie = $this->app['cookie']; - - if (empty($this->cookie)) { - $this->cookie = $cookie->get(); - } - if (is_array($name)) { return $this->cookie = array_merge($this->cookie, $name); } elseif (!empty($name)) { - $data = $cookie->has($name) ? $cookie->get($name) : $default; + $data = isset($this->cookie[$name]) ? $this->cookie[$name] : $default; } else { $data = $this->cookie; } @@ -1109,10 +1122,6 @@ class Request */ public function server($name = '', $default = null, $filter = '') { - if (empty($this->server)) { - $this->server = $_SERVER; - } - if (is_array($name)) { return $this->server = array_merge($this->server, $name); } @@ -1208,10 +1217,6 @@ class Request */ public function env($name = '', $default = null, $filter = '') { - if (empty($this->env)) { - $this->env = $this->app['env']->get(); - } - if (is_array($name)) { return $this->env = array_merge($this->env, $name); } @@ -1523,17 +1528,16 @@ class Request */ public function isSsl() { - $server = array_merge($_SERVER, $this->server); - if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + if ($this->server['HTTPS'] && ('1' == $this->server['HTTPS'] || 'on' == strtolower($this->server['HTTPS']))) { return true; - } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + } elseif ('https' == $this->server['REQUEST_SCHEME']) { return true; - } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + } elseif ('443' == $this->server['SERVER_PORT']) { return true; - } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + } elseif ('https' == $this->server['HTTP_X_FORWARDED_PROTO']) { return true; - } elseif ($this->config['https_agent_name'] && isset($server[$this->config['https_agent_name']])) { + } elseif ($this->config['https_agent_name'] && $this->server[$this->config['https_agent_name']]) { return true; } @@ -1593,23 +1597,23 @@ class Request $httpAgentIp = $this->config['http_agent_ip']; - if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { - $ip = $_SERVER[$httpAgentIp]; + if ($httpAgentIp && $this->server($httpAgentIp)) { + $ip = $this->server($httpAgentIp); } elseif ($adv) { - if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + if ($this->server('HTTP_X_FORWARDED_FOR')) { + $arr = explode(',', $this->server('HTTP_X_FORWARDED_FOR')); $pos = array_search('unknown', $arr); if (false !== $pos) { unset($arr[$pos]); } $ip = trim(current($arr)); - } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { - $ip = $_SERVER['HTTP_CLIENT_IP']; - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; + } elseif ($this->server('HTTP_CLIENT_IP')) { + $ip = $this->server('HTTP_CLIENT_IP'); + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); } - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); } // IP地址类型 @@ -1635,13 +1639,13 @@ class Request */ public function isMobile() { - if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { return true; - } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { return true; - } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { return true; - } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { + } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) { return true; } @@ -1676,11 +1680,7 @@ class Request */ public function host($strict = false) { - if (isset($_SERVER['HTTP_X_REAL_HOST'])) { - $host = $_SERVER['HTTP_X_REAL_HOST']; - } else { - $host = $this->server('HTTP_HOST'); - } + $host = $this->server('HTTP_X_REAL_HOST') ?: $this->server('HTTP_HOST'); return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; } @@ -1879,13 +1879,13 @@ class Request public function token($name = '__token__', $type = 'md5') { $type = is_callable($type) ? $type : 'md5'; - $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); if ($this->isAjax()) { header($name . ': ' . $token); } - $this->app['session']->set($name, $token); + facade\Session::set($name, $token); return $token; } @@ -1953,14 +1953,13 @@ class Request if (isset($fun)) { $key = $fun($key); } - $cache = $this->app['cache']; - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->server('REQUEST_TIME')) { // 读取缓存 $response = Response::create()->code(304); throw new HttpResponseException($response); - } elseif ($cache->has($key)) { - list($content, $header) = $cache->get($key); + } elseif (facade\Cache::has($key)) { + list($content, $header) = facade\Cache::get($key); $response = Response::create($content)->header($header); throw new HttpResponseException($response); -- Gitee From d41928a032ebd0a8f6b910f4498a290122bfbd18 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 26 May 2018 22:58:32 +0800 Subject: [PATCH 1292/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 32 ++++++++++++++++++++++++++++++-- library/think/Request.php | 16 ++-------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index e8643ac2..021bd68f 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -382,7 +382,7 @@ class App extends Container $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); // 模块请求缓存检查 - $this->request->cache( + $this->checkRequestCache( $config['app']['request_cache'], $config['app']['request_cache_expire'], $config['app']['request_cache_except'] @@ -436,7 +436,7 @@ class App extends Container $this->hook->listen('app_begin'); // 请求缓存检查 - $this->request->cache( + $this->checkRequestCache( $this->config('request_cache'), $this->config('request_cache_expire'), $this->config('request_cache_except') @@ -514,6 +514,34 @@ class App extends Container ]); } + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function checkRequestCache($key, $expire = null, $except = [], $tag = null) + { + $cache = $this->request->cache($key, $expire, $except, $tag); + + if ($cache) { + list($key, $expire, $tag) = $cache; + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->server('REQUEST_TIME')) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($this->cache->has($key)) { + list($content, $header) = $this->cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + } + } + /** * 设置当前请求的调度信息 * @access public diff --git a/library/think/Request.php b/library/think/Request.php index 656fcc0f..141bb660 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -11,8 +11,6 @@ namespace think; -use think\exception\HttpResponseException; - class Request { /** @@ -1897,7 +1895,7 @@ class Request * @param mixed $expire 缓存有效期 * @param array $except 缓存排除 * @param string $tag 缓存标签 - * @return void + * @return mixed */ public function cache($key, $expire = null, $except = [], $tag = null) { @@ -1954,18 +1952,8 @@ class Request $key = $fun($key); } - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->server('REQUEST_TIME')) { - // 读取缓存 - $response = Response::create()->code(304); - throw new HttpResponseException($response); - } elseif (facade\Cache::has($key)) { - list($content, $header) = facade\Cache::get($key); - - $response = Response::create($content)->header($header); - throw new HttpResponseException($response); - } - $this->cache = [$key, $expire, $tag]; + return $this->cache; } /** -- Gitee From fd7e1a071940e5ef14546eec39c0a640811ae8ba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 26 May 2018 23:04:17 +0800 Subject: [PATCH 1293/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index 021bd68f..55b88530 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -529,7 +529,7 @@ class App extends Container if ($cache) { list($key, $expire, $tag) = $cache; - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->server('REQUEST_TIME')) { + if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { // 读取缓存 $response = Response::create()->code(304); throw new HttpResponseException($response); -- Gitee From 1bb15b0e1198c1dc891729fa439148937490e8b5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 28 May 2018 12:00:19 +0800 Subject: [PATCH 1294/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 141bb660..1a0bc8a5 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1526,16 +1526,15 @@ class Request */ public function isSsl() { - - if ($this->server['HTTPS'] && ('1' == $this->server['HTTPS'] || 'on' == strtolower($this->server['HTTPS']))) { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { return true; - } elseif ('https' == $this->server['REQUEST_SCHEME']) { + } elseif ('https' == $this->server('REQUEST_SCHEME')) { return true; - } elseif ('443' == $this->server['SERVER_PORT']) { + } elseif ('443' == $this->server('SERVER_PORT')) { return true; - } elseif ('https' == $this->server['HTTP_X_FORWARDED_PROTO']) { + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { return true; - } elseif ($this->config['https_agent_name'] && $this->server[$this->config['https_agent_name']]) { + } elseif ($this->config['https_agent_name'] && $this->server($this->config['https_agent_name'])) { return true; } -- Gitee From 20d78d152fd76119b31643697447f1f7e173e297 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 29 May 2018 12:17:07 +0800 Subject: [PATCH 1295/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0route=5Fcache=5Foption=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E7=9A=84=E7=BC=93=E5=AD=98=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 6 ++++-- library/think/Cache.php | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 55b88530..f599952a 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -587,9 +587,10 @@ class App extends Container // 检测路由缓存 if (!$this->appDebug && $this->config->get('route_check_cache')) { $routeKey = $this->getRouteCacheKey(); + $option = $this->config->get('route_cache_option') ?: $this->cache->getConfig(); - if ($this->cache->has($routeKey)) { - return $this->cache->get($routeKey); + if ($this->cache->connect($option)->has($routeKey)) { + return $this->cache->connect($option)->get($routeKey); } } @@ -631,6 +632,7 @@ class App extends Container if (!empty($routeKey)) { try { $this->cache + ->connect($option) ->tag('route_cache') ->set($routeKey, $dispatch); } catch (\Exception $e) { diff --git a/library/think/Cache.php b/library/think/Cache.php index c7a7aa6e..162cb6d3 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -100,6 +100,11 @@ class Cache return new static($config->pull('cache')); } + public function getConfig() + { + return $this->config; + } + public function setConfig(array $config) { $this->config = array_merge($this->config, $config); -- Gitee From f89199f57f5784943fdfbfe70c2edd5ca9e1e51d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 29 May 2018 14:28:15 +0800 Subject: [PATCH 1296/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E4=B8=AD=E9=97=B4=E4=BB=B6=E5=A4=9A?= =?UTF-8?q?=E6=AC=A1=E6=89=A7=E8=A1=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Resource.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/think/route/Resource.php b/library/think/route/Resource.php index 1e8521f5..5d0d34a0 100644 --- a/library/think/route/Resource.php +++ b/library/think/route/Resource.php @@ -95,9 +95,7 @@ class Resource extends RuleGroup $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); } - $option['rest'] = $key; - - $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0], $option); + $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]); } $this->router->setGroup($origin); -- Gitee From 15b1f5ae27e71b40f74166f82211347de1f5e381 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 29 May 2018 16:20:07 +0800 Subject: [PATCH 1297/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3optimize:config?= =?UTF-8?q?=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/console/command/optimize/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/console/command/optimize/Config.php b/library/think/console/command/optimize/Config.php index 9b692b33..1be4e3a2 100644 --- a/library/think/console/command/optimize/Config.php +++ b/library/think/console/command/optimize/Config.php @@ -99,7 +99,7 @@ class Config extends Command if (is_file($path . 'provider.php')) { $provider = include $path . 'provider.php'; if (is_array($provider)) { - $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export($provider, true) . ');' . PHP_EOL; + $content .= PHP_EOL . '\think\Container::getInstance()->bindTo(' . var_export($provider, true) . ');' . PHP_EOL; } } -- Gitee From 3d6a39830616497531a4f742ec3dfa7d7dce467e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 29 May 2018 18:18:13 +0800 Subject: [PATCH 1298/1384] =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=94=AF=E6=8C=81json=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 ++ library/think/log/driver/File.php | 48 ++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/convention.php b/convention.php index fbce075e..c1753afc 100644 --- a/convention.php +++ b/convention.php @@ -186,6 +186,8 @@ return [ 'level' => [], // 是否记录trace信息到日志 'record_trace' => false, + // 是否JSON格式记录 + 'json' => false, ], // +---------------------------------------------------------------------- diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 1db14925..e309358e 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -25,6 +25,7 @@ class File 'path' => '', 'apart_level' => [], 'max_files' => 0, + 'json' => false, ]; protected $writed = []; @@ -84,7 +85,12 @@ class File if (!is_string($msg)) { $msg = var_export($msg, true); } - $level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; + + if ($this->config['json']) { + $level .= json_encode([$type => str_replace("\n", ' ', $msg)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; + } } if (in_array($type, $this->config['apart_level'])) { @@ -136,18 +142,38 @@ class File $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; $runtime = round(microtime(true) - Container::get('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() - Container::get('app')->getBeginMem()) / 1024, 2); - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; + + $memory_use = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + + if ($this->config['json']) { + $info = [ + 'host' => $_SERVER['HTTP_HOST'], + 'time' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ]; + } else { + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; + } } - $now = date($this->config['time_format']); - $ip = Container::get('request')->ip(); - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message; + if ($this->config['json']) { + $info['timestamp'] = date($this->config['time_format']); + $info['ip'] = Container::get('request')->ip(); + $info['method'] = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; + $info['uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n" . $message; + } else { + $now = date($this->config['time_format']); + $ip = Container::get('request')->ip(); + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message; + } $this->writed[$destination] = true; } -- Gitee From e7a4d3c094f4c06f95e452f5a647b6f54af9e8bb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 29 May 2018 21:36:24 +0800 Subject: [PATCH 1299/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 141bb660..cd1a3719 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -586,7 +586,7 @@ class Request $url = $this->server('SCRIPT_NAME'); } elseif (basename($this->server('PHP_SELF')) === $script_name) { $url = $this->server('PHP_SELF'); - } elseif ($this->server('ORIG_SCRIPT_NAME') && basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { $url = $this->server('ORIG_SCRIPT_NAME'); } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; -- Gitee From 82a8f5ff7ca6b2fdee2bb07c10b1c68e22ccf875 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 29 May 2018 23:45:40 +0800 Subject: [PATCH 1300/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BFile=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 142 +++++++++++++++++----------- library/think/log/driver/Socket.php | 13 ++- 2 files changed, 96 insertions(+), 59 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index e309358e..8f8a6467 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -11,7 +11,7 @@ namespace think\log\driver; -use think\Container; +use think\App; /** * 本地化调试输出到文件 @@ -29,16 +29,19 @@ class File ]; protected $writed = []; + 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'] = Container::get('app')->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR; + $this->config['path'] = $this->app->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR; } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { $this->config['path'] .= DIRECTORY_SEPARATOR; } @@ -51,6 +54,54 @@ class File * @return bool */ public function save(array $log = []) + { + $destination = $this->getMasterLogFile(); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + if ($this->config['json']) { + $info[$type][] = str_replace("\n", ' ', $msg); + } else { + $info[$type][] = '[ ' . $type . ' ]' . $msg; + } + } + + if (in_array($type, $this->config['apart_level'])) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile(); + + $this->write($info[$type], $filename, true); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination); + } + + return true; + } + + protected function getApartLevelFile() + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $filename = $path . DIRECTORY_SEPARATOR . $name . '_' . $type . '.log'; + } elseif ($this->config['max_files']) { + $filename = $path . DIRECTORY_SEPARATOR . date('Ymd') . '_' . $type . $cli . '.log'; + } else { + $filename = $path . DIRECTORY_SEPARATOR . date('d') . '_' . $type . $cli . '.log'; + } + } + + protected function getMasterLogFile() { if ($this->config['single']) { $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; @@ -78,42 +129,19 @@ class File $path = dirname($destination); !is_dir($path) && mkdir($path, 0755, true); - $info = ''; - foreach ($log as $type => $val) { - $level = ''; - foreach ($val as $msg) { - if (!is_string($msg)) { - $msg = var_export($msg, true); - } - - if ($this->config['json']) { - $level .= json_encode([$type => str_replace("\n", ' ', $msg)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; - } else { - $level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; - } - } - - if (in_array($type, $this->config['apart_level'])) { - // 独立记录的日志级别 - if ($this->config['single']) { - $filename = $path . DIRECTORY_SEPARATOR . $name . '_' . $type . '.log'; - } elseif ($this->config['max_files']) { - $filename = $path . DIRECTORY_SEPARATOR . date('Ymd') . '_' . $type . $cli . '.log'; - } else { - $filename = $path . DIRECTORY_SEPARATOR . date('d') . '_' . $type . $cli . '.log'; - } + return $destination; + } - $this->write($level, $filename, true); - } else { - $info .= $level; + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { } - } - if ($info) { - return $this->write($info, $destination); + $this->writed[$destination] = false; } - - return true; } /** @@ -127,23 +155,16 @@ class File protected function write($message, $destination, $apart = false) { // 检测日志文件大小,超过配置大小则备份日志文件重新生成 - if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { - try { - rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); - } catch (\Exception $e) { - } - - $this->writed[$destination] = false; - } + $this->checkLogSize($destination); if (empty($this->writed[$destination]) && PHP_SAPI != 'cli') { - if (Container::get('app')->isDebug() && !$apart) { + if ($this->app->isDebug() && !$apart) { // 获取基本信息 $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - $runtime = round(microtime(true) - Container::get('app')->getBeginTime(), 10); + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $memory_use = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); if ($this->config['json']) { $info = [ @@ -157,22 +178,35 @@ class File $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; + + if (isset($message['info'])) { + array_unshift($message['info'], $current_uri . $time_str . $memory_str . $file_load); + } else { + $message['info'][] = $current_uri . $time_str . $memory_str . $file_load; + } } } if ($this->config['json']) { $info['timestamp'] = date($this->config['time_format']); - $info['ip'] = Container::get('request')->ip(); + $info['ip'] = $this->app['request']->ip(); $info['method'] = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; $info['uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n" . $message; + foreach ($message as $type => $msg) { + $info[$type] = implode(" ", $msg); + } + + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; } else { - $now = date($this->config['time_format']); - $ip = Container::get('request')->ip(); - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message; + $now = date($this->config['time_format']); + $ip = $this->app['request']->ip(); + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); + foreach ($message as $type => $msg) { + $info[$type] = implode("\r\n", $msg); + } + $message = implode("\r\n", $info); } $this->writed[$destination] = true; diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index d3e717fe..29286646 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -11,7 +11,7 @@ namespace think\log\driver; -use think\Container; +use think\App; /** * github: https://github.com/luofei614/SocketLog @@ -41,14 +41,17 @@ class Socket ]; protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + protected $app; /** * 架构函数 * @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); } @@ -68,11 +71,11 @@ class Socket $trace = []; - if (Container::get('app')->isDebug()) { - $runtime = round(microtime(true) - Container::get('app')->getBeginTime(), 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() - Container::get('app')->getBeginMem()) / 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()) . ']'; -- Gitee From d714396c45020833e9ac37330f01b4497d919e91 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 12:06:08 +0800 Subject: [PATCH 1301/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Db=E7=B1=BBconnect?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Db.php b/library/think/Db.php index 8b7705a1..8652866a 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -126,7 +126,7 @@ class Db { // 解析配置参数 $options = self::parseConfig($config ?: self::$config); - $query = $query ?: ($options['query'] ?: self::$config['query']); + $query = $query ?: (!empty($options['query']) ? $options['query'] : self::$config['query']); // 创建数据库连接对象实例 self::$connection = Connection::instance($options, $name); -- Gitee From 6ca011bad90af97e8efdbd1bcf1ab84851d41c26 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 12:15:52 +0800 Subject: [PATCH 1302/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BDb=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Db.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/library/think/Db.php b/library/think/Db.php index 8652866a..f72c0f1d 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -126,7 +126,8 @@ class Db { // 解析配置参数 $options = self::parseConfig($config ?: self::$config); - $query = $query ?: (!empty($options['query']) ? $options['query'] : self::$config['query']); + + $query = $query ?: $options['query']; // 创建数据库连接对象实例 self::$connection = Connection::instance($options, $name); @@ -147,11 +148,13 @@ class Db $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config; } - if (is_string($config)) { - return self::parseDsnConfig($config); - } else { - return $config; + $result = is_string($config) ? self::parseDsnConfig($config) : $config; + + if (empty($result['query'])) { + $result['query'] = self::$config['query']; } + + return $result; } /** -- Gitee From 3c47d48515dda1ad0adb79bacb251cccf0e9f554 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 12:51:11 +0800 Subject: [PATCH 1303/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BLog=E7=B1=BBwrite?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E4=B8=8D=E4=BC=9A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=86=99=E5=85=A5=E4=B9=8B=E5=89=8D=E6=97=A5=E5=BF=97=20?= =?UTF-8?q?=E6=94=B9=E8=BF=9BFile=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Log.php | 19 ++++------ library/think/log/driver/File.php | 62 +++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/library/think/Log.php b/library/think/Log.php index 1c2e9d5c..52076a2e 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -196,7 +196,7 @@ class Log implements LoggerInterface */ public function save() { - if (empty($this->log) || !$this->allowWrite || !$this->driver) { + if (empty($this->log) || !$this->allowWrite) { return true; } @@ -222,6 +222,7 @@ class Log implements LoggerInterface } $result = $this->driver->save($log); + if ($result) { $this->log = []; } @@ -240,11 +241,11 @@ class Log implements LoggerInterface public function write($msg, $type = 'info', $force = false) { // 封装日志信息 - $log = $this->log; + if (empty($this->config['level'])) { + $force = true; + } - if (true === $force || empty($this->config['level'])) { - $log[$type][] = $msg; - } elseif (in_array($type, $this->config['level'])) { + if (true === $force || in_array($type, $this->config['level'])) { $log[$type][] = $msg; } else { return false; @@ -254,13 +255,7 @@ class Log implements LoggerInterface $this->app['hook']->listen('log_write', $log); // 写入日志 - $result = $this->driver->save($log); - - if ($result) { - $this->log = []; - } - - return $result; + return $this->driver->save($log); } /** diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 8f8a6467..18f99d75 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -57,6 +57,9 @@ class File { $destination = $this->getMasterLogFile(); + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + $info = []; foreach ($log as $type => $val) { @@ -74,9 +77,10 @@ class File if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 - $filename = $this->getApartLevelFile(); + $filename = $this->getApartLevelFile($path, $type); $this->write($info[$type], $filename, true); + unset($info[$type]); } } @@ -88,23 +92,16 @@ class File return true; } - protected function getApartLevelFile() - { - $cli = PHP_SAPI == 'cli' ? '_cli' : ''; - if ($this->config['single']) { - $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; - $filename = $path . DIRECTORY_SEPARATOR . $name . '_' . $type . '.log'; - } elseif ($this->config['max_files']) { - $filename = $path . DIRECTORY_SEPARATOR . date('Ymd') . '_' . $type . $cli . '.log'; - } else { - $filename = $path . DIRECTORY_SEPARATOR . date('d') . '_' . $type . $cli . '.log'; - } - } - + /** + * 获取主日志文件名 + * @access public + * @return string + */ protected function getMasterLogFile() { 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 . '.log'; } else { $cli = PHP_SAPI == 'cli' ? '_cli' : ''; @@ -126,12 +123,39 @@ class File $destination = $this->config['path'] . $filename; } - $path = dirname($destination); - !is_dir($path) && mkdir($path, 0755, true); - return $destination; } + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录类型 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $name .= '_' . $type; + } elseif ($this->config['max_files']) { + $name = date('Ymd') . '_' . $type . $cli; + } else { + $name = date('d') . '_' . $type . $cli; + } + + $filename = $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ protected function checkLogSize($destination) { if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { @@ -196,7 +220,7 @@ class File $info[$type] = implode(" ", $msg); } - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\r\n"; } else { $now = date($this->config['time_format']); $ip = $this->app['request']->ip(); -- Gitee From c0f0b9d90c9c15e56e06356d1348505af66677e8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 14:07:48 +0800 Subject: [PATCH 1304/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=97=A5=E5=BF=97F?= =?UTF-8?q?ile=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 18f99d75..e0611ce9 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -129,7 +129,7 @@ class File /** * 获取独立日志文件名 * @access public - * @param string $path 日志目录类型 + * @param string $path 日志目录 * @param string $type 日志类型 * @return string */ @@ -147,7 +147,7 @@ class File $name = date('d') . '_' . $type . $cli; } - $filename = $path . DIRECTORY_SEPARATOR . $name . '.log'; + return $path . DIRECTORY_SEPARATOR . $name . '.log'; } /** -- Gitee From 9e3f5861c72837226d286fde2dfe37d1af7130c4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 14:44:45 +0800 Subject: [PATCH 1305/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=85=B3=E8=81=94=E6=93=8D=E4=BD=9C=E7=9A=84=E5=86=85=E7=BD=AE?= =?UTF-8?q?=E4=BA=8B=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 115 +++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 38 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 27e3ad78..12463550 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -538,17 +538,30 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 模型更新 - $result = $this->db(false)->where($where)->strict(false)->field($allowFields)->update($data); + $db = $this->db(false); + $db->startTrans(); - // 关联更新 - if (!empty($this->relationWrite)) { - $this->autoRelationUpdate(); - } + try { + $result = $db->where($where) + ->strict(false) + ->field($allowFields) + ->update($data); - // 更新回调 - $this->trigger('after_update'); + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } - return $result; + $db->commit(); + + // 更新回调 + $this->trigger('after_update'); + + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } } /** @@ -572,31 +585,43 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检查允许字段 $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); - $result = $this->db(false)->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); + $db = $this->db(false); + $db->startTrans(); + + try { + $result = $db->strict(false) + ->field($allowFields) + ->insert($this->data, $this->replace, false, $sequence); - // 获取自动增长主键 - if ($result && $insertId = $this->db(false)->getLastInsID($sequence)) { - $pk = $this->getPk(); + // 获取自动增长主键 + if ($result && $insertId = $db->getLastInsID($sequence)) { + $pk = $this->getPk(); - foreach ((array) $pk as $key) { - if (!isset($this->data[$key]) || '' == $this->data[$key]) { - $this->data[$key] = $insertId; + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } } } - } - // 关联写入 - if (!empty($this->relationWrite)) { - $this->autoRelationInsert(); - } + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } - // 标记为更新 - $this->isUpdate = true; + $db->commit(); - // 新增回调 - $this->trigger('after_insert'); + // 标记为更新 + $this->isUpdate = true; - return $result; + // 新增回调 + $this->trigger('after_insert'); + + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } } /** @@ -618,7 +643,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->db(false)->where($where)->setInc($field, $step, $lazyTime); + $result = $this->db(false) + ->where($where) + ->setInc($field, $step, $lazyTime); if (true !== $result) { $this->data[$field] += $step; @@ -649,7 +676,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->db(false)->where($where)->setDec($field, $step, $lazyTime); + $result = $this->db(false) + ->where($where) + ->setDec($field, $step, $lazyTime); if (true !== $result) { $this->data[$field] -= $step; @@ -758,21 +787,31 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 读取更新条件 $where = $this->getWhere(); - // 删除当前模型数据 - $result = $this->db(false)->where($where)->delete(); + $db = $this->db(false); + $db->startTrans(); + + try { + // 删除当前模型数据 + $result = $db->where($where)->delete(); - // 关联删除 - if (!empty($this->relationWrite)) { - $this->autoRelationDelete(); - } + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $db->commit(); - $this->trigger('after_delete'); + $this->trigger('after_delete'); - // 清空数据 - $this->data = []; - $this->origin = []; + // 清空数据 + $this->data = []; + $this->origin = []; - return $result; + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } } /** -- Gitee From e99e7a3b5e79a90f3ce34dbca06898a7c1ca2267 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 15:45:48 +0800 Subject: [PATCH 1306/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=97=A5=E5=BF=97J?= =?UTF-8?q?SON=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index e0611ce9..5787de22 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -217,10 +217,10 @@ class File $info['method'] = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; $info['uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; foreach ($message as $type => $msg) { - $info[$type] = implode(" ", $msg); + $info[$type] = implode("\r\n", $msg); } - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\r\n"; + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; } else { $now = date($this->config['time_format']); $ip = $this->app['request']->ip(); -- Gitee From 977e7fcbd03ca5fd11b3705a373941a53e1cd32a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 15:47:17 +0800 Subject: [PATCH 1307/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 5787de22..58754fb7 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -69,7 +69,7 @@ class File } if ($this->config['json']) { - $info[$type][] = str_replace("\n", ' ', $msg); + $info[$type][] = $msg; } else { $info[$type][] = '[ ' . $type . ' ]' . $msg; } -- Gitee From ba007c3b0cf4186b1cfcf34b18ae4a6c9572c177 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 17:29:28 +0800 Subject: [PATCH 1308/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Log.php | 10 +- library/think/log/driver/File.php | 210 +++++++++++++++++++--------- library/think/log/driver/Socket.php | 2 +- 3 files changed, 153 insertions(+), 69 deletions(-) diff --git a/library/think/Log.php b/library/think/Log.php index 52076a2e..5cc98b00 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -126,11 +126,11 @@ class Log implements LoggerInterface $msg = strtr($msg, $replace); } - $this->log[$type][] = $msg; - if (PHP_SAPI == 'cli') { // 命令行日志实时写入 - $this->save(); + $this->write($msg, $type, true); + } else { + $this->log[$type][] = $msg; } return $this; @@ -221,7 +221,7 @@ class Log implements LoggerInterface } } - $result = $this->driver->save($log); + $result = $this->driver->save($log, true); if ($result) { $this->log = []; @@ -255,7 +255,7 @@ class Log implements LoggerInterface $this->app['hook']->listen('log_write', $log); // 写入日志 - return $this->driver->save($log); + return $this->driver->save($log, false); } /** diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 58754fb7..d1f5c05c 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -50,10 +50,11 @@ class File /** * 日志写入接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 * @return bool */ - public function save(array $log = []) + public function save(array $log = [], $append = false) { $destination = $this->getMasterLogFile(); @@ -61,6 +62,7 @@ class File !is_dir($path) && mkdir($path, 0755, true); $info = []; + foreach ($log as $type => $val) { foreach ($val as $msg) { @@ -75,23 +77,48 @@ class File } } - if (in_array($type, $this->config['apart_level'])) { + if (!$this->config['json'] && in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 $filename = $this->getApartLevelFile($path, $type); - $this->write($info[$type], $filename, true); + $this->write($info[$type], $filename, true, $append); unset($info[$type]); } } if ($info) { - return $this->write($info, $destination); + return $this->write($info, $destination, false, $append); } return true; } + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliMessage($message); + } elseif ($this->config['json']) { + $message = $this->parseJsonMessage($message, $append); + } else { + $message = $this->parseWebMessage($message, $append, $apart); + } + + return error_log($message, 3, $destination); + } + /** * 获取主日志文件名 * @access public @@ -163,85 +190,142 @@ class File rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); } catch (\Exception $e) { } - - $this->writed[$destination] = false; } } /** - * 日志写入 + * JSON日志解析 * @access protected * @param array $message 日志信息 - * @param string $destination 日志文件 - * @param bool $apart 是否独立文件写入 - * @return bool + * @param bool $append 是否追加请求信息 + * @return string */ - protected function write($message, $destination, $apart = false) + protected function parseJsonMessage($message, $append) { - // 检测日志文件大小,超过配置大小则备份日志文件重新生成 - $this->checkLogSize($destination); + if ($this->app->isDebug()) { + // 获取基本信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + + $info = [ + 'host' => $this->app['request']->host(), + 'time' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ]; + } - if (empty($this->writed[$destination]) && PHP_SAPI != 'cli') { - if ($this->app->isDebug() && !$apart) { - // 获取基本信息 - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $this->appendJsonRequestLog($info); - $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + foreach ($message as $type => $msg) { + $info[$type] = implode("\r\n", $msg); + } - if ($this->config['json']) { - $info = [ - 'host' => $_SERVER['HTTP_HOST'], - 'time' => number_format($runtime, 6) . 's', - 'reqs' => $reqs . 'req/s', - 'memory' => $memory_use . 'kb', - 'file' => count(get_included_files()), - ]; - } else { - $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - if (isset($message['info'])) { - array_unshift($message['info'], $current_uri . $time_str . $memory_str . $file_load); - } else { - $message['info'][] = $current_uri . $time_str . $memory_str . $file_load; - } - } - } + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } - if ($this->config['json']) { - $info['timestamp'] = date($this->config['time_format']); - $info['ip'] = $this->app['request']->ip(); - $info['method'] = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $info['uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - foreach ($message as $type => $msg) { - $info[$type] = implode("\r\n", $msg); - } + /** + * WEB日志解析 + * @access protected + * @param array $message 日志信息 + * @param bool $append 是否追加请求信息 + * @param bool $apart 是否独立日志 + * @return string + */ + protected function parseWebMessage($message, $append, $apart) + { + if ($this->app->isDebug() && !$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + if (isset($message['info'])) { + array_unshift($message['info'], $time_str . $memory_str . $file_load); } else { - $now = date($this->config['time_format']); - $ip = $this->app['request']->ip(); - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); - foreach ($message as $type => $msg) { - $info[$type] = implode("\r\n", $msg); - } - $message = implode("\r\n", $info); + $message['info'][] = $time_str . $memory_str . $file_load; } + } + + $this->appendRequestLog($message); - $this->writed[$destination] = true; + foreach ($message as $type => $msg) { + $info[$type] = implode("\r\n", $msg); } - if (PHP_SAPI == 'cli') { - $now = date($this->config['time_format']); + return implode("\r\n", $info) . "\r\n"; + } + + /** + * CLI日志解析 + * @access protected + * @param array $message 日志信息 + * @return string + */ + protected function parseCliMessage($message) + { + $now = date($this->config['time_format']); + + if ($this->config['json']) { + $info['timestamp'] = $now; + + foreach ($message as $type => $msg) { + $info[$type] = implode("\r\n", $msg); + } + + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + foreach ($message as $type => $msg) { + $info[$type] = implode("\r\n", $msg); + } + + $message = implode("\r\n", $info); + $message = "[{$now}]" . $message; } - return error_log($message, 3, $destination); + return $message; + } + + /** + * 追加JSON请求日志 + * @access protected + * @param array $info 日志信息 + * @return void + */ + protected function appendJsonRequestLog(&$info) + { + $info['timestamp'] = date($this->config['time_format']); + $info['ip'] = $this->app['request']->ip(); + $info['method'] = $this->app['request']->method(); + $info['uri'] = $this->app['request']->url(); } + /** + * 追加请求日志 + * @access protected + * @param array $message 日志信息 + * @return void + */ + protected function appendRequestLog(&$message) + { + $now = date($this->config['time_format']); + $ip = $this->app['request']->ip(); + $method = $this->app['request']->method(); + $uri = $this->app['request']->url(true); + + if (!isset($message['info'])) { + $message['info'] = []; + } + + array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); + } } diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index 29286646..b4a2cadc 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -63,7 +63,7 @@ class Socket * @param array $log 日志信息 * @return bool */ - public function save(array $log = []) + public function save(array $log = [], $append = false) { if (!$this->check()) { return false; -- Gitee From b52f2d8173ffdd5f5c7f5af9adee77211d8f11ba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 17:57:35 +0800 Subject: [PATCH 1309/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index d1f5c05c..065c6b0c 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -28,7 +28,6 @@ class File 'json' => false, ]; - protected $writed = []; protected $app; // 实例化并传入参数 @@ -218,7 +217,9 @@ class File ]; } - $this->appendJsonRequestLog($info); + if ($append) { + $this->appendJsonRequestLog($info); + } foreach ($message as $type => $msg) { $info[$type] = implode("\r\n", $msg); @@ -255,10 +256,16 @@ class File } } - $this->appendRequestLog($message); + if ($append || $apart) { + $this->appendRequestLog($message, $apart); + } foreach ($message as $type => $msg) { - $info[$type] = implode("\r\n", $msg); + if (is_array($msg)) { + $info[] = implode("\r\n", $msg); + } else { + $info[] = $msg; + } } return implode("\r\n", $info) . "\r\n"; @@ -313,19 +320,25 @@ class File * 追加请求日志 * @access protected * @param array $message 日志信息 + * @param bool $apart 独立日志 * @return void */ - protected function appendRequestLog(&$message) + protected function appendRequestLog(&$message, $apart = false) { $now = date($this->config['time_format']); $ip = $this->app['request']->ip(); $method = $this->app['request']->method(); $uri = $this->app['request']->url(true); - if (!isset($message['info'])) { - $message['info'] = []; + if ($apart) { + array_unshift($message, "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); + } else { + if (!isset($message['info'])) { + $message['info'] = []; + } + + array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); } - array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); } } -- Gitee From f0ed8841e10fa55dfcdd5682afc8b5d994247159 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 18:15:41 +0800 Subject: [PATCH 1310/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 065c6b0c..70e0ef38 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -201,7 +201,7 @@ class File */ protected function parseJsonMessage($message, $append) { - if ($this->app->isDebug()) { + if ($this->app->isDebug() && $append) { // 获取基本信息 $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; @@ -217,9 +217,7 @@ class File ]; } - if ($append) { - $this->appendJsonRequestLog($info); - } + $this->appendJsonRequestLog($info); foreach ($message as $type => $msg) { $info[$type] = implode("\r\n", $msg); @@ -238,7 +236,7 @@ class File */ protected function parseWebMessage($message, $append, $apart) { - if ($this->app->isDebug() && !$apart) { + if ($this->app->isDebug() && $append && !$apart) { // 增加额外的调试信息 $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; @@ -256,9 +254,7 @@ class File } } - if ($append || $apart) { - $this->appendRequestLog($message, $apart); - } + $this->appendRequestLog($message, $apart); foreach ($message as $type => $msg) { if (is_array($msg)) { @@ -332,6 +328,7 @@ class File if ($apart) { array_unshift($message, "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); + } else { if (!isset($message['info'])) { $message['info'] = []; @@ -339,6 +336,5 @@ class File array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); } - } } -- Gitee From 6af2052e205f66f0813626cd758bf14382d8ff11 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 18:21:45 +0800 Subject: [PATCH 1311/1384] =?UTF-8?q?CLI=E6=97=A5=E5=BF=97=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 70e0ef38..4394cd4e 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -287,12 +287,12 @@ class File $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; } else { foreach ($message as $type => $msg) { - $info[$type] = implode("\r\n", $msg); + $info[] = implode("\r\n", $msg); } $message = implode("\r\n", $info); - $message = "[{$now}]" . $message; + $message = "[{$now}]" . $message . "\r\n"; } return $message; -- Gitee From 6d1ad3d16efd946dcd8367b55c60b38f38056671 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 18:28:01 +0800 Subject: [PATCH 1312/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 4394cd4e..ade64a21 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -209,7 +209,6 @@ class File $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); $info = [ - 'host' => $this->app['request']->host(), 'time' => number_format($runtime, 6) . 's', 'reqs' => $reqs . 'req/s', 'memory' => $memory_use . 'kb', @@ -309,6 +308,7 @@ class File $info['timestamp'] = date($this->config['time_format']); $info['ip'] = $this->app['request']->ip(); $info['method'] = $this->app['request']->method(); + $info['host'] = $this->app['request']->host(); $info['uri'] = $this->app['request']->url(); } -- Gitee From 43dfcea7f4e08e6525a640af12c6213404d3e2c0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 18:43:11 +0800 Subject: [PATCH 1313/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index ade64a21..aab5a688 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -246,14 +246,10 @@ class File $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - if (isset($message['info'])) { - array_unshift($message['info'], $time_str . $memory_str . $file_load); - } else { - $message['info'][] = $time_str . $memory_str . $file_load; - } + array_unshift($message, $time_str . $memory_str . $file_load); } - $this->appendRequestLog($message, $apart); + $this->appendRequestLog($message); foreach ($message as $type => $msg) { if (is_array($msg)) { @@ -286,7 +282,7 @@ class File $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; } else { foreach ($message as $type => $msg) { - $info[] = implode("\r\n", $msg); + $info[] = is_array($msg) ? implode("\r\n", $msg) : $msg; } $message = implode("\r\n", $info); @@ -316,25 +312,16 @@ class File * 追加请求日志 * @access protected * @param array $message 日志信息 - * @param bool $apart 独立日志 * @return void */ - protected function appendRequestLog(&$message, $apart = false) + protected function appendRequestLog(&$message) { $now = date($this->config['time_format']); $ip = $this->app['request']->ip(); $method = $this->app['request']->method(); $uri = $this->app['request']->url(true); - if ($apart) { - array_unshift($message, "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); + array_unshift($message, "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); - } else { - if (!isset($message['info'])) { - $message['info'] = []; - } - - array_unshift($message['info'], "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); - } } } -- Gitee From f978113ec99e0a948eeddcfa6498d3e06ea17f0a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 18:48:32 +0800 Subject: [PATCH 1314/1384] =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E7=A9=BA=E6=A0=BC=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index aab5a688..006efd74 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -72,7 +72,7 @@ class File if ($this->config['json']) { $info[$type][] = $msg; } else { - $info[$type][] = '[ ' . $type . ' ]' . $msg; + $info[$type][] = '[ ' . $type . ' ] ' . $msg; } } -- Gitee From 0fae69d96890030b45a52d17710829bb20c8297b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 30 May 2018 22:53:27 +0800 Subject: [PATCH 1315/1384] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 171 +++++++++++------------------- 1 file changed, 62 insertions(+), 109 deletions(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 006efd74..ff322590 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -69,11 +69,7 @@ class File $msg = var_export($msg, true); } - if ($this->config['json']) { - $info[$type][] = $msg; - } else { - $info[$type][] = '[ ' . $type . ' ] ' . $msg; - } + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; } if (!$this->config['json'] && in_array($type, $this->config['apart_level'])) { @@ -107,12 +103,20 @@ class File // 检测日志文件大小,超过配置大小则备份日志文件重新生成 $this->checkLogSize($destination); + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; + } + if (PHP_SAPI == 'cli') { - $message = $this->parseCliMessage($message); - } elseif ($this->config['json']) { - $message = $this->parseJsonMessage($message, $append); + $message = $this->parseCliLog($info); } else { - $message = $this->parseWebMessage($message, $append, $apart); + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); } return error_log($message, 3, $destination); @@ -193,135 +197,84 @@ class File } /** - * JSON日志解析 + * CLI日志解析 * @access protected - * @param array $message 日志信息 - * @param bool $append 是否追加请求信息 + * @param array $info 日志信息 * @return string */ - protected function parseJsonMessage($message, $append) + protected function parseCliLog($info) { - if ($this->app->isDebug() && $append) { - // 获取基本信息 - $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - - $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); - - $info = [ - 'time' => number_format($runtime, 6) . 's', - 'reqs' => $reqs . 'req/s', - 'memory' => $memory_use . 'kb', - 'file' => count(get_included_files()), - ]; - } + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); - $this->appendJsonRequestLog($info); + $message = implode("\r\n", $info); - foreach ($message as $type => $msg) { - $info[$type] = implode("\r\n", $msg); + $message = "[{$now}]" . $message . "\r\n"; } - return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + return $message; } /** - * WEB日志解析 + * 解析日志 * @access protected - * @param array $message 日志信息 - * @param bool $append 是否追加请求信息 - * @param bool $apart 是否独立日志 + * @param array $info 日志信息 * @return string */ - protected function parseWebMessage($message, $append, $apart) + protected function parseLog($info) { - if ($this->app->isDebug() && $append && !$apart) { - // 增加额外的调试信息 - $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - - $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + $requestInfo = [ + 'ip' => $this->app['request']->ip(), + 'method' => $this->app['request']->method(), + 'host' => $this->app['request']->host(), + 'uri' => $this->app['request']->url(), + ]; - $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - array_unshift($message, $time_str . $memory_str . $file_load); + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; } - $this->appendRequestLog($message); - - foreach ($message as $type => $msg) { - if (is_array($msg)) { - $info[] = implode("\r\n", $msg); - } else { - $info[] = $msg; - } - } + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); return implode("\r\n", $info) . "\r\n"; } - /** - * CLI日志解析 - * @access protected - * @param array $message 日志信息 - * @return string - */ - protected function parseCliMessage($message) + protected function getDebugLog(&$info, $append, $apart) { - $now = date($this->config['time_format']); + if ($this->app->isDebug() && $append) { - if ($this->config['json']) { - $info['timestamp'] = $now; + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - foreach ($message as $type => $msg) { - $info[$type] = implode("\r\n", $msg); - } + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; - } else { - foreach ($message as $type => $msg) { - $info[] = is_array($msg) ? implode("\r\n", $msg) : $msg; - } + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; - $message = implode("\r\n", $info); + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $message = "[{$now}]" . $message . "\r\n"; - } + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); - return $message; - } - - /** - * 追加JSON请求日志 - * @access protected - * @param array $info 日志信息 - * @return void - */ - protected function appendJsonRequestLog(&$info) - { - $info['timestamp'] = date($this->config['time_format']); - $info['ip'] = $this->app['request']->ip(); - $info['method'] = $this->app['request']->method(); - $info['host'] = $this->app['request']->host(); - $info['uri'] = $this->app['request']->url(); - } - - /** - * 追加请求日志 - * @access protected - * @param array $message 日志信息 - * @return void - */ - protected function appendRequestLog(&$message) - { - $now = date($this->config['time_format']); - $ip = $this->app['request']->ip(); - $method = $this->app['request']->method(); - $uri = $this->app['request']->url(true); - - array_unshift($message, "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}"); + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + array_unshift($info, $time_str . $memory_str . $file_load); + } + } } } -- Gitee From fd6e0ada3e87dd2e1cda5ca2d46c9b85d63d4326 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 1 Jun 2018 11:41:38 +0800 Subject: [PATCH 1316/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E6=A8=A1=E5=9E=8B=E4=BA=8B=E4=BB=B6=E4=B8=A4?= =?UTF-8?q?=E6=AC=A1=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/SoftDelete.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index da774b82..2aaa5319 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -86,7 +86,9 @@ trait SoftDelete // 软删除 $this->data($name, $this->autoWriteTimestamp($name)); - $result = $this->isUpdate()->save(); + $result = $this->isUpdate()->withEvent(false)->save(); + + $this->withEvent = true; } else { // 读取更新条件 $where = $this->getWhere(); -- Gitee From 0d18306979908540cbc38c314a9a9d8a2bb302bc Mon Sep 17 00:00:00 2001 From: mineyang Date: Thu, 31 May 2018 11:43:30 +0800 Subject: [PATCH 1317/1384] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E7=BA=A7?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=E7=9A=84=E6=B3=A8=E8=A7=A3=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Build.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Build.php b/library/think/Build.php index 6de01402..0c3244da 100644 --- a/library/think/Build.php +++ b/library/think/Build.php @@ -239,7 +239,7 @@ class Build $class = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller); - if (strpos($layer, DIRECTORY_SEPARATOR)) { + if (strpos($layer, '\\')) { // 多级控制器 $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11)); $controller = $level . '.' . $controller; -- Gitee From 04d8180148fe11610a9601ed6a7d7320998d21f1 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 1 Jun 2018 11:56:50 +0800 Subject: [PATCH 1318/1384] =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/App.php b/library/think/App.php index f599952a..34b677af 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App extends Container { - const VERSION = '5.1.14'; + const VERSION = '5.1.15'; /** * 当前模块路径 -- Gitee From 6f0a8ee07f165fe75acca17d870fde441553b73a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 1 Jun 2018 14:35:53 +0800 Subject: [PATCH 1319/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/MorphTo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 208c6ecf..17315c88 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -112,7 +112,7 @@ class MorphTo extends Relation /** * 解析模型的完整命名空间 - * @access public + * @access protected * @param string $model 模型名(或者完整类名) * @return string */ -- Gitee From d0e0c4a165986ad4cba84d1b480b0e1e2570bbcc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 2 Jun 2018 09:47:00 +0800 Subject: [PATCH 1320/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BSession=E7=B1=BB=20?= =?UTF-8?q?=E6=94=B9=E8=BF=9BApp=E7=B1=BB=E7=9A=84=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E6=96=B9=E6=B3=95=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 50 ++++++++++--------------------------- library/think/Container.php | 27 +++++++++++++++----- library/think/Session.php | 4 ++- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 34b677af..c2cee9a1 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -118,6 +118,12 @@ class App extends Container */ protected $bindModule; + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + public function __construct($appPath = '') { $this->appPath = $appPath ? realpath($appPath) . DIRECTORY_SEPARATOR : $this->getAppPath(); @@ -153,39 +159,6 @@ class App extends Container return $this; } - /** - * 注册核心容器实例 - * @access public - * @return void - */ - public function registerCoreContainer() - { - // 注册核心类到容器 - $this->bindTo([ - 'app' => App::class, - 'build' => Build::class, - 'cache' => Cache::class, - 'config' => Config::class, - 'cookie' => Cookie::class, - 'debug' => Debug::class, - 'env' => Env::class, - 'hook' => Hook::class, - 'lang' => Lang::class, - 'log' => Log::class, - 'middleware' => Middleware::class, - 'request' => Request::class, - 'response' => Response::class, - 'route' => Route::class, - 'session' => Session::class, - 'url' => Url::class, - 'validate' => Validate::class, - 'view' => View::class, - 'rule_name' => route\RuleName::class, - // 接口依赖注入 - 'think\LoggerInterface' => Log::class, - ]); - } - /** * 初始化应用 * @access public @@ -193,12 +166,15 @@ class App extends Container */ public function initialize() { - $this->beginTime = microtime(true); - $this->beginMem = memory_get_usage(); + if ($this->initialized) { + return; + } - static::setInstance($this); + $this->initialized = true; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); - $this->registerCoreContainer(); + static::setInstance($this); $this->instance('app', $this); diff --git a/library/think/Container.php b/library/think/Container.php index 743a88d3..79c84e13 100644 --- a/library/think/Container.php +++ b/library/think/Container.php @@ -38,12 +38,27 @@ class Container implements \ArrayAccess * @var array */ protected $bind = [ - 'app' => 'think\App', - 'config' => 'think\Config', - 'lang' => 'think\Lang', - 'log' => 'think\Log', - 'request' => 'think\Request', - 'response' => 'think\Response', + 'app' => App::class, + 'build' => Build::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'cookie' => Cookie::class, + 'debug' => Debug::class, + 'env' => Env::class, + 'hook' => Hook::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'url' => Url::class, + 'validate' => Validate::class, + 'view' => View::class, + 'rule_name' => route\RuleName::class, + // 接口依赖注入 + 'think\LoggerInterface' => Log::class, ]; /** diff --git a/library/think/Session.php b/library/think/Session.php index 2d05599c..6731b03b 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -197,7 +197,9 @@ class Session { if (is_null($this->init)) { $this->init(); - } elseif (false === $this->init) { + } + + if (false === $this->init) { if (PHP_SESSION_ACTIVE != session_status()) { session_start(); } -- Gitee From 23cb5349c6f9222dc7ae4ac96240b171c245f367 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 2 Jun 2018 10:11:16 +0800 Subject: [PATCH 1321/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BB?= =?UTF-8?q?=E7=9A=84param=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 925e2148..346711a8 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -276,6 +276,12 @@ class Request */ protected $secureKey; + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam; + /** * 架构函数 * @access public @@ -889,7 +895,7 @@ class Request */ public function param($name = '', $default = null, $filter = '') { - if (empty($this->param)) { + if (!$this->mergeParam) { $method = $this->method(true); // 自动获取请求变量 @@ -907,7 +913,8 @@ class Request } // 当前请求参数和URL地址中的参数合并 - $this->param = array_merge($this->get(false), $vars, $this->route(false)); + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->mergeParam = true; } if (true === $name) { @@ -931,7 +938,6 @@ class Request public function route($name = '', $default = null, $filter = '') { if (is_array($name)) { - $this->param = []; return $this->route = array_merge($this->route, $name); } @@ -953,7 +959,6 @@ class Request } if (is_array($name)) { - $this->param = []; return $this->get = array_merge($this->get, $name); } @@ -980,7 +985,6 @@ class Request } if (is_array($name)) { - $this->param = []; return $this->post = array_merge($this->post, $name); } @@ -1007,7 +1011,6 @@ class Request } if (is_array($name)) { - $this->param = []; return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); } @@ -1055,7 +1058,6 @@ class Request } if (is_array($name)) { - $this->param = []; return $this->request = array_merge($this->request, $name); } -- Gitee From e64c7ab1ad81bfc4fb99333b83f4c24a41c5214b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 2 Jun 2018 18:41:56 +0800 Subject: [PATCH 1322/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E5=8F=98=E9=87=8F=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index af5d2191..7cd975ad 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -28,11 +28,11 @@ class Route protected $rest = [ 'index' => ['get', '', 'index'], 'create' => ['get', '/create', 'create'], - 'edit' => ['get', '/:id/edit', 'edit'], - 'read' => ['get', '/:id', 'read'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], 'save' => ['post', '', 'save'], - 'update' => ['put', '/:id', 'update'], - 'delete' => ['delete', '/:id', 'delete'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], ]; /** -- Gitee From 8353b68e59e7b9f352b7ef02c0b254a19465cda4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 2 Jun 2018 20:59:41 +0800 Subject: [PATCH 1323/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BB?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=5F=5Fisset=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/think/Request.php b/library/think/Request.php index 346711a8..3bd646ad 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1989,4 +1989,14 @@ class Request return $this->param($name); } + /** + * 检测请求数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + return isset($this->param[$name]); + } } -- Gitee From 49bf0ad722320319a827c90a9ff9475c0c8b42c4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 3 Jun 2018 08:07:54 +0800 Subject: [PATCH 1324/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BuseGlobalScope?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=AF=B9=E8=BD=AF=E5=88=A0=E9=99=A4=E7=9A=84?= =?UTF-8?q?=E5=BD=B1=E5=93=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 12463550..f4c34b97 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -283,12 +283,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query = $this->buildQuery(); - if ($useBaseQuery) { - // 软删除 - if (method_exists($this, 'withNoTrashed')) { - $this->withNoTrashed($query); - } + // 软删除 + if (method_exists($this, 'withNoTrashed')) { + $this->withNoTrashed($query); + } + if ($useBaseQuery) { // 全局作用域 if (method_exists($this, 'base')) { call_user_func_array([$this, 'base'], [ & $query]); -- Gitee From 4e6f3fa1a81f6ec0e0163fb0b8c2b1973127488d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 3 Jun 2018 10:05:56 +0800 Subject: [PATCH 1325/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 3bd646ad..c0a177e4 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -280,7 +280,7 @@ class Request * 是否合并Param * @var bool */ - protected $mergeParam; + protected $mergeParam = false; /** * 架构函数 @@ -959,6 +959,7 @@ class Request } if (is_array($name)) { + $this->mergeParam = false; return $this->get = array_merge($this->get, $name); } @@ -985,6 +986,7 @@ class Request } if (is_array($name)) { + $this->mergeParam = false; return $this->post = array_merge($this->post, $name); } @@ -1011,6 +1013,7 @@ class Request } if (is_array($name)) { + $this->mergeParam = false; return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); } @@ -1238,7 +1241,7 @@ class Request if (function_exists('apache_request_headers') && $result = apache_request_headers()) { $header = $result; } else { - $server = $this->server ?: $_SERVER; + $server = $this->server; foreach ($server as $key => $val) { if (0 === strpos($key, 'HTTP_')) { $key = str_replace('_', '-', strtolower(substr($key, 5))); -- Gitee From a3ea33e696147a5ea5c14dcedf4889ebf9c8f4bd Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 3 Jun 2018 10:55:52 +0800 Subject: [PATCH 1326/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + library/think/Route.php | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/library/think/App.php b/library/think/App.php index c2cee9a1..85e6441c 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -346,6 +346,7 @@ class App extends Container } Db::init($config['database']); + $this->route->setConfig($config['app']); $this->request->init($config['app']); $this->cookie->init($config['cookie']); $this->view->init($config['template']); diff --git a/library/think/Route.php b/library/think/Route.php index 7cd975ad..3e4f95a3 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -144,6 +144,17 @@ class Route return isset($this->config[$name]) ? $this->config[$name] : null; } + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + public static function __make(App $app, Config $config) { $config = $config->pull('app'); -- Gitee From cc5bade726a74eb1580cf72ea73569e842a8df62 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 3 Jun 2018 22:29:07 +0800 Subject: [PATCH 1327/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index c0a177e4..15c4d4ec 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -539,7 +539,7 @@ class Request return $this; } elseif (!$this->url) { if ($this->isCli()) { - $this->url = $this->server('argv')[1] ?: ''; + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; } elseif ($this->server('HTTP_X_REWRITE_URL')) { $this->url = $this->server('HTTP_X_REWRITE_URL'); } elseif ($this->server('REQUEST_URI')) { @@ -659,7 +659,7 @@ class Request unset($_GET[$this->config['var_pathinfo']]); } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... - $pathinfo = isset($this->server('argv')[1]) ? $this->server('argv')[1] : ''; + $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; } elseif ('cli-server' == PHP_SAPI) { $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); } elseif ($this->server('PATH_INFO')) { -- Gitee From 976248f8b4b5d87d119e05b98a3d717b5770789e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 12:04:02 +0800 Subject: [PATCH 1328/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 15c4d4ec..146e3c8d 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -448,7 +448,7 @@ class Request /** * 设置或获取当前包含协议的域名 * @access public - * @param string|bool $domain 域名 + * @param string|true $domain 域名 * @return string|$this */ public function domain($domain = null) @@ -557,7 +557,7 @@ class Request /** * 设置或获取当前URL 不含QUERY_STRING * @access public - * @param string $url URL地址 + * @param string|true $url URL地址 * @return string|$this */ public function baseUrl($url = null) @@ -576,7 +576,7 @@ class Request /** * 设置或获取当前执行的文件 SCRIPT_NAME * @access public - * @param string $file 当前执行的文件 + * @param string|true $file 当前执行的文件 * @return string|$this */ public function baseFile($file = null) @@ -609,7 +609,7 @@ class Request /** * 设置或获取URL访问根地址 * @access public - * @param string $url URL地址 + * @param string|true $url URL地址 * @return string|$this */ public function root($url = null) -- Gitee From f4e63436e7b273e806bc4fa53e6cb277499cdf64 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 12:24:48 +0800 Subject: [PATCH 1329/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BCookie=E7=B1=BBinit?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Cookie.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Cookie.php b/library/think/Cookie.php index 2fd8c31b..08141617 100644 --- a/library/think/Cookie.php +++ b/library/think/Cookie.php @@ -53,7 +53,7 @@ class Cookie { $this->config = array_merge($this->config, array_change_key_case($config)); - if (!empty($this->config['httponly'])) { + if (!empty($this->config['httponly']) && PHP_SESSION_ACTIVE != session_status()) { ini_set('session.cookie_httponly', 1); } } -- Gitee From f6d41aba8e06e4b6562986b1e63d8ed20c492405 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 12:37:04 +0800 Subject: [PATCH 1330/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=A4=9A=E5=AF=B9?= =?UTF-8?q?=E5=A4=9A=E5=85=B3=E8=81=94=E5=88=A0=E9=99=A4=E7=9A=84=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/BelongsToMany.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index a798fd6d..a6ff4ef4 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -566,13 +566,15 @@ class BelongsToMany extends Relation $pivot[] = is_array($id) ? [$this->foreignKey, 'in', $id] : [$this->foreignKey, '=', $id]; } - $this->pivot->where($pivot)->delete(); + $result = $this->pivot->where($pivot)->delete(); // 删除关联表数据 if (isset($id) && $relationDel) { $model = $this->model; $model::destroy($id); } + + return $result; } /** -- Gitee From af47fe1ffbfe1dc5512a0bf46358e1bb9ebc1283 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 14:43:28 +0800 Subject: [PATCH 1331/1384] =?UTF-8?q?=E4=B8=80=E5=AF=B9=E5=A4=9A=E5=85=B3?= =?UTF-8?q?=E8=81=94=E5=86=99=E5=85=A5=E6=94=AF=E6=8C=81replace=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/HasMany.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index aecc2545..4bfb7dae 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -221,10 +221,11 @@ class HasMany extends Relation /** * 保存(新增)当前关联数据对象 * @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(); @@ -235,21 +236,22 @@ class HasMany extends Relation $model = new $this->model; - return $model->save($data) ? $model : false; + return $model->replace($replace)->save($data) ? $model : false; } /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 + * @param array $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 * @return array|false */ - public function saveAll(array $dataSet) + public function saveAll(array $dataSet, $replace = true) { $result = []; foreach ($dataSet as $key => $data) { - $result[] = $this->save($data); + $result[] = $this->save($data, $replace); } return empty($result) ? false : $result; -- Gitee From 4105a7844cdc53445a14881b452aa85257e12fc6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 16:04:01 +0800 Subject: [PATCH 1332/1384] =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=A2=9E=E5=8A=A0f?= =?UTF-8?q?ilter=E6=A3=80=E6=B5=8B=E6=96=B9=E6=B3=95,=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E8=AF=B7=E6=B1=82=E5=8F=82=E6=95=B0=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E8=B7=AF=E7=94=B1=E6=98=AF=E5=90=A6=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index affd328f..c7743476 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -339,6 +339,24 @@ abstract class Rule { return $this->option('domain', $domain); } + + /** + * 设置参数过滤检查 + * @access public + * @param string|array $name + * @param mixed $value + * @return $this + */ + public function filter($name, $value = null) + { + if (is_array($name)) { + $this->option['filter'] = $name; + } else { + $this->option['filter'][$name] = $value; + } + + return $this; + } /** * 绑定模型 @@ -870,6 +888,14 @@ abstract class Rule return false; } + // 请求参数检查 + if (isset($option['filter'])) { + foreach($option['filter'] as $name => $value){ + if ($value != $request->param($name)) { + return false; + } + } + } return true; } -- Gitee From e068e037f204898e5cbe6ae3e47335a791084bab Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 16:28:35 +0800 Subject: [PATCH 1333/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index c7743476..4cbdd4bd 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -339,7 +339,7 @@ abstract class Rule { return $this->option('domain', $domain); } - + /** * 设置参数过滤检查 * @access public @@ -890,8 +890,8 @@ abstract class Rule // 请求参数检查 if (isset($option['filter'])) { - foreach($option['filter'] as $name => $value){ - if ($value != $request->param($name)) { + foreach ($option['filter'] as $name => $value) { + if ($request->param($name) != $value) { return false; } } -- Gitee From 7656398ff9af32868c4ac2962e7847555942b78f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 4 Jun 2018 18:00:41 +0800 Subject: [PATCH 1334/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRule=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 4cbdd4bd..23a9db92 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -891,7 +891,7 @@ abstract class Rule // 请求参数检查 if (isset($option['filter'])) { foreach ($option['filter'] as $name => $value) { - if ($request->param($name) != $value) { + if ($request->param($name, '', null) != $value) { return false; } } -- Gitee From 05c0b0ea30bf920c78b2c7bc791a146055082ab4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 11:55:43 +0800 Subject: [PATCH 1335/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E7=9A=84=E9=97=AD=E5=8C=85=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Dispatch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 05983978..321bbf98 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -263,7 +263,7 @@ abstract class Dispatch { foreach ($bindModel as $key => $val) { if ($val instanceof \Closure) { - $result = $this->app->invokeFunction($val, $matches); + $result = $this->app->invokeFunction($val, [$matches]); } else { $fields = explode('&', $key); -- Gitee From b534e1e708138803f9281fbcc8fb8d8d49df4e62 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 12:29:36 +0800 Subject: [PATCH 1336/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BHasMany=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/HasMany.php | 10 +++++----- library/think/route/Dispatch.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index 4bfb7dae..46082d0a 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -189,13 +189,13 @@ class HasMany extends Relation /** * 一对多 关联模型预查询 * @access public - * @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($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = null) { $foreignKey = $this->foreignKey; diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 321bbf98..05983978 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -263,7 +263,7 @@ abstract class Dispatch { foreach ($bindModel as $key => $val) { if ($val instanceof \Closure) { - $result = $this->app->invokeFunction($val, [$matches]); + $result = $this->app->invokeFunction($val, $matches); } else { $fields = explode('&', $key); -- Gitee From 863e6fd11a7217c3b569012d9bd00db363c871eb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 13:19:36 +0800 Subject: [PATCH 1337/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/MorphMany.php | 4 ++-- library/think/model/relation/MorphOne.php | 4 ++-- library/think/model/relation/OneToOne.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index a50ed8dc..3e913ccd 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -236,10 +236,10 @@ class MorphMany extends Relation * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 + * @param \Closure $closure 闭包 * @return array */ - protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 $this->query->removeOption('where'); diff --git a/library/think/model/relation/MorphOne.php b/library/think/model/relation/MorphOne.php index 09444008..ede680c6 100644 --- a/library/think/model/relation/MorphOne.php +++ b/library/think/model/relation/MorphOne.php @@ -180,10 +180,10 @@ class MorphOne extends Relation * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 + * @param \Closure $closure 闭包 * @return array */ - protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 if ($closure) { diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index 195d0bdd..ac5d4e4c 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -313,10 +313,10 @@ abstract class OneToOne extends Relation * @param string $key 关联键名 * @param string $relation 关联名 * @param string $subRelation 子关联 - * @param bool|\Closure $closure + * @param \Closure $closure * @return array */ - protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = false) + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 if ($closure) { -- Gitee From 878136fa3ed6967a83f2609e11c2275e99249633 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 14:42:05 +0800 Subject: [PATCH 1338/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BSession=E7=B1=BB=20?= =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBsession/env/server=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E5=8F=96=E6=B6=88filter=E5=8F=82=E6=95=B0=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 41 ++++++++++++++++++++++++++------------- library/think/Session.php | 8 ++++++++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 146e3c8d..492b5f0b 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -315,7 +315,7 @@ class Request public static function __make(App $app, Config $config) { $request = new static($config->pull('app')); - $request->session($app['session']->get()); + $request->cookie($app['cookie']->get()); $request->server($_SERVER); $request->env($app['env']->get()); @@ -1072,16 +1072,23 @@ class Request * @access public * @param mixed $name 数据名称 * @param string $default 默认值 - * @param string|array $filter 过滤方法 * @return mixed */ - public function session($name = '', $default = null, $filter = '') + public function session($name = '', $default = null) { + if (empty($this->session)) { + $this->session = facade\Session::get(); + } + if (is_array($name)) { return $this->session = array_merge($this->session, $name); } - return $this->input($this->session, $name, $default, $filter); + if ('' === $name) { + return $this->session; + } + + return isset($this->session[$name]) ? $this->session[$name] : $default; } /** @@ -1120,16 +1127,21 @@ class Request * @access public * @param mixed $name 数据名称 * @param string $default 默认值 - * @param string|array $filter 过滤方法 * @return mixed */ - public function server($name = '', $default = null, $filter = '') + public function server($name = '', $default = null) { if (is_array($name)) { return $this->server = array_merge($this->server, $name); } - return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); + } + + return isset($this->server[$name]) ? $this->server[$name] : $default; } /** @@ -1215,16 +1227,21 @@ class Request * @access public * @param mixed $name 数据名称 * @param string $default 默认值 - * @param string|array $filter 过滤方法 * @return mixed */ - public function env($name = '', $default = null, $filter = '') + public function env($name = '', $default = null) { if (is_array($name)) { return $this->env = array_merge($this->env, $name); } - return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + if (empty($name)) { + return $this->env; + } else { + $name = strtoupper($name); + } + + return isset($this->env[$name]) ? $this->env[$name] : $default; } /** @@ -1292,8 +1309,6 @@ class Request // 解析name if (strpos($name, '/')) { list($name, $type) = explode('/', $name); - } else { - $type = 's'; } // 按.拆分成多维数组进行判断 @@ -1429,12 +1444,12 @@ class Request break; // 字符串 case 's': - default: if (is_scalar($data)) { $data = (string) $data; } else { throw new \InvalidArgumentException('variable type error:' . gettype($data)); } + break; } } diff --git a/library/think/Session.php b/library/think/Session.php index 6731b03b..89e339b2 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -93,6 +93,14 @@ class Session 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']; + } } /** -- Gitee From a0cb1dc2596d84eaf7f01bcd15cff3f05c81db4f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 18:29:11 +0800 Subject: [PATCH 1339/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=85=B3=E8=81=94?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/Conversion.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/think/model/concern/Conversion.php b/library/think/model/concern/Conversion.php index 697e495b..7494f76a 100644 --- a/library/think/model/concern/Conversion.php +++ b/library/think/model/concern/Conversion.php @@ -53,7 +53,7 @@ trait Conversion * @param bool $override 是否覆盖 * @return $this */ - public function append($append = [], $override = false) + public function append(array $append = [], $override = false) { $this->append = $override ? $append : array_merge($this->append, $append); @@ -102,7 +102,7 @@ trait Conversion * @param bool $override 是否覆盖 * @return $this */ - public function hidden($hidden = [], $override = false) + public function hidden(array $hidden = [], $override = false) { $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); @@ -116,7 +116,7 @@ trait Conversion * @param bool $override 是否覆盖 * @return $this */ - public function visible($visible = [], $override = false) + public function visible(array $visible = [], $override = false) { $this->visible = $override ? $visible : array_merge($this->visible, $visible); @@ -168,12 +168,12 @@ trait Conversion if (is_array($name)) { // 追加关联对象属性 $relation = $this->getAttr($key); - $item[$key] = $relation->append($name)->toArray(); + $item[$key] = $relation->visible($name)->append($name)->toArray(); } elseif (strpos($name, '.')) { list($key, $attr) = explode('.', $name); // 追加关联对象属性 $relation = $this->getAttr($key); - $item[$key] = $relation->append([$attr])->toArray(); + $item[$key] = $relation->visible([$attr])->append([$attr])->toArray(); } else { $value = $this->getAttr($name, $item); if (false !== $value) { -- Gitee From 4203f23d991ca22a96e2726ba45e3f1614ad35f9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 19:42:44 +0800 Subject: [PATCH 1340/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=93=8D=E4=BD=9C=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=90=8E=E4=B8=8D=E6=B8=85=E7=A9=BA=E5=AF=B9=E8=B1=A1=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 26 ++++++++++------------ library/think/model/concern/SoftDelete.php | 9 ++++---- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index f4c34b97..f576104e 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -28,10 +28,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess use model\concern\Conversion; /** - * 是否更新数据 + * 是否存在数据 * @var bool */ - private $isUpdate = false; + private $exists = false; /** * 是否Replace @@ -387,7 +387,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->isUpdate ? $this->updateData($where) : $this->insertData($sequence); + $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence); if (false === $result) { return false; @@ -419,7 +419,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } if (!empty($where)) { - $this->isUpdate = true; + $this->exists = true; $this->updateWhere = $where; } } @@ -612,7 +612,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $db->commit(); // 标记为更新 - $this->isUpdate = true; + $this->exists = true; // 新增回调 $this->trigger('after_insert'); @@ -734,7 +734,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } foreach ($dataSet as $key => $data) { - if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { $result[$key] = self::update($data, [], $this->field); } else { $result[$key] = self::create($data, $this->field); @@ -760,13 +760,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public function isUpdate($update = true, $where = null) { if (is_bool($update)) { - $this->isUpdate = $update; + $this->exists = $update; if (!empty($where)) { $this->updateWhere = $where; } } else { - $this->isUpdate = true; + $this->exists = true; $this->updateWhere = $update; } @@ -776,11 +776,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 删除当前的记录 * @access public - * @return integer + * @return bool */ public function delete() { - if (false === $this->trigger('before_delete')) { + if (!$this->exists || false === $this->trigger('before_delete')) { return false; } @@ -803,11 +803,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->trigger('after_delete'); - // 清空数据 - $this->data = []; - $this->origin = []; + $this->exists = false; - return $result; + return true; } catch (\Exception $e) { $db->rollback(); throw $e; diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 2aaa5319..5dc540e5 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -72,11 +72,11 @@ trait SoftDelete * 删除当前的记录 * @access public * @param bool $force 是否强制删除 - * @return integer + * @return bool */ public function delete($force = false) { - if (false === $this->trigger('before_delete', $this)) { + if (!$this->exists || false === $this->trigger('before_delete', $this)) { return false; } @@ -105,10 +105,9 @@ trait SoftDelete $this->trigger('after_delete', $this); // 清空数据 - $this->data = []; - $this->origin = []; + $this->exists = false; - return $result; + return true; } /** -- Gitee From abb27e709ef2780aed7a3d8c89bd3734d498e283 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 5 Jun 2018 23:22:46 +0800 Subject: [PATCH 1341/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=9A=84save=E6=96=B9=E6=B3=95=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E4=B8=BA=E5=B8=83=E5=B0=94=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 60 ++++++++++++---------- library/think/Request.php | 6 --- library/think/model/concern/SoftDelete.php | 12 ++--- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index f576104e..c0dc9a3d 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -288,11 +288,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->withNoTrashed($query); } - if ($useBaseQuery) { - // 全局作用域 - if (method_exists($this, 'base')) { - call_user_func_array([$this, 'base'], [ & $query]); - } + // 全局作用域 + if ($useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); } // 返回当前模型的数据库查询对象 @@ -368,13 +366,23 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this; } + /** + * 新增数据是否使用Replace + * @access public + * @return bool + */ + public function isExists() + { + return $this->exists; + } + /** * 保存当前数据对象 * @access public * @param array $data 数据 * @param array $where 更新条件 * @param string $sequence 自增序列名 - * @return integer|false + * @return bool */ public function save($data = [], $where = [], $sequence = null) { @@ -399,7 +407,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 重新记录原始数据 $this->origin = $this->data; - return $result; + return true; } /** @@ -412,7 +420,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected function checkBeforeSave($data, $where) { if (!empty($data)) { - // 数据对象赋值 foreach ($data as $key => $value) { $this->setAttr($key, $value, $data); @@ -463,14 +470,15 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 废弃字段 $field = array_diff($field, (array) $this->disuse); } + return $field; } /** - * 保存写入数据 + * 更新写入数据 * @access protected - * @param array $where 保存条件 - * @return int|false + * @param mixed $where 更新条件 + * @return bool */ protected function updateData($where) { @@ -491,7 +499,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->autoRelationUpdate(); } - return 0; + return false; } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { // 自动写入更新时间 $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); @@ -542,7 +550,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $db->startTrans(); try { - $result = $db->where($where) + $db->where($where) ->strict(false) ->field($allowFields) ->update($data); @@ -557,7 +565,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 更新回调 $this->trigger('after_update'); - return $result; + return true; } catch (\Exception $e) { $db->rollback(); throw $e; @@ -567,8 +575,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 新增写入数据 * @access protected - * @param string $sequence 自增名 - * @return int|false + * @param string $sequence 自增序列名 + * @return bool */ protected function insertData($sequence) { @@ -617,7 +625,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 新增回调 $this->trigger('after_insert'); - return $result; + return true; } catch (\Exception $e) { $db->rollback(); throw $e; @@ -630,7 +638,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param string $field 字段名 * @param integer $step 增长值 * @param integer $lazyTime 延时时间(s) - * @return integer|true + * @return bool * @throws Exception */ public function setInc($field, $step = 1, $lazyTime = 0) @@ -654,7 +662,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 更新回调 $this->trigger('after_update'); - return $result; + return true; } /** @@ -663,7 +671,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param string $field 字段名 * @param integer $step 减少值 * @param integer $lazyTime 延时时间(s) - * @return integer|true + * @return bool * @throws Exception */ public function setDec($field, $step = 1, $lazyTime = 0) @@ -687,7 +695,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 更新回调 $this->trigger('after_update'); - return $result; + return true; } /** @@ -958,12 +966,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * 删除记录 * @access public * @param mixed $data 主键列表 支持闭包查询条件 - * @return integer 成功删除的记录数 + * @return bool */ public static function destroy($data) { if (empty($data) && 0 !== $data) { - return 0; + return false; } $model = new static(); @@ -979,16 +987,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } $resultSet = $query->select($data); - $count = 0; if ($resultSet) { foreach ($resultSet as $data) { - $result = $data->delete(); - $count += $result; + $data->delete(); } } - return $count; + return true; } /** diff --git a/library/think/Request.php b/library/think/Request.php index 492b5f0b..dc7906eb 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -13,12 +13,6 @@ namespace think; class Request { - /** - * 对象实例 - * @var object - */ - protected $instance; - /** * 配置参数 * @var array diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 5dc540e5..3ffb3d6b 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -104,7 +104,6 @@ trait SoftDelete $this->trigger('after_delete', $this); - // 清空数据 $this->exists = false; return true; @@ -115,7 +114,7 @@ trait SoftDelete * @access public * @param mixed $data 主键列表 支持闭包查询条件 * @param bool $force 是否强制删除 - * @return integer 成功删除的记录数 + * @return bool */ public static function destroy($data, $force = false) { @@ -129,20 +128,18 @@ trait SoftDelete call_user_func_array($data, [ & $query]); $data = null; } elseif (is_null($data)) { - return 0; + return false; } $resultSet = $query->select($data); - $count = 0; if ($resultSet) { foreach ($resultSet as $data) { - $result = $data->delete($force); - $count += $result; + $data->delete($force); } } - return $count; + return true; } /** @@ -175,7 +172,6 @@ trait SoftDelete $this->trigger('after_restore'); return $result; - } return 0; -- Gitee From 9c9b4a41a9bc718ec2d8d1c55a1c8b745e788589 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 11:47:29 +0800 Subject: [PATCH 1342/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBisA?= =?UTF-8?q?jax=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index dc7906eb..3e8b1d26 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1563,8 +1563,8 @@ class Request */ public function isAjax($ajax = false) { - $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); - $result = ('xmlhttprequest' == $value) ? true : false; + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = 'xmlhttprequest' == strtolower($value) ? true : false; if (true === $ajax) { return $result; -- Gitee From 9a92b1cbcb9f684a643430850a45672c4f9ca22e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 14:24:36 +0800 Subject: [PATCH 1343/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E6=A8=A1=E5=9D=97=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/App.php b/library/think/App.php index 85e6441c..1f2bfd56 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -346,6 +346,7 @@ class App extends Container } Db::init($config['database']); + $this->middleware->setConfig($config['middleware']); $this->route->setConfig($config['app']); $this->request->init($config['app']); $this->cookie->init($config['cookie']); -- Gitee From 09213e11e67b3f2d0f50527e40216ee88df667d8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 14:29:13 +0800 Subject: [PATCH 1344/1384] =?UTF-8?q?=E6=83=AF=E4=BE=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=AD=E9=97=B4=E4=BB=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/convention.php b/convention.php index c1753afc..6373e506 100644 --- a/convention.php +++ b/convention.php @@ -4,7 +4,7 @@ return [ // +---------------------------------------------------------------------- // | 应用设置 // +---------------------------------------------------------------------- - 'app' => [ + 'app' => [ // 应用名称 'app_name' => '', // 应用地址 @@ -150,7 +150,7 @@ return [ // | 模板设置 // +---------------------------------------------------------------------- - 'template' => [ + 'template' => [ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 'auto_rule' => 1, // 模板引擎类型 支持 php think 支持扩展 @@ -177,7 +177,7 @@ return [ // | 日志设置 // +---------------------------------------------------------------------- - 'log' => [ + 'log' => [ // 日志记录方式,内置 file socket 支持扩展 'type' => 'File', // 日志保存目录 @@ -194,7 +194,7 @@ return [ // | Trace设置 开启 app_trace 后 有效 // +---------------------------------------------------------------------- - 'trace' => [ + 'trace' => [ // 内置Html Console 支持扩展 'type' => 'Html', 'file' => __DIR__ . '/tpl/page_trace.tpl', @@ -204,7 +204,7 @@ return [ // | 缓存设置 // +---------------------------------------------------------------------- - 'cache' => [ + 'cache' => [ // 驱动方式 'type' => 'File', // 缓存保存目录 @@ -219,7 +219,7 @@ return [ // | 会话设置 // +---------------------------------------------------------------------- - 'session' => [ + 'session' => [ 'id' => '', // SESSION_ID的提交变量,解决flash上传跨域 'var_session_id' => '', @@ -237,7 +237,7 @@ return [ // | Cookie设置 // +---------------------------------------------------------------------- - 'cookie' => [ + 'cookie' => [ // cookie 名称前缀 'prefix' => '', // cookie 保存时间 @@ -258,7 +258,7 @@ return [ // | 数据库设置 // +---------------------------------------------------------------------- - 'database' => [ + 'database' => [ // 数据库类型 'type' => 'mysql', // 数据库连接DSN配置 @@ -304,16 +304,21 @@ return [ ], //分页配置 - 'paginate' => [ + 'paginate' => [ 'type' => 'bootstrap', 'var_page' => 'page', 'list_rows' => 15, ], //控制台配置 - 'console' => [ + 'console' => [ 'name' => 'Think Console', 'version' => '0.1', 'user' => null, ], + + // 中间件配置 + 'middleware' => [ + 'default_namespace' => 'app\\http\\middleware\\', + ], ]; -- Gitee From 1523e78ebf67a18d3f4e5eee93ac61da4d148c71 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 17:09:46 +0800 Subject: [PATCH 1345/1384] =?UTF-8?q?=E5=8F=96=E6=B6=88Request=E7=B1=BB?= =?UTF-8?q?=E7=9A=84get/post/put/request/server/env/cookie/session/file/he?= =?UTF-8?q?ader/url/baseUrl/baseFile/domain=E6=96=B9=E6=B3=95=E7=9A=84?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BB=85=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=8E=B7=E5=8F=96=20route=E6=96=B9=E6=B3=95=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E8=B7=AF=E7=94=B1=E5=8F=98=E9=87=8F=E7=94=A8=E6=B3=95?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=B8=BAsetRouteVars=20panDomain=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=AE=BE=E7=BD=AE=E6=B3=9B=E5=9F=9F=E5=90=8D=E7=9A=84?= =?UTF-8?q?=E7=94=A8=E6=B3=95=E8=B0=83=E6=95=B4=E4=B8=BAsetPanDomain=20roo?= =?UTF-8?q?t=E6=96=B9=E6=B3=95=E7=9A=84=E8=AE=BE=E7=BD=AE=E7=94=A8?= =?UTF-8?q?=E6=B3=95=E8=B0=83=E6=95=B4=E4=B8=BAsetRoot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 209 +++++++++++---------------- library/think/Route.php | 2 +- library/think/Url.php | 2 +- library/think/route/Dispatch.php | 4 +- library/think/route/Domain.php | 2 +- library/think/route/Rule.php | 2 +- library/think/route/dispatch/Url.php | 2 +- 7 files changed, 92 insertions(+), 131 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 3e8b1d26..1486de92 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -440,26 +440,14 @@ class Request } /** - * 设置或获取当前包含协议的域名 + * 获取当前包含协议、端口的域名 * @access public - * @param string|true $domain 域名 - * @return string|$this + * @param bool $port 是否需要去除端口号 + * @return string */ - public function domain($domain = null) + public function domain($port = false) { - if (is_null($domain)) { - if (!$this->domain) { - $this->domain = $this->scheme() . '://' . $this->host(); - } - return $this->domain; - } - - if (true === $domain) { - return $this->scheme() . '://' . $this->host(true); - } - - $this->domain = $domain; - return $this; + return $this->scheme() . '://' . $this->host($port); } /** @@ -505,33 +493,36 @@ class Request } /** - * 设置或获取当前泛域名的值 + * 设置当前泛域名的值 * @access public * @param string $domain 域名 - * @return string|$this + * @return $this */ - public function panDomain($domain = null) + public function setPanDomain($domain) { - if (is_null($domain)) { - return $this->panDomain; - } - $this->panDomain = $domain; return $this; } /** - * 设置或获取当前完整URL 包括QUERY_STRING + * 获取当前泛域名的值 * @access public - * @param string|true $url URL地址 true 带域名获取 - * @return string|$this + * @return string */ - public function url($url = null) + public function panDomain() { - if (!is_null($url) && true !== $url) { - $this->url = $url; - return $this; - } elseif (!$this->url) { + return $this->panDomain; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含域名 + * @return string + */ + public function url($complete = false) + { + if (!$this->url) { if ($this->isCli()) { $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; } elseif ($this->server('HTTP_X_REWRITE_URL')) { @@ -545,40 +536,34 @@ class Request } } - return true === $url ? $this->domain() . $this->url : $this->url; + return $complete ? $this->domain() . $this->url : $this->url; } /** - * 设置或获取当前URL 不含QUERY_STRING + * 获取当前URL 不含QUERY_STRING * @access public - * @param string|true $url URL地址 + * @param bool $domain 是否包含域名 * @return string|$this */ - public function baseUrl($url = null) + public function baseUrl($domain = false) { - if (!is_null($url) && true !== $url) { - $this->baseUrl = $url; - return $this; - } elseif (!$this->baseUrl) { + if (!$this->baseUrl) { $str = $this->url(); $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; } - return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + return $domain ? $this->domain() . $this->baseUrl : $this->baseUrl; } /** * 设置或获取当前执行的文件 SCRIPT_NAME * @access public - * @param string|true $file 当前执行的文件 + * @param bool $domain 是否包含域名 * @return string|$this */ - public function baseFile($file = null) + public function baseFile($domain = false) { - if (!is_null($file) && true !== $file) { - $this->baseFile = $file; - return $this; - } elseif (!$this->baseFile) { + if (!$this->baseFile) { $url = ''; if (!$this->isCli()) { $script_name = basename($this->server('SCRIPT_FILENAME')); @@ -597,21 +582,31 @@ class Request $this->baseFile = $url; } - return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + return $domain ? $this->domain() . $this->baseFile : $this->baseFile; } /** - * 设置或获取URL访问根地址 + * 设置URL访问根地址 * @access public - * @param string|true $url URL地址 + * @param string $url URL地址 * @return string|$this */ - public function root($url = null) + public function setRoot($url = null) { - if (!is_null($url) && true !== $url) { - $this->root = $url; - return $this; - } elseif (!$this->root) { + + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function root($domain = false) + { + if (!$this->root) { $file = $this->baseFile(); if ($file && 0 !== strpos($this->url(), $file)) { $file = str_replace('\\', '/', dirname($file)); @@ -619,7 +614,7 @@ class Request $this->root = rtrim($file, '/'); } - return true === $url ? $this->domain() . $this->root : $this->root; + return $domain ? $this->domain() . $this->root : $this->root; } /** @@ -922,7 +917,18 @@ class Request } /** - * 设置获取路由参数 + * 设置路由变量 + * @access public + * @param array $route 路由变量 + * @return void + */ + public function setRouteVars(array $name) + { + $this->route = array_merge($this->route, $route); + } + + /** + * 获取路由参数 * @access public * @param mixed $name 变量名 * @param mixed $default 默认值 @@ -931,17 +937,13 @@ class Request */ public function route($name = '', $default = null, $filter = '') { - if (is_array($name)) { - return $this->route = array_merge($this->route, $name); - } - return $this->input($this->route, $name, $default, $filter); } /** - * 设置获取GET参数 + * 获取GET参数 * @access public - * @param mixed $name 变量名 + * @param string|false $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -952,18 +954,13 @@ class Request $this->get = $_GET; } - if (is_array($name)) { - $this->mergeParam = false; - return $this->get = array_merge($this->get, $name); - } - return $this->input($this->get, $name, $default, $filter); } /** - * 设置获取POST参数 + * 获取POST参数 * @access public - * @param mixed $name 变量名 + * @param string|false $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -979,18 +976,13 @@ class Request } } - if (is_array($name)) { - $this->mergeParam = false; - return $this->post = array_merge($this->post, $name); - } - return $this->input($this->post, $name, $default, $filter); } /** - * 设置获取PUT参数 + * 获取PUT参数 * @access public - * @param mixed $name 变量名 + * @param string|false $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -1006,18 +998,13 @@ class Request } } - if (is_array($name)) { - $this->mergeParam = false; - return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); - } - return $this->input($this->put, $name, $default, $filter); } /** - * 设置获取DELETE参数 + * 获取DELETE参数 * @access public - * @param mixed $name 变量名 + * @param string|false $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -1028,9 +1015,9 @@ class Request } /** - * 设置获取PATCH参数 + * 获取PATCH参数 * @access public - * @param mixed $name 变量名 + * @param string|false $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -1043,8 +1030,8 @@ class Request /** * 获取request变量 * @access public - * @param mixed $name 数据名称 - * @param string $default 默认值 + * @param string|false $name 变量名 + * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed */ @@ -1054,17 +1041,13 @@ class Request $this->request = $_REQUEST; } - if (is_array($name)) { - return $this->request = array_merge($this->request, $name); - } - return $this->input($this->request, $name, $default, $filter); } /** * 获取session数据 * @access public - * @param mixed $name 数据名称 + * @param string $name 数据名称 * @param string $default 默认值 * @return mixed */ @@ -1074,10 +1057,6 @@ class Request $this->session = facade\Session::get(); } - if (is_array($name)) { - return $this->session = array_merge($this->session, $name); - } - if ('' === $name) { return $this->session; } @@ -1088,16 +1067,14 @@ class Request /** * 获取cookie参数 * @access public - * @param mixed $name 数据名称 + * @param string $name 变量名 * @param string $default 默认值 * @param string|array $filter 过滤方法 * @return mixed */ public function cookie($name = '', $default = null, $filter = '') { - if (is_array($name)) { - return $this->cookie = array_merge($this->cookie, $name); - } elseif (!empty($name)) { + if (!empty($name)) { $data = isset($this->cookie[$name]) ? $this->cookie[$name] : $default; } else { $data = $this->cookie; @@ -1119,16 +1096,12 @@ class Request /** * 获取server参数 * @access public - * @param mixed $name 数据名称 + * @param string $name 数据名称 * @param string $default 默认值 * @return mixed */ public function server($name = '', $default = null) { - if (is_array($name)) { - return $this->server = array_merge($this->server, $name); - } - if (empty($name)) { return $this->server; } else { @@ -1141,7 +1114,7 @@ class Request /** * 获取上传的文件信息 * @access public - * @param string|array $name 名称 + * @param string $name 名称 * @return null|array|\think\File */ public function file($name = '') @@ -1150,10 +1123,6 @@ class Request $this->file = isset($_FILES) ? $_FILES : []; } - if (is_array($name)) { - return $this->file = array_merge($this->file, $name); - } - $files = $this->file; if (!empty($files)) { // 处理上传文件 @@ -1219,16 +1188,12 @@ class Request /** * 获取环境变量 * @access public - * @param mixed $name 数据名称 + * @param string $name 数据名称 * @param string $default 默认值 * @return mixed */ public function env($name = '', $default = null) { - if (is_array($name)) { - return $this->env = array_merge($this->env, $name); - } - if (empty($name)) { return $this->env; } else { @@ -1239,11 +1204,11 @@ class Request } /** - * 设置或者获取当前的Header + * 获取当前的Header * @access public - * @param string|array $name header名称 - * @param string $default 默认值 - * @return string + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array */ public function header($name = '', $default = null) { @@ -1269,10 +1234,6 @@ class Request $this->header = array_change_key_case($header); } - if (is_array($name)) { - return $this->header = array_merge($this->header, $name); - } - if ('' === $name) { return $this->header; } diff --git a/library/think/Route.php b/library/think/Route.php index 3e4f95a3..9fde8927 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -872,7 +872,7 @@ class Route if (isset($panDomain)) { // 保存当前泛域名 - $this->request->panDomain($panDomain); + $this->request->setPanDomain($panDomain); } } diff --git a/library/think/Url.php b/library/think/Url.php index 7cbdfffa..32fb9c71 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -384,6 +384,6 @@ class Url public function root($root) { $this->root = $root; - $this->app['request']->root($root); + $this->app['request']->setRoot($root); } } diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 05983978..27a56d46 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -81,7 +81,7 @@ abstract class Dispatch // 设置请求的路由信息 // 设置当前请求的参数 - $this->request->route($this->rule->getVars()); + $this->request->setRouteVars($this->rule->getVars()); $this->request->routeInfo([ 'rule' => $this->rule->getRule(), 'route' => $this->rule->getRoute(), @@ -135,7 +135,7 @@ abstract class Dispatch } if (!empty($option['append'])) { - $this->request->route($option['append']); + $this->request->setRouteVars($option['append']); } } diff --git a/library/think/route/Domain.php b/library/think/route/Domain.php index 4c3f0a35..260ca500 100644 --- a/library/think/route/Domain.php +++ b/library/think/route/Domain.php @@ -61,7 +61,7 @@ class Domain extends RuleGroup $result = $this->checkUrlBind($request, $url); if (!empty($this->option['append'])) { - $request->route($this->option['append']); + $request->setRouteVars($this->option['append']); unset($this->option['append']); } diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 23a9db92..687ea981 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -839,7 +839,7 @@ abstract class Rule } // 设置当前请求的路由变量 - $request->route($var); + $request->setRouteVars($var); // 路由到模块/控制器/操作 return new ModuleDispatch($request, $this, [$module, $controller, $action], ['convert' => false]); diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 1329cb4c..266ff940 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -80,7 +80,7 @@ class Url extends Dispatch } // 设置当前请求的参数 - $this->request->route($var); + $this->request->setRouteVars($var); // 封装路由 $route = [$module, $controller, $action]; -- Gitee From 4e57890c0ffaddd91c177e3b52e25467e071f38f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 17:40:00 +0800 Subject: [PATCH 1346/1384] =?UTF-8?q?Request=E7=B1=BB=E7=9A=84action/modul?= =?UTF-8?q?e/controller=E6=96=B9=E6=B3=95=E7=9A=84=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8D=95=E7=8B=AC=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/Request.php | 101 +++++++++++++++--------- library/think/facade/Request.php | 50 ++++++------ library/think/route/Rule.php | 6 +- library/think/route/dispatch/Module.php | 8 +- 5 files changed, 98 insertions(+), 69 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 1f2bfd56..61a5c911 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -483,7 +483,7 @@ class App extends Container $this->lang->detect(); } - $this->request->langset($this->lang->range()); + $this->request->setLangset($this->lang->range()); // 加载系统语言包 $this->lang->load([ diff --git a/library/think/Request.php b/library/think/Request.php index 1486de92..c1f21208 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -310,9 +310,9 @@ class Request { $request = new static($config->pull('app')); - $request->cookie($app['cookie']->get()); - $request->server($_SERVER); - $request->env($app['env']->get()); + $request->cookie = $app['cookie']->get(); + $request->server = $_SERVER; + $request->env = $app['env']->get(); return $request; } @@ -922,7 +922,7 @@ class Request * @param array $route 路由变量 * @return void */ - public function setRouteVars(array $name) + public function setRouteVars(array $route) { $this->route = array_merge($this->route, $route); } @@ -1753,67 +1753,94 @@ class Request } /** - * 设置或者获取当前的模块名 + * 设置当前的模块名 * @access public * @param string $module 模块名 - * @return string|Request + * @return $this */ - public function module($module = null) + public function setModule($module) { - if (!is_null($module)) { - $this->module = $module; - return $this; - } - - return $this->module ?: ''; + $this->module = $module; + return $this; } /** - * 设置或者获取当前的控制器名 + * 设置当前的控制器名 * @access public * @param string $controller 控制器名 - * @return string|Request + * @return $this */ - public function controller($controller = null) + public function setController($controller) { - if (!is_null($controller)) { - $this->controller = $controller; - return $this; - } - - return $this->controller ?: ''; + $this->controller = $controller; + return $this; } /** - * 设置或者获取当前的操作名 + * 设置当前的操作名 * @access public * @param string $action 操作名 - * @return string|Request + * @return $this */ - public function action($action = null) + public function setAction($action) { - if (!is_null($action) && !is_bool($action)) { - $this->action = $action; - return $this; - } + $this->action = $action; + return $this; + } + + /** + * 获取当前的模块名 + * @access public + * @return string + */ + public function module() + { + return $this->module ?: ''; + } + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller($convert = false) + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为驼峰 + * @return string + */ + public function action($convert = false) + { $name = $this->action ?: ''; - return true === $action ? $name : strtolower($name); + return $convert ? $name : strtolower($name); } /** - * 设置或者获取当前的语言 + * 设置当前的语言 * @access public * @param string $lang 语言名 - * @return string|Request + * @return $this */ - public function langset($lang = null) + public function setLangset($lang) { - if (!is_null($lang)) { - $this->langset = $lang; - return $this; - } + $this->langset = $lang; + return $this; + } + /** + * 获取当前的语言 + * @access public + * @return string + */ + public function langset() + { return $this->langset ?: ''; } diff --git a/library/think/facade/Request.php b/library/think/facade/Request.php index 50e642fb..d0eedb24 100644 --- a/library/think/facade/Request.php +++ b/library/think/facade/Request.php @@ -18,11 +18,11 @@ use think\Facade; * @mixin \think\Request * @method void hook(mixed $method, mixed $callback = null) static Hook 方法注入 * @method \think\Request create(string $uri, string $method = 'GET', array $params = [], array $cookie = [], array $files = [], array $server = [], string $content = null) static 创建一个URL请求 - * @method mixed domain(string $domain = null) static 设置或获取当前包含协议的域名 - * @method mixed url(mixed $url = null) static 设置或获取当前完整URL - * @method mixed baseUrl(string $url = null) static 设置或获取当前URL - * @method mixed baseFile(string $file = null) static 设置或获取当前执行的文件 - * @method mixed root(string $url = null) static 设置或获取URL访问根地址 + * @method mixed domain(bool $port = false) static 获取当前包含协议、端口的域名 + * @method mixed url(bool $domain = false) static 获取当前完整URL + * @method mixed baseUrl(bool $domain = false) static 获取当前URL + * @method mixed baseFile(bool $domain = false) static 获取当前执行的文件 + * @method mixed root(bool $domain = false) static 获取URL访问根地址 * @method string rootUrl() static 获取URL访问根目录 * @method string pathinfo() static 获取当前请求URL的pathinfo信息(含URL后缀) * @method string path() static 获取当前请求URL的pathinfo信息(不含URL后缀) @@ -40,20 +40,20 @@ use think\Facade; * @method bool isOptions() static 是否为OPTIONS请求 * @method bool isCli() static 是否为cli * @method bool isCgi() static 是否为cgi - * @method mixed param(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取当前请求的参数 - * @method mixed route(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取路由参数 - * @method mixed get(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取GET参数 - * @method mixed post(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取POST参数 - * @method mixed put(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取PUT参数 - * @method mixed delete(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取DELETE参数 - * @method mixed patch(mixed $name = '', mixed $default = null, mixed $filter = '') static 设置获取PATCH参数 - * @method mixed request(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取request变量 - * @method mixed session(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取session数据 - * @method mixed cookie(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取cookie参数 - * @method mixed server(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取server参数 - * @method mixed env(mixed $name = '', mixed $default = null, mixed $filter = '') static 获取环境变量 - * @method mixed file(mixed $name = '') static 获取上传的文件信息 - * @method mixed header(mixed $name = '', mixed $default = null) static 设置或者获取当前的Header + * @method mixed param(string $name = '', mixed $default = null, mixed $filter = '') static 获取当前请求的参数 + * @method mixed route(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取路由参数 + * @method mixed get(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取GET参数 + * @method mixed post(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取POST参数 + * @method mixed put(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取PUT参数 + * @method mixed delete(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取DELETE参数 + * @method mixed patch(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取PATCH参数 + * @method mixed request(string $name = '', mixed $default = null, mixed $filter = '') static 获取request变量 + * @method mixed session(string $name = '', mixed $default = null, mixed $filter = '') static 获取session数据 + * @method mixed cookie(string $name = '', mixed $default = null, mixed $filter = '') static 获取cookie参数 + * @method mixed server(string $name = '', mixed $default = null, mixed $filter = '') static 获取server参数 + * @method mixed env(string $name = '', mixed $default = null, mixed $filter = '') static 获取环境变量 + * @method mixed file(string $name = '') static 获取上传的文件信息 + * @method mixed header(string $name = '', mixed $default = null) static 设置或者获取当前的Header * @method mixed input(array $data,mixed $name = '', mixed $default = null, mixed $filter = '') static 获取变量 支持过滤和默认值 * @method mixed filter(mixed $filter = null) static 设置或获取当前的过滤规则 * @method mixed has(string $name, string $type = 'param', bool $checkEmpty = false) static 是否存在某个请求参数 @@ -71,12 +71,12 @@ use think\Facade; * @method string protocol() static 当前请求 SERVER_PROTOCOL * @method string remotePort() static 当前请求 REMOTE_PORT * @method string contentType() static 当前请求 HTTP_CONTENT_TYPE - * @method array routeInfo(array $route = []) static 获取当前请求的路由信息 - * @method array dispatch(array $dispatch = null) static 设置或者获取当前请求的调度信息 - * @method mixed module(string $module = null) static 设置或者获取当前的模块名 - * @method mixed controller(string $controller = null) static 设置或者获取当前的控制器名 - * @method mixed action(string $action = null) static 设置或者获取当前的操作名 - * @method mixed langset(string $lang = null) static 设置或者获取当前的语言 + * @method array routeInfo() static 获取当前请求的路由信息 + * @method array dispatch() static 获取当前请求的调度信息 + * @method string module() static 获取当前的模块名 + * @method string controller(bool $convert = false) static 获取当前的控制器名 + * @method string action(bool $convert = false) static 获取当前的操作名 + * @method string langset() static 获取当前的语言 * @method string getContent() static 设置或者获取当前请求的content * @method string getInput() static 获取当前请求的php://input * @method string token(string $name = '__token__', mixed $type = 'md5') static 生成请求令牌 diff --git a/library/think/route/Rule.php b/library/think/route/Rule.php index 687ea981..bdb7f1f2 100644 --- a/library/think/route/Rule.php +++ b/library/think/route/Rule.php @@ -809,9 +809,9 @@ abstract class Rule $result = new ControllerDispatch($request, $this, implode('/', $route), $var); - $request->action(array_pop($route)); - $request->controller($route ? array_pop($route) : $this->getConfig('default_controller')); - $request->module($route ? array_pop($route) : $this->getConfig('default_module')); + $request->setAction(array_pop($route)); + $request->setController($route ? array_pop($route) : $this->getConfig('default_controller')); + $request->setModule($route ? array_pop($route) : $this->getConfig('default_module')); return $result; } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 091f2f6e..9f1db055 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -55,7 +55,7 @@ class Module extends Dispatch // 模块初始化 if ($module && $available) { // 初始化模块 - $this->request->module($module); + $this->request->setModule($module); $this->app->init($module); } else { throw new HttpException(404, 'module not exists:' . $module); @@ -72,7 +72,9 @@ class Module extends Dispatch $this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action')); // 设置当前请求的控制器、操作 - $this->request->controller(Loader::parseName($this->controller, 1))->action($this->actionName); + $this->request + ->setController(Loader::parseName($this->controller, 1)) + ->setAction($this->actionName); return $this; } @@ -104,7 +106,7 @@ class Module extends Dispatch $methodName = $reflect->getName(); $suffix = $this->rule->getConfig('action_suffix'); $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; - $this->request->action($actionName); + $this->request->setAction($actionName); // 自动获取请求变量 $vars = $this->rule->getConfig('url_param_type') -- Gitee From 7be62e170d8be1cfc3cade7eb25fbaf43f540dd0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 17:50:12 +0800 Subject: [PATCH 1347/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=85=B3=E8=81=94?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=AF=B9delete=E6=96=B9=E6=B3=95=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Relation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index 315fb28d..165e6030 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -143,7 +143,7 @@ abstract class Relation $result = call_user_func_array([$this->query->getModel(), $method], $args); - return $result === $this->query ? $this : $result; + return $result === $this->query && 'delete' != $method ? $this : $result; } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } -- Gitee From 5a242bbd988f58d5da54dea22e07fae76c96779c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 6 Jun 2018 17:54:25 +0800 Subject: [PATCH 1348/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/Relation.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index 165e6030..b969bca2 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -127,6 +127,19 @@ abstract class Relation } } + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + return $this->query->delete($data); + } + /** * 执行基础查询(仅执行一次) * @access protected @@ -143,7 +156,7 @@ abstract class Relation $result = call_user_func_array([$this->query->getModel(), $method], $args); - return $result === $this->query && 'delete' != $method ? $this : $result; + return $result === $this->query ? $this : $result; } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } -- Gitee From 1d1526258337b7a852ebca25245d0fb21aadf71d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Jun 2018 12:25:50 +0800 Subject: [PATCH 1349/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index c1f21208..60d22d76 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -108,9 +108,9 @@ class Request /** * 当前调度信息 - * @var array + * @var \think\route\Dispatch */ - protected $dispatch = []; + protected $dispatch; /** * 当前模块名 @@ -920,17 +920,18 @@ class Request * 设置路由变量 * @access public * @param array $route 路由变量 - * @return void + * @return $this */ public function setRouteVars(array $route) { $this->route = array_merge($this->route, $route); + return $this; } /** * 获取路由参数 * @access public - * @param mixed $name 变量名 + * @param string|false $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed @@ -1418,6 +1419,10 @@ class Request */ public function has($name, $type = 'param', $checkEmpty = false) { + if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'file', 'session', 'cookie', 'env', 'header', 'route'])) { + return false; + } + if (empty($this->$type)) { $param = $this->$type(); } else { @@ -1670,7 +1675,7 @@ class Request /** * 当前请求 SERVER_PROTOCOL * @access public - * @return integer + * @return string */ public function protocol() { @@ -1714,7 +1719,7 @@ class Request * @param array $route 路由名称 * @return array */ - public function routeInfo($route = []) + public function routeInfo(array $route = []) { if (!empty($route)) { $this->routeInfo = $route; @@ -1726,8 +1731,8 @@ class Request /** * 设置或者获取当前请求的调度信息 * @access public - * @param array $dispatch 调度信息 - * @return array + * @param \think\route\Dispatch $dispatch 调度信息 + * @return \think\route\Dispatch */ public function dispatch($dispatch = null) { @@ -1875,7 +1880,7 @@ class Request * @param mixed $type 令牌生成方法 * @return string */ - public function token($name = '__token__', $type = 'md5') + public function token($name = '__token__', $type = null) { $type = is_callable($type) ? $type : 'md5'; $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); -- Gitee From ec121008e81f2b5c7298758f0db3bff0a2469a9f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Jun 2018 14:27:25 +0800 Subject: [PATCH 1350/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8Cmake=E6=8C=87=E4=BB=A4=E7=94=9F=E6=88=90=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E5=BC=BA=E5=88=B6=E5=B0=8F=E5=86=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 2 +- library/think/console/command/Make.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 61a5c911..4c2e19d1 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App extends Container { - const VERSION = '5.1.15'; + const VERSION = '5.1.16'; /** * 当前模块路径 diff --git a/library/think/console/command/Make.php b/library/think/console/command/Make.php index 3bdfc6b9..7474f266 100644 --- a/library/think/console/command/Make.php +++ b/library/think/console/command/Make.php @@ -46,7 +46,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)); -- Gitee From 94c66cfb5b8a570a7624e06c2f98fb087c222ad5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Jun 2018 15:34:34 +0800 Subject: [PATCH 1351/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=92=8Curl=E7=94=9F=E6=88=90=E5=AF=B9?= =?UTF-8?q?=E5=9F=9F=E5=90=8D=E7=9A=84=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Route.php | 5 +++-- library/think/Url.php | 5 ++++- library/think/route/RuleName.php | 20 ++++++++++++++++++-- library/think/route/dispatch/Url.php | 6 +++++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/library/think/Route.php b/library/think/Route.php index 9fde8927..29e0d33e 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -382,11 +382,12 @@ class Route * 读取路由标识 * @access public * @param string $name 路由标识 + * @param string $domain 域名 * @return mixed */ - public function getName($name = null) + public function getName($name = null, $domain = null) { - return $this->app['rule_name']->get($name); + return $this->app['rule_name']->get($name, $domain); } /** diff --git a/library/think/Url.php b/library/think/Url.php index 32fb9c71..aa27b3e6 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -112,7 +112,10 @@ class Url } if ($url) { - $rule = $this->app['route']->getName(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 = $this->app['route']->getName($url); diff --git a/library/think/route/RuleName.php b/library/think/route/RuleName.php index 408a7c93..11fc1942 100644 --- a/library/think/route/RuleName.php +++ b/library/think/route/RuleName.php @@ -47,9 +47,10 @@ class RuleName * 根据路由标识获取路由信息(用于URL生成) * @access public * @param string $name 路由标识 + * @param string $domain 域名 * @return array|null */ - public function get($name = null) + public function get($name = null, $domain = null) { if (is_null($name)) { return $this->item; @@ -57,7 +58,22 @@ class RuleName $name = strtolower($name); - return isset($this->item[$name]) ? $this->item[$name] : null; + if (isset($this->item[$name])) { + if (is_null($domain)) { + $result = $this->item[$name]; + } else { + $result = []; + foreach ($this->item[$name] as $item) { + if ($item[2] == $domain) { + $result[] = $item; + } + } + } + } else { + $result = null; + } + + return $result; } } diff --git a/library/think/route/dispatch/Url.php b/library/think/route/dispatch/Url.php index 266ff940..90a865b4 100644 --- a/library/think/route/dispatch/Url.php +++ b/library/think/route/dispatch/Url.php @@ -52,6 +52,7 @@ class Url extends Dispatch // 解析模块 $module = $this->rule->getConfig('app_multi_module') ? array_shift($path) : null; + if ($this->param['auto_search']) { $controller = $this->autoFindController($module, $path); } else { @@ -74,6 +75,7 @@ class Url extends Dispatch } $panDomain = $this->request->panDomain(); + if ($panDomain && $key = array_search('*', $var)) { // 泛域名赋值 $var[$key] = $panDomain; @@ -112,7 +114,9 @@ class Url extends Dispatch $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); } - if ($this->rule->getRouter()->getName($name) || $this->rule->getRouter()->getName($name2)) { + $host = $this->request->host(true); + + if ($this->rule->getRouter()->getName($name, $host) || $this->rule->getRouter()->getName($name2, $host)) { return true; } -- Gitee From 0708c81a274371cc73834326543da1e868a9fbba Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Jun 2018 17:39:39 +0800 Subject: [PATCH 1352/1384] =?UTF-8?q?=E6=9B=B4=E6=94=B9http=5Fagent=5Fip?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convention.php b/convention.php index 6373e506..6675a57a 100644 --- a/convention.php +++ b/convention.php @@ -84,7 +84,7 @@ return [ // HTTPS代理标识 'https_agent_name' => '', // IP代理获取标识 - 'http_agent_ip' => 'X-REAL-IP', + 'http_agent_ip' => 'HTTP_X_REAL_IP', // URL伪静态后缀 'url_html_suffix' => 'html', // 域名根,如thinkphp.cn -- Gitee From 2d8ef466a4bac7cc6f4b9031da204bf73a884f5e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Jun 2018 17:57:18 +0800 Subject: [PATCH 1353/1384] =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 60d22d76..1a83d8b6 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -35,7 +35,7 @@ class Request // HTTPS代理标识 'https_agent_name' => '', // IP代理获取标识 - 'http_agent_ip' => 'X-REAL-IP', + 'http_agent_ip' => 'HTTP_X_REAL_IP', // URL伪静态后缀 'url_html_suffix' => 'html', ]; -- Gitee From 771daea86939a22ac50f6318e48288b0a4a2cb4f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 7 Jun 2018 18:56:44 +0800 Subject: [PATCH 1354/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBpos?= =?UTF-8?q?t/put=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 1a83d8b6..2be157af 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -593,7 +593,6 @@ class Request */ public function setRoot($url = null) { - $this->root = $url; return $this; } @@ -902,7 +901,8 @@ class Request } // 当前请求参数和URL地址中的参数合并 - $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->mergeParam = true; } @@ -969,12 +969,7 @@ class Request public function post($name = '', $default = null, $filter = '') { if (empty($this->post)) { - $content = $this->input; - if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { - $this->post = (array) json_decode($content, true); - } else { - $this->post = $_POST; - } + $this->post = !empty($_POST) ? $_POST : $this->getJsonInputData($this->input); } return $this->input($this->post, $name, $default, $filter); @@ -991,17 +986,27 @@ class Request public function put($name = '', $default = null, $filter = '') { if (is_null($this->put)) { - $content = $this->input; - if (false !== strpos($this->contentType(), 'application/json')) { - $this->put = (array) json_decode($content, true); + $data = $this->getJsonInputData($this->input); + + if (!empty($data)) { + $this->put = $data; } else { - parse_str($content, $this->put); + parse_str($this->input, $this->put); } } return $this->input($this->put, $name, $default, $filter); } + protected function getJsonInputData($content) + { + if (false !== strpos($this->contentType(), 'application/json')) { + return (array) json_decode($content, true); + } + + return []; + } + /** * 获取DELETE参数 * @access public -- Gitee From e0a8bdc52957a8f9774353fbfa0b9fd6ee206188 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Jun 2018 12:10:59 +0800 Subject: [PATCH 1355/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=20=E6=A8=A1=E5=9E=8B=E5=A2=9E=E5=8A=A0isForce?= =?UTF-8?q?=E5=92=8CisExists=E6=96=B9=E6=B3=95=20=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84restore=E6=96=B9=E6=B3=95=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC=E6=94=B9=E4=B8=BA=E5=B8=83=E5=B0=94=E5=80=BC=20?= =?UTF-8?q?=E8=BD=AF=E5=88=A0=E9=99=A4=E7=9A=84delete=E6=96=B9=E6=B3=95for?= =?UTF-8?q?ce=E5=8F=82=E6=95=B0=E5=8F=96=E6=B6=88=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8force=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 23 +++++++++++++++- library/think/model/concern/SoftDelete.php | 31 +++++++++++----------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index c0dc9a3d..828894d0 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -354,6 +354,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this; } + /** + * 判断force + * @access public + * @return bool + */ + public function isForce() + { + return $this->force; + } + /** * 新增数据是否使用Replace * @access public @@ -367,7 +377,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } /** - * 新增数据是否使用Replace + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return void + */ + public function exists($exists) + { + $this->exists = $exists; + } + + /** + * 判断数据是否存在数据库 * @access public * @return bool */ diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 3ffb3d6b..fa052945 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -71,18 +71,17 @@ trait SoftDelete /** * 删除当前的记录 * @access public - * @param bool $force 是否强制删除 * @return bool */ - public function delete($force = false) + public function delete() { - if (!$this->exists || false === $this->trigger('before_delete', $this)) { + if (!$this->isExists() || false === $this->trigger('before_delete', $this)) { return false; } $name = $this->getDeleteTimeField(); - if ($name && !$force) { + if ($name && !$this->isForce()) { // 软删除 $this->data($name, $this->autoWriteTimestamp($name)); @@ -104,7 +103,7 @@ trait SoftDelete $this->trigger('after_delete', $this); - $this->exists = false; + $this->exists(false); return true; } @@ -135,7 +134,7 @@ trait SoftDelete if ($resultSet) { foreach ($resultSet as $data) { - $data->delete($force); + $data->force($force)->delete(); } } @@ -146,35 +145,35 @@ trait SoftDelete * 恢复被软删除的记录 * @access public * @param array $where 更新条件 - * @return integer + * @return bool */ public function restore($where = []) { $name = $this->getDeleteTimeField(); - if (empty($where)) { - $pk = $this->getPk(); - - $where[] = [$pk, '=', $this->getData($pk)]; - } - if ($name) { if (false === $this->trigger('before_restore')) { return false; } + if (empty($where)) { + $pk = $this->getPk(); + + $where[] = [$pk, '=', $this->getData($pk)]; + } + // 恢复删除 - $result = $this->db(false) + $this->db(false) ->where($where) ->useSoftDelete($name, $this->getWithTrashedExp()) ->update([$name => $this->defaultSoftDelete]); $this->trigger('after_restore'); - return $result; + return true; } - return 0; + return false; } /** -- Gitee From a72d66387dab1fa55b9b87bfb0887b4c6d2182a7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Jun 2018 12:29:11 +0800 Subject: [PATCH 1356/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84delete=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/concern/SoftDelete.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index fa052945..96f87b36 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -87,7 +87,7 @@ trait SoftDelete $result = $this->isUpdate()->withEvent(false)->save(); - $this->withEvent = true; + $this->withEvent(true); } else { // 读取更新条件 $where = $this->getWhere(); -- Gitee From e17e7575841354f3d2b4dd6430529d00d8ac6c66 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Jun 2018 15:20:54 +0800 Subject: [PATCH 1357/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Count=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 4 ++-- library/think/Request.php | 7 ++++--- library/think/db/Query.php | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 828894d0..7119c143 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -750,8 +750,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function saveAll($dataSet, $replace = true) { - $result = []; - $db = $this->db(false); $db->startTrans(); @@ -762,6 +760,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $auto = true; } + $result = []; + foreach ($dataSet as $key => $data) { if ($this->exists || (!empty($auto) && isset($data[$pk]))) { $result[$key] = self::update($data, [], $this->field); diff --git a/library/think/Request.php b/library/think/Request.php index 2be157af..182793dc 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -761,12 +761,12 @@ class Request /** * 当前的请求类型 * @access public - * @param bool $method true 获取原始请求类型 + * @param bool $origin 是否获取原始请求类型 * @return string */ - public function method($method = false) + public function method($origin = false) { - if (true === $method) { + if ($origin) { // 获取原始请求类型 return $this->isCli() ? 'GET' : $this->server('REQUEST_METHOD'); } elseif (!$this->method) { @@ -910,6 +910,7 @@ class Request // 获取包含文件上传信息的数组 $file = $this->file(); $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 23476a32..c9c68654 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -639,7 +639,7 @@ class Query */ public function count($field = '*') { - if (isset($this->options['group'])) { + if (!empty($this->options['group'])) { // 支持GROUP $options = $this->getOptions(); $subSql = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql(); -- Gitee From b8011389737f4c6ab7f5757b6ea2647bb5d54ac0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 8 Jun 2018 16:56:15 +0800 Subject: [PATCH 1358/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=A4=9A=E5=AF=B9?= =?UTF-8?q?=E5=A4=9Adetach=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 6 +++--- library/think/model/relation/BelongsToMany.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index c9c68654..a9dcdd9f 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -635,7 +635,7 @@ class Query * COUNT查询 * @access public * @param string $field 字段名 - * @return integer|string + * @return float|string */ public function count($field = '*') { @@ -660,7 +660,7 @@ class Query * SUM查询 * @access public * @param string $field 字段名 - * @return float|int + * @return float */ public function sum($field) { @@ -695,7 +695,7 @@ class Query * AVG查询 * @access public * @param string $field 字段名 - * @return float|int + * @return float */ public function avg($field) { diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index a6ff4ef4..02e66669 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -563,7 +563,7 @@ class BelongsToMany extends Relation $pivot[] = [$this->localKey, '=', $this->parent->$pk]; if (isset($id)) { - $pivot[] = is_array($id) ? [$this->foreignKey, 'in', $id] : [$this->foreignKey, '=', $id]; + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; } $result = $this->pivot->where($pivot)->delete(); -- Gitee From 38d866f56564cb5ae8d42659e6693744ff02183c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 9 Jun 2018 20:10:48 +0800 Subject: [PATCH 1359/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBSes?= =?UTF-8?q?sion=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 182793dc..0058da9f 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1068,7 +1068,7 @@ class Request return $this->session; } - return isset($this->session[$name]) ? $this->session[$name] : $default; + return $this->getData($this->session, $name) ?: $default; } /** @@ -1273,14 +1273,10 @@ class Request list($name, $type) = explode('/', $name); } - // 按.拆分成多维数组进行判断 - foreach (explode('.', $name) as $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - // 无输入数据,返回默认值 - return $default; - } + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; } if (is_object($data)) { @@ -1306,6 +1302,26 @@ class Request return $data; } + /** + * 获取数据 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @return mixed + */ + protected function getData(array $data, $name) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return; + } + } + + return $data; + } + /** * 设置或获取当前的过滤规则 * @access public -- Gitee From 3fe5c9b23ba25c38129a5e198e464f446e60a387 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 9 Jun 2018 21:24:20 +0800 Subject: [PATCH 1360/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BLog=E7=B1=BBrecord?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Log.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/Log.php b/library/think/Log.php index 5cc98b00..b63233cd 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -117,7 +117,7 @@ class Log implements LoggerInterface return; } - if (is_string($msg)) { + if (is_string($msg) && !empty($context)) { $replace = []; foreach ($context as $key => $val) { $replace['{' . $key . '}'] = $val; -- Gitee From 9cf3a8a82c91e2e012d0c628d48d434871c3e5ff Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 9 Jun 2018 23:28:32 +0800 Subject: [PATCH 1361/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E4=B8=AD=E9=97=B4=E4=BB=B6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 25 +------ library/think/Controller.php | 8 +++ library/think/Middleware.php | 35 ++++++---- library/think/route/dispatch/Module.php | 87 ++++++++++++++++--------- 4 files changed, 87 insertions(+), 68 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 4c2e19d1..8874c046 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -427,30 +427,7 @@ class App extends Container } $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { - if (is_null($data)) { - try { - // 执行调度 - $data = $dispatch->run(); - } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); - } - } - - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $isAjax = $request->isAjax(); - $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); - - $response = Response::create($data, $type); - } else { - $data = ob_get_clean(); - $status = empty($data) ? 204 : 200; - $response = Response::create($data, '', $status); - } - return $response; + return is_null($data) ? $dispatch->run() : $data; }); $response = $this->middleware->dispatch($this->request); diff --git a/library/think/Controller.php b/library/think/Controller.php index 0220aee4..4b67b528 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -48,6 +48,12 @@ class Controller */ protected $beforeActionList = []; + /** + * 控制器中间件 + * @var array + */ + protected $middleware = []; + /** * 构造方法 * @access public @@ -61,6 +67,8 @@ class Controller // 控制器初始化 $this->initialize(); + $this->app['middleware']->import($this->middleware, 'controller'); + // 前置操作方法 foreach ((array) $this->beforeActionList as $method => $options) { is_numeric($method) ? diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 229ceaa4..1f50f7f3 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -39,17 +39,17 @@ class Middleware $this->config = array_merge($this->config, $config); } - public function import(array $middlewares = []) + public function import(array $middlewares = [], $type = 'route') { foreach ($middlewares as $middleware) { - $this->add($middleware); + $this->add($middleware, $type); } } /** * {@inheritdoc} */ - public function add($middleware) + public function add($middleware, $type = 'route') { if (is_null($middleware)) { return; @@ -58,14 +58,14 @@ class Middleware $middleware = $this->buildMiddleware($middleware); if ($middleware) { - $this->queue[] = $middleware; + $this->queue[$type][] = $middleware; } } /** * {@inheritdoc} */ - public function unshift($middleware) + public function unshift($middleware, $type = 'route') { if (is_null($middleware)) { return; @@ -74,24 +74,24 @@ class Middleware $middleware = $this->buildMiddleware($middleware); if ($middleware) { - array_unshift($this->queue, $middleware); + array_unshift($this->queue[$type], $middleware); } } /** * {@inheritdoc} */ - public function all() + public function all($type = 'route') { - return $this->queue; + return $this->queue[$type] ?: []; } /** * {@inheritdoc} */ - public function dispatch(Request $request) + public function dispatch(Request $request, $type = 'route') { - return call_user_func($this->resolve(), $request); + return call_user_func($this->resolve($type), $request); } protected function buildMiddleware($middleware) @@ -127,10 +127,11 @@ class Middleware return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null]; } - protected function resolve() + protected function resolve($type = 'route') { - return function (Request $request) { - $middleware = array_shift($this->queue); + return function (Request $request) use ($type) { + + $middleware = array_shift($this->queue[$type]); if (null === $middleware) { throw new InvalidArgumentException('The queue was exhausted, with no response returned'); @@ -139,7 +140,7 @@ class Middleware list($call, $param) = $middleware; try { - $response = call_user_func_array($call, [$request, $this->resolve(), $param]); + $response = call_user_func_array($call, [$request, $this->resolve($type), $param]); } catch (HttpResponseException $exception) { $response = $exception->getResponse(); } @@ -152,4 +153,10 @@ class Middleware }; } + public function __call($method, $args) + { + array_push($args, $method); + return call_user_func_array([$this, 'add'], $args); + } + } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 9f1db055..8feee4f2 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -14,7 +14,10 @@ namespace think\route\dispatch; use ReflectionMethod; use think\exception\ClassNotFoundException; use think\exception\HttpException; +use think\exception\HttpResponseException; use think\Loader; +use think\Request; +use think\Response; use think\route\Dispatch; class Module extends Dispatch @@ -84,8 +87,8 @@ class Module extends Dispatch // 监听module_init $this->app['hook']->listen('module_init'); - // 实例化控制器 try { + // 实例化控制器 $instance = $this->app->controller($this->controller, $this->rule->getConfig('url_controller_layer'), $this->rule->getConfig('controller_suffix'), @@ -94,36 +97,60 @@ class Module extends Dispatch throw new HttpException(404, 'controller not exists:' . $e->getClass()); } - // 获取当前操作名 - $action = $this->actionName . $this->rule->getConfig('action_suffix'); - - if (is_callable([$instance, $action])) { - // 执行操作方法 - $call = [$instance, $action]; - - // 严格获取当前操作方法名 - $reflect = new ReflectionMethod($instance, $action); - $methodName = $reflect->getName(); - $suffix = $this->rule->getConfig('action_suffix'); - $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; - $this->request->setAction($actionName); - - // 自动获取请求变量 - $vars = $this->rule->getConfig('url_param_type') - ? $this->request->route() - : $this->request->param(); - } elseif (is_callable([$instance, '_empty'])) { - // 空操作 - $call = [$instance, '_empty']; - $vars = [$this->actionName]; - $reflect = new ReflectionMethod($instance, '_empty'); - } else { - // 操作不存在 - throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); - } + $this->app['middleware']->controller(function (Request $request, $next) use ($instance) { + + try { + // 获取当前操作名 + $action = $this->actionName . $this->rule->getConfig('action_suffix'); + + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + + // 严格获取当前操作方法名 + $reflect = new ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $this->rule->getConfig('action_suffix'); + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $this->request->setAction($actionName); + + // 自动获取请求变量 + $vars = $this->rule->getConfig('url_param_type') + ? $this->request->route() + : $this->request->param(); + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$this->actionName]; + $reflect = new ReflectionMethod($instance, '_empty'); + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $this->app['hook']->listen('action_begin', $call); + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } - $this->app['hook']->listen('action_begin', $call); + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $request->isAjax(); + $type = $isAjax ? $this->app->config('app.default_ajax_return') : $this->app->config('app.default_return_type'); + + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + $status = empty($data) ? 204 : 200; + $response = Response::create($data, '', $status); + } + return $response; + }); - return $this->app->invokeReflectMethod($instance, $reflect, $vars); + return $this->app['middleware']->dispatch($this->request, 'controller'); } } -- Gitee From caaabf8099a4a9068ba353c7c2455f78b33f4cd0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Jun 2018 11:09:25 +0800 Subject: [PATCH 1362/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 23 ++++++- library/think/Controller.php | 5 +- library/think/Middleware.php | 55 ++++++++++++----- library/think/route/dispatch/Module.php | 80 ++++++++++--------------- 4 files changed, 96 insertions(+), 67 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index 8874c046..7682be6d 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -427,7 +427,9 @@ class App extends Container } $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { - return is_null($data) ? $dispatch->run() : $data; + $data = is_null($data) ? $dispatch->run() : $data; + + return $this->autoResponse($data); }); $response = $this->middleware->dispatch($this->request); @@ -438,6 +440,25 @@ class App extends Container return $response; } + public function autoResponse($data) + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $this->request->isAjax(); + $type = $isAjax ? $this->config('default_ajax_return') : $this->config('default_return_type'); + + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + $status = empty($data) ? 204 : 200; + $response = Response::create($data, '', $status); + } + + return $response; + } + protected function getRouteCacheKey() { if ($this->config->get('route_check_cache_key')) { diff --git a/library/think/Controller.php b/library/think/Controller.php index 4b67b528..3f6bfd2f 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -43,7 +43,7 @@ class Controller protected $batchValidate = false; /** - * 前置操作方法列表 + * 前置操作方法列表(即将废弃) * @var array $beforeActionList */ protected $beforeActionList = []; @@ -67,9 +67,10 @@ class Controller // 控制器初始化 $this->initialize(); + // 控制器中间件 $this->app['middleware']->import($this->middleware, 'controller'); - // 前置操作方法 + // 前置操作方法 即将废弃 foreach ((array) $this->beforeActionList as $method => $options) { is_numeric($method) ? $this->beforeAction($options) : diff --git a/library/think/Middleware.php b/library/think/Middleware.php index 1f50f7f3..4814fdb9 100644 --- a/library/think/Middleware.php +++ b/library/think/Middleware.php @@ -39,6 +39,12 @@ class Middleware $this->config = array_merge($this->config, $config); } + /** + * 导入中间件 + * @access public + * @param array $middlewares + * @param string $type 中间件类型 + */ public function import(array $middlewares = [], $type = 'route') { foreach ($middlewares as $middleware) { @@ -47,7 +53,10 @@ class Middleware } /** - * {@inheritdoc} + * 注册中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 */ public function add($middleware, $type = 'route') { @@ -55,7 +64,7 @@ class Middleware return; } - $middleware = $this->buildMiddleware($middleware); + $middleware = $this->buildMiddleware($middleware, $type); if ($middleware) { $this->queue[$type][] = $middleware; @@ -63,7 +72,20 @@ class Middleware } /** - * {@inheritdoc} + * 注册控制器中间件 + * @access public + * @param mixed $middleware + */ + public function controller($middleware) + { + return $this->add($middleware, 'controller'); + } + + /** + * 移除中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 */ public function unshift($middleware, $type = 'route') { @@ -71,7 +93,7 @@ class Middleware return; } - $middleware = $this->buildMiddleware($middleware); + $middleware = $this->buildMiddleware($middleware, $type); if ($middleware) { array_unshift($this->queue[$type], $middleware); @@ -79,7 +101,9 @@ class Middleware } /** - * {@inheritdoc} + * 获取注册的中间件 + * @access public + * @param string $type 中间件类型 */ public function all($type = 'route') { @@ -87,14 +111,23 @@ class Middleware } /** - * {@inheritdoc} + * 中间件调度 + * @access public + * @param Request $request + * @param string $type 中间件类型 */ public function dispatch(Request $request, $type = 'route') { return call_user_func($this->resolve($type), $request); } - protected function buildMiddleware($middleware) + /** + * 解析中间件 + * @access protected + * @param mixed $middleware + * @param string $type 中间件类型 + */ + protected function buildMiddleware($middleware, $type = 'route') { if (is_array($middleware)) { list($middleware, $param) = $middleware; @@ -117,7 +150,7 @@ class Middleware } if (is_array($middleware)) { - return $this->import($middleware); + return $this->import($middleware, $type); } if (strpos($middleware, ':')) { @@ -153,10 +186,4 @@ class Middleware }; } - public function __call($method, $args) - { - array_push($args, $method); - return call_user_func_array([$this, 'add'], $args); - } - } diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index 8feee4f2..b6ede54a 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -14,10 +14,8 @@ namespace think\route\dispatch; use ReflectionMethod; use think\exception\ClassNotFoundException; use think\exception\HttpException; -use think\exception\HttpResponseException; use think\Loader; use think\Request; -use think\Response; use think\route\Dispatch; class Module extends Dispatch @@ -98,57 +96,39 @@ class Module extends Dispatch } $this->app['middleware']->controller(function (Request $request, $next) use ($instance) { - - try { - // 获取当前操作名 - $action = $this->actionName . $this->rule->getConfig('action_suffix'); - - if (is_callable([$instance, $action])) { - // 执行操作方法 - $call = [$instance, $action]; - - // 严格获取当前操作方法名 - $reflect = new ReflectionMethod($instance, $action); - $methodName = $reflect->getName(); - $suffix = $this->rule->getConfig('action_suffix'); - $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; - $this->request->setAction($actionName); - - // 自动获取请求变量 - $vars = $this->rule->getConfig('url_param_type') - ? $this->request->route() - : $this->request->param(); - } elseif (is_callable([$instance, '_empty'])) { - // 空操作 - $call = [$instance, '_empty']; - $vars = [$this->actionName]; - $reflect = new ReflectionMethod($instance, '_empty'); - } else { - // 操作不存在 - throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); - } - - $this->app['hook']->listen('action_begin', $call); - $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); - } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); + // 获取当前操作名 + $action = $this->actionName . $this->rule->getConfig('action_suffix'); + + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + + // 严格获取当前操作方法名 + $reflect = new ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $this->rule->getConfig('action_suffix'); + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $this->request->setAction($actionName); + + // 自动获取请求变量 + $vars = $this->rule->getConfig('url_param_type') + ? $this->request->route() + : $this->request->param(); + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$this->actionName]; + $reflect = new ReflectionMethod($instance, '_empty'); + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); } - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $isAjax = $request->isAjax(); - $type = $isAjax ? $this->app->config('app.default_ajax_return') : $this->app->config('app.default_return_type'); + $this->app['hook']->listen('action_begin', $call); - $response = Response::create($data, $type); - } else { - $data = ob_get_clean(); - $status = empty($data) ? 204 : 200; - $response = Response::create($data, '', $status); - } - return $response; + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + + return $this->app->autoResponse($data); }); return $this->app['middleware']->dispatch($this->request, 'controller'); -- Gitee From 77baf12287c5ed4de2ef8e2c146f468ba53c5847 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Jun 2018 20:53:14 +0800 Subject: [PATCH 1363/1384] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/facade/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/facade/Route.php b/library/think/facade/Route.php index 3e4dcaae..c9f843d9 100644 --- a/library/think/facade/Route.php +++ b/library/think/facade/Route.php @@ -26,7 +26,7 @@ use think\Facade; * @method void setName(string $name) static 批量导入路由标识 * @method void import(array $rules, string $type = '*') static 导入配置文件的路由规则 * @method \think\route\RuleItem rule(string $rule, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由规则 - * @method void rules(string $rules, string $method = '*', array $option = [], array $pattern = []) static 批量注册路由规则 + * @method void rules(array $rules, string $method = '*', array $option = [], array $pattern = []) static 批量注册路由规则 * @method \think\route\RuleGroup group(string|array $name, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由分组 * @method \think\route\RuleItem any(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 * @method \think\route\RuleItem get(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 -- Gitee From dc5f520cab42931a74a43424ab99a889a29f591f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sun, 10 Jun 2018 20:53:25 +0800 Subject: [PATCH 1364/1384] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tpl/default_index.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpl/default_index.tpl b/tpl/default_index.tpl index 740b1928..e5c1363a 100644 --- a/tpl/default_index.tpl +++ b/tpl/default_index.tpl @@ -5,6 +5,6 @@ class Index{$suffix} { public function index() { - return '

:) 2018新年快乐

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

'; + return '

:)

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

'; } } -- Gitee From 612db2d6cabf2dac4618dd13b756535f1fddac01 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 12:12:21 +0800 Subject: [PATCH 1365/1384] =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=E4=B8=AD?= =?UTF-8?q?=E9=97=B4=E4=BB=B6=E5=AE=9A=E4=B9=89=E6=94=AF=E6=8C=81=E8=AE=BE?= =?UTF-8?q?=E7=BD=AEonly=E5=92=8Cexcept=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Controller.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/library/think/Controller.php b/library/think/Controller.php index 3f6bfd2f..19fc8df6 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -68,7 +68,21 @@ class Controller $this->initialize(); // 控制器中间件 - $this->app['middleware']->import($this->middleware, 'controller'); + if ($this->middleware) { + foreach ($this->middleware as $key => $val) { + if (!is_int($key)) { + if (isset($val['only']) && !in_array($this->request->action(), $val['only'])) { + continue; + } elseif (isset($val['except']) && in_array($this->request->action(), $val['except'])) { + continue; + } else { + $val = $key; + } + } + + $this->app['middleware']->controller($val); + } + } // 前置操作方法 即将废弃 foreach ((array) $this->beforeActionList as $method => $options) { -- Gitee From 0c921f589083f92280db6dac8d20ab83cf359b70 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 14:10:07 +0800 Subject: [PATCH 1366/1384] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=B1=BB=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0jsonAssoc=E5=B1=9E=E6=80=A7=E7=94=A8=E4=BA=8E=E5=AE=9A?= =?UTF-8?q?=E4=B9=89json=E6=95=B0=E6=8D=AE=E6=98=AF=E5=90=A6=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=95=B0=E7=BB=84=EF=BC=8C=E9=BB=98=E8=AE=A4=E4=B8=BA?= =?UTF-8?q?false?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/db/Query.php | 8 +++++--- library/think/model/concern/Attribute.php | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index 7119c143..d169c619 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -236,7 +236,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 设置当前模型 确保查询返回模型对象 $query = Db::connect($this->connection, false, $this->query); $query->model($this) - ->json($this->json) + ->json($this->json, $this->jsonAssoc) ->setJsonFieldType($this->jsonType); if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) { diff --git a/library/think/db/Query.php b/library/think/db/Query.php index a9dcdd9f..794f8635 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2124,11 +2124,13 @@ class Query * 设置JSON字段信息 * @access public * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 * @return $this */ - public function json(array $json = []) + public function json(array $json = [], $assoc = false) { - $this->options['json'] = $json; + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; return $this; } @@ -2897,7 +2899,7 @@ class Query protected function resultToModel(&$result, $options = [], $resultSet = false) { if (!empty($options['json'])) { - $this->jsonResult($result, $options['json']); + $this->jsonResult($result, $options['json'], $options['json_assoc']); } $condition = (!$resultSet && isset($options['where']['AND'])) ? $options['where']['AND'] : null; diff --git a/library/think/model/concern/Attribute.php b/library/think/model/concern/Attribute.php index 2ff0a6ea..c65c557b 100644 --- a/library/think/model/concern/Attribute.php +++ b/library/think/model/concern/Attribute.php @@ -36,6 +36,12 @@ trait Attribute */ protected $json = []; + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + /** * JSON数据表字段类型 * @var array -- Gitee From 7df61b50ddeaec5c081af7d1f5be547f6bdfdfe8 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 14:36:38 +0800 Subject: [PATCH 1367/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3cli=E4=B8=8B?= =?UTF-8?q?=E9=9D=A2=E7=9A=84=E4=B8=80=E5=A4=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/think/App.php b/library/think/App.php index 7682be6d..e1a7a9a4 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -452,6 +452,7 @@ class App extends Container $response = Response::create($data, $type); } else { $data = ob_get_clean(); + $data = false === $data ? '' : $data; $status = empty($data) ? 204 : 200; $response = Response::create($data, '', $status); } -- Gitee From 019a99cb9c973ad3035b5f490c5a0f82dbfa255c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 17:18:34 +0800 Subject: [PATCH 1368/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Request=E7=B1=BBmet?= =?UTF-8?q?hod=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/App.php | 25 +------------------------ library/think/Request.php | 5 +++-- library/think/route/Dispatch.php | 24 +++++++++++++++++++++++- library/think/route/dispatch/Module.php | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/library/think/App.php b/library/think/App.php index e1a7a9a4..14503665 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -333,7 +333,6 @@ class App extends Container // 对容器中的对象实例进行配置更新 $this->containerConfigUpdate($module); } - } protected function containerConfigUpdate($module) @@ -427,9 +426,7 @@ class App extends Container } $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { - $data = is_null($data) ? $dispatch->run() : $data; - - return $this->autoResponse($data); + return is_null($data) ? $dispatch->run() : $data; }); $response = $this->middleware->dispatch($this->request); @@ -440,26 +437,6 @@ class App extends Container return $response; } - public function autoResponse($data) - { - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $isAjax = $this->request->isAjax(); - $type = $isAjax ? $this->config('default_ajax_return') : $this->config('default_return_type'); - - $response = Response::create($data, $type); - } else { - $data = ob_get_clean(); - $data = false === $data ? '' : $data; - $status = empty($data) ? 204 : 200; - $response = Response::create($data, '', $status); - } - - return $response; - } - protected function getRouteCacheKey() { if ($this->config->get('route_check_cache_key')) { diff --git a/library/think/Request.php b/library/think/Request.php index 0058da9f..375aad30 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -771,8 +771,9 @@ class Request return $this->isCli() ? 'GET' : $this->server('REQUEST_METHOD'); } elseif (!$this->method) { if (isset($_POST[$this->config['var_method']])) { - $this->method = strtoupper($_POST[$this->config['var_method']]); - $this->{$this->method}($_POST); + $this->method = strtoupper($_POST[$this->config['var_method']]); + $method = strtolower($this->method); + $this->{$method} = $_POST; } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); } else { diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 27a56d46..7b9385e5 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -162,7 +162,29 @@ abstract class Dispatch $this->autoValidate($option['validate']); } - return $this->exec(); + $data = $this->exec(); + + return $this->autoResponse($data); + } + + protected function autoResponse($data) + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $this->request->isAjax(); + $type = $isAjax ? $this->rule->getConfig('default_ajax_return') : $this->rule->getConfig('default_return_type'); + + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + $data = false === $data ? '' : $data; + $status = empty($data) ? 204 : 200; + $response = Response::create($data, '', $status); + } + + return $response; } /** diff --git a/library/think/route/dispatch/Module.php b/library/think/route/dispatch/Module.php index b6ede54a..4224b362 100644 --- a/library/think/route/dispatch/Module.php +++ b/library/think/route/dispatch/Module.php @@ -128,7 +128,7 @@ class Module extends Dispatch $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); - return $this->app->autoResponse($data); + return $this->autoResponse($data); }); return $this->app['middleware']->dispatch($this->request, 'controller'); -- Gitee From 0d17dccab7cd9edeb83be11f469d257c3d038b9c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 17:54:57 +0800 Subject: [PATCH 1369/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=9D=99=E6=80=81?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=9A=84=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleItem.php b/library/think/route/RuleItem.php index 31b58408..1fd98783 100644 --- a/library/think/route/RuleItem.php +++ b/library/think/route/RuleItem.php @@ -242,7 +242,7 @@ class RuleItem extends Rule } if (false === strpos($rule, '<')) { - if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) { return $var; } return false; -- Gitee From d090478d4695fec765233b4fc8b7aa831ea055f7 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 18:08:36 +0800 Subject: [PATCH 1370/1384] =?UTF-8?q?=E5=88=86=E7=BB=84=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E8=87=AA=E5=8A=A8=E5=AE=8C=E6=95=B4=E5=8C=B9?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index fc1ed612..621db0e5 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -414,6 +414,11 @@ class RuleGroup extends Rule $method = strtolower($method); + if ('/' == $rule) { + // 首页自动完整匹配 + $rule .= '$'; + } + // 创建路由规则实例 $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern); -- Gitee From 6430384c7e139c9e970a05ccdfc58f00124b5aae Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 18:26:52 +0800 Subject: [PATCH 1371/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9Bsqlsrv=E7=9A=84colu?= =?UTF-8?q?mn=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/connector/Sqlsrv.php | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/library/think/db/connector/Sqlsrv.php b/library/think/db/connector/Sqlsrv.php index 4b541709..adefbb35 100644 --- a/library/think/db/connector/Sqlsrv.php +++ b/library/think/db/connector/Sqlsrv.php @@ -125,6 +125,100 @@ class Sqlsrv extends Connection 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 -- Gitee From 0fd3dac47fa3a920a73e58044f81e239821aa26c Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 18:35:21 +0800 Subject: [PATCH 1372/1384] =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=B1=BB=E7=9A=84a?= =?UTF-8?q?part=5Flevel=E9=85=8D=E7=BD=AE=E5=A6=82=E6=9E=9C=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BAtrue=20=E5=88=99=E8=87=AA=E5=8A=A8=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=AF=B9=E5=BA=94=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/log/driver/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index ff322590..dfd963c2 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -72,7 +72,7 @@ class File $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; } - if (!$this->config['json'] && in_array($type, $this->config['apart_level'])) { + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { // 独立记录的日志级别 $filename = $this->getApartLevelFile($path, $type); -- Gitee From 320866f736bec2daeffc6f1c76053f045f1ea7fe Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 11 Jun 2018 18:43:51 +0800 Subject: [PATCH 1373/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/RuleGroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/RuleGroup.php b/library/think/route/RuleGroup.php index 621db0e5..19d077db 100644 --- a/library/think/route/RuleGroup.php +++ b/library/think/route/RuleGroup.php @@ -414,7 +414,7 @@ class RuleGroup extends Rule $method = strtolower($method); - if ('/' == $rule) { + if ('/' === $rule || '' === $rule) { // 首页自动完整匹配 $rule .= '$'; } -- Gitee From 3846e0e48c496065e2dcb2469836c09aec35a273 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Jun 2018 07:20:33 +0800 Subject: [PATCH 1374/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B204=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/route/Dispatch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/route/Dispatch.php b/library/think/route/Dispatch.php index 7b9385e5..7f1982cd 100644 --- a/library/think/route/Dispatch.php +++ b/library/think/route/Dispatch.php @@ -180,7 +180,7 @@ abstract class Dispatch } else { $data = ob_get_clean(); $data = false === $data ? '' : $data; - $status = empty($data) ? 204 : 200; + $status = '' === $data ? 204 : 200; $response = Response::create($data, '', $status); } -- Gitee From da914116384e31b76498ff4d553b29855c3fbd5a Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Jun 2018 11:17:12 +0800 Subject: [PATCH 1375/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBses?= =?UTF-8?q?sion=E6=96=B9=E6=B3=95=E8=BF=94=E5=9B=9E=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/think/Request.php b/library/think/Request.php index 375aad30..03458e79 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1069,7 +1069,9 @@ class Request return $this->session; } - return $this->getData($this->session, $name) ?: $default; + $data = $this->getData($this->session, $name); + + return is_null($data) ? $default : $data; } /** -- Gitee From 62243ee48eb2c8739fc1a87573ab14f5479f0230 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Jun 2018 14:34:57 +0800 Subject: [PATCH 1376/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BSession=E7=B1=BBhas?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Session.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/library/think/Session.php b/library/think/Session.php index 89e339b2..e700f0e4 100644 --- a/library/think/Session.php +++ b/library/think/Session.php @@ -478,16 +478,21 @@ class Session public function has($name, $prefix = null) { empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; - if (strpos($name, '.')) { - // 支持数组 - list($name1, $name2) = explode('.', $name); + $name = explode('.', $name); - return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); - } else { - return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); + foreach ($name as $val) { + if (!isset($value[$val])) { + return false; + } else { + $value = $value[$val]; + } } + + return true; } /** -- Gitee From 033bc4edaac4d4d8dfefd1dbb318ab6a2f12b8e0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Jun 2018 15:22:55 +0800 Subject: [PATCH 1377/1384] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=B1=BB=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=9B=B4=E9=AB=98=E6=95=88=E7=9A=84ctype=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Validate.php | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/library/think/Validate.php b/library/think/Validate.php index bddbc69d..de46956c 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -112,13 +112,24 @@ class Validate */ protected $currentScene = null; + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + /** * 内置正则验证规则 * @var array */ protected $regex = [ - 'alpha' => '/^[A-Za-z]+$/', - 'alphaNum' => '/^[A-Za-z0-9]+$/', 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', @@ -129,19 +140,6 @@ class Validate 'zip' => '/\d{6}/', ]; - /** - * Filter_var 规则 - * @var array - */ - protected $filter = [ - 'email' => FILTER_VALIDATE_EMAIL, - 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], - 'integer' => FILTER_VALIDATE_INT, - 'url' => FILTER_VALIDATE_URL, - 'macAddr' => FILTER_VALIDATE_MAC, - 'float' => FILTER_VALIDATE_FLOAT, - ]; - /** * 验证场景定义 * @var array @@ -751,6 +749,9 @@ class Validate case 'number': $result = ctype_digit((string) $value); break; + case 'alphaNum': + $result = ctype_alnum($value); + break; case 'array': // 是否为数组 $result = is_array($value); @@ -768,6 +769,10 @@ class Validate if (isset(self::$type[$rule])) { // 注册的验证规则 $result = call_user_func_array(self::$type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); } elseif (isset($this->filter[$rule])) { // Filter_var验证规则 $result = $this->filter($value, $this->filter[$rule]); -- Gitee From ba8e992754959bfeaa8b70f9b439851fcf3db468 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Jun 2018 16:28:43 +0800 Subject: [PATCH 1378/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BRequest=E7=B1=BBcoo?= =?UTF-8?q?kie=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 03458e79..93fbb95b 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -11,6 +11,9 @@ namespace think; +use think\facade\Cookie; +use think\facade\Session; + class Request { /** @@ -310,7 +313,6 @@ class Request { $request = new static($config->pull('app')); - $request->cookie = $app['cookie']->get(); $request->server = $_SERVER; $request->env = $app['env']->get(); @@ -1062,7 +1064,7 @@ class Request public function session($name = '', $default = null) { if (empty($this->session)) { - $this->session = facade\Session::get(); + $this->session = Session::get(); } if ('' === $name) { @@ -1084,8 +1086,12 @@ class Request */ public function cookie($name = '', $default = null, $filter = '') { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + if (!empty($name)) { - $data = isset($this->cookie[$name]) ? $this->cookie[$name] : $default; + $data = Cookie::has($name) ? Cookie::get($name) : $default; } else { $data = $this->cookie; } -- Gitee From 1f496dd13bfa48f2437239b6a8989c488e36bfc5 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 13 Jun 2018 15:42:23 +0800 Subject: [PATCH 1379/1384] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Model.php | 2 +- library/think/model/concern/SoftDelete.php | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/library/think/Model.php b/library/think/Model.php index d169c619..98d29eb4 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -284,7 +284,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $query = $this->buildQuery(); // 软删除 - if (method_exists($this, 'withNoTrashed')) { + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { $this->withNoTrashed($query); } diff --git a/library/think/model/concern/SoftDelete.php b/library/think/model/concern/SoftDelete.php index 96f87b36..9091377d 100644 --- a/library/think/model/concern/SoftDelete.php +++ b/library/think/model/concern/SoftDelete.php @@ -10,6 +10,12 @@ use think\db\Query; trait SoftDelete { + /** + * 是否包含软删除数据 + * @var bool + */ + protected $withTrashed = false; + /** * 判断当前实例是否被软删除 * @access public @@ -35,7 +41,19 @@ trait SoftDelete { $model = new static(); - return $model->db(false); + return $model->withTrashedData(true)->db(false); + } + + /** + * 是否包含软删除数据 + * @access protected + * @param bool $withTrashed 是否包含软删除数据 + * @return $this + */ + protected function withTrashedData($withTrashed) + { + $this->withTrashed = $withTrashed; + return $this; } /** @@ -50,7 +68,7 @@ trait SoftDelete if ($field) { return $model - ->db(false) + ->db(false, false) ->useSoftDelete($field, $model->getWithTrashedExp()); } -- Gitee From 0fff2b76696be82692406d6d1758f2d4847caf2b Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 14 Jun 2018 14:41:12 +0800 Subject: [PATCH 1380/1384] =?UTF-8?q?Query=E7=B1=BB=E7=9A=84readMaster?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=94=B9=E4=B8=BAprotected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 794f8635..4ba0d817 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -87,7 +87,7 @@ class Query * 读取主库的表 * @var array */ - private static $readMaster = []; + protected static $readMaster = []; /** * 日期查询表达式 -- Gitee From 9849f4047b403e6da6c85f850e23c7559c87d02f Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Jun 2018 20:06:13 +0800 Subject: [PATCH 1381/1384] =?UTF-8?q?=E5=A2=9E=E5=8A=A0whereBetweenTimeFie?= =?UTF-8?q?ld=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Query.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 4ba0d817..e0f9a8ac 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -2249,6 +2249,33 @@ class Query return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); } + /** + * 查询当前时间在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + /** * 查询日期或者时间范围 * @access public -- Gitee From 1cf61ed06a4ceb82cfbf61b565db91e0e11464c9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Jun 2018 20:24:28 +0800 Subject: [PATCH 1382/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9BColumn=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=9A=84=E7=BC=93=E5=AD=98=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/db/Connection.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 67a4958f..72722362 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -1340,11 +1340,8 @@ abstract class Connection 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); + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $guid); if (false !== $result) { return $result; -- Gitee From 356f91a433889e2177d765592080ac9321606b4d Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 15 Jun 2018 22:00:28 +0800 Subject: [PATCH 1383/1384] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=A4=9A=E6=80=81?= =?UTF-8?q?=E4=B8=80=E5=AF=B9=E5=A4=9A=E7=9A=84=E5=85=B3=E8=81=94=E9=A2=84?= =?UTF-8?q?=E8=BD=BD=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/model/relation/MorphTo.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 17315c88..bb7c4d0b 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -198,14 +198,14 @@ class MorphTo extends Relation if ($key == $result->$morphType) { // 关联模型 if (!isset($data[$result->$morphKey])) { - throw new Exception('relation data not exists :' . $this->model); + $relationModel = null; } else { $relationModel = $data[$result->$morphKey]; $relationModel->setParent(clone $result); $relationModel->isUpdate(true); - - $result->setRelation($attr, $relationModel); } + + $result->setRelation($attr, $relationModel); } } } -- Gitee From b1b29ef80ca479d12850049230a0f512ab1cc0d4 Mon Sep 17 00:00:00 2001 From: solomon_zjf Date: Wed, 20 Jun 2018 13:25:45 +0800 Subject: [PATCH 1384/1384] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=87=86=E7=A1=AE?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Request.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/think/Request.php b/library/think/Request.php index 93fbb95b..c45b978e 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -733,9 +733,9 @@ class Request } foreach ($this->mimeType as $key => $val) { - $array = explode(',', $val); - foreach ($array as $k => $v) { - if (stristr($accept, $v)) { + $array = array_flip(explode(',', $val)); + foreach ($accept as $v) { + if (isset($array[$v])) { return $key; } } -- Gitee

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 diff --git a/tests/extensions/7.0/apcu.so b/tests/extensions/7.0/apcu.so deleted file mode 100755 index c2f0ad1402d0e709af094ee60ff617c1f0a34bd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 320965 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≶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 E4+;+smjD0& diff --git a/tests/extensions/7.0/memcached.so b/tests/extensions/7.0/memcached.so deleted file mode 100755 index 544ba6d52ee89046ac2ddab0d4acfe2d95e8bfda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323764 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 h1~w&V?BEM#C@~958OzT3EJU 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