diff --git a/thinkphp/README.md b/thinkphp/README.md index 5da1eef6a..52c34a4bf 100644 --- a/thinkphp/README.md +++ b/thinkphp/README.md @@ -1,12 +1,11 @@ -ThinkPHP 5.1 +![](http://www.thinkphp.cn/Uploads/editor/2016-06-23/576b4732a6e04.png) + +ThinkPHP 5.1 —— 12载初心,你值得信赖的PHP框架 =============== -[![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对底层架构做了进一步的改进,减少依赖,其主要特性包括: @@ -30,12 +29,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) ## 命名规范 diff --git a/thinkphp/base.php b/thinkphp/base.php index fc8b7b130..aad321d6a 100644 --- a/thinkphp/base.php +++ b/thinkphp/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, @@ -47,30 +48,31 @@ Container::getInstance()->bind([ 'url' => Url::class, 'validate' => Validate::class, 'view' => View::class, - 'middlewareDispatcher' => http\middleware\Dispatcher::class, + 'rule_name' => route\RuleName::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/thinkphp/convention.php b/thinkphp/convention.php index 01046c4f5..4cae2c34c 100644 --- a/thinkphp/convention.php +++ b/thinkphp/convention.php @@ -30,7 +30,7 @@ return [ // 默认JSONP处理方法 'var_jsonp_handler' => 'callback', // 默认时区 - 'default_timezone' => 'PRC', + 'default_timezone' => 'Asia/Shanghai', // 是否开启多语言 'lang_switch_on' => false, // 默认全局过滤方法 用逗号分隔多个 @@ -89,6 +89,8 @@ return [ 'url_lazy_route' => false, // 是否强制使用路由 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, // 路由是否完全匹配 'route_complete_match' => false, // 使用注解路由 @@ -289,4 +291,10 @@ return [ 'list_rows' => 15, ], + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + ], ]; diff --git a/thinkphp/helper.php b/thinkphp/helper.php index 461460ea3..18ade67df 100644 --- a/thinkphp/helper.php +++ b/thinkphp/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; @@ -370,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); @@ -653,11 +654,15 @@ 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) { + if ('' === $template) { + $template = Loader::parseName(request()->action(true)); + } + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); } } diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php index dd8084d16..16b1bb7ce 100644 --- a/thinkphp/lang/zh-cn.php +++ b/thinkphp/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' => '类不存在', @@ -32,7 +33,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 规范错误', @@ -66,6 +67,8 @@ return [ 'relation data not exists' => '关联数据不存在', 'relation not support' => '关联不支持', 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', // 上传错误信息 'unknown upload error' => '未知上传错误!', @@ -83,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/thinkphp/library/think/App.php b/thinkphp/library/think/App.php index 8892585c8..ae87e5973 100644 --- a/thinkphp/library/think/App.php +++ b/thinkphp/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'; /** * 当前模块路径 @@ -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, ]); // 加载环境变量配置文件 @@ -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'); } @@ -252,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); + } } // 加载公共文件 @@ -263,11 +269,21 @@ 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 . 'provider.php')) { - $this->container->bind(include $path . 'provider.php'); + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->container->bind($provider); + } } // 自动读取配置文件 @@ -298,10 +314,10 @@ class App implements \ArrayAccess */ public function run() { - // 初始化应用 - $this->initialize(); - try { + // 初始化应用 + $this->initialize(); + if ($this->bind) { // 模块/控制器绑定 $this->route->bind($this->bind); @@ -313,28 +329,18 @@ 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'); // 获取应用调度信息 $dispatch = $this->dispatch; if (empty($dispatch)) { - // 进行URL路由检测 + // 路由检测 + $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(); } @@ -358,14 +364,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->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; @@ -381,7 +395,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); @@ -389,6 +403,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' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + ]); + } + /** * 设置当前请求的调度信息 * @access public @@ -408,9 +440,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); } /** @@ -574,9 +606,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/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php index ed3ffe43d..6de014023 100644 --- a/thinkphp/library/think/Build.php +++ b/thinkphp/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) { @@ -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 . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer); } $filename = $this->app->getRuntimePath() . 'build_route.php'; @@ -218,25 +215,61 @@ class Build } /** - * 生成控制器类的路由规则 + * 生成子目录控制器类的路由规则 * @access protected + * @param string $path 控制器目录 * @param string $namespace 应用命名空间 * @param string $module 模块 - * @param string $controller 控制器名 * @param bool $suffix 类库后缀 * @param string $layer 控制器层目录名 * @return string */ - protected function getControllerRoute($namespace, $module, $controller, $alias = false, $layer = '') + protected function buildDirRoute($path, $namespace, $module, $suffix, $layer) + { + $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); + } + + $subDir = glob($path . '*', GLOB_ONLYDIR); + + foreach ($subDir as $dir) { + $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir)); + } + + return $content; + } + + /** + * 生成控制器类的路由规则 + * @access protected + * @param string $class 控制器完整类名 + * @param string $module 模块名 + * @param string $controller 控制器名 + * @return string + */ + protected function getControllerRoute($class, $module, $controller) { - $class = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller); $content = ''; $comment = $class->getDocComment(); - if ($alias) { - $controller = substr($controller, 0, -10); - } - if (false !== strpos($comment, '@route(')) { $comment = $this->parseRouteComment($comment); $route = $module . '/' . $controller; diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php index e30471a5e..603c655f4 100644 --- a/thinkphp/library/think/Config.php +++ b/thinkphp/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; } /** @@ -90,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(); } @@ -117,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; } /** diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php index 5cb1ccde7..8782aa92e 100644 --- a/thinkphp/library/think/Console.php +++ b/thinkphp/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", @@ -48,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(); @@ -61,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'; diff --git a/thinkphp/library/think/Container.php b/thinkphp/library/think/Container.php index b934cb346..b138e6003 100644 --- a/thinkphp/library/think/Container.php +++ b/thinkphp/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 { @@ -76,6 +78,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 @@ -155,137 +178,184 @@ class Container } if (isset($this->instances[$abstract]) && !$newInstance) { - $object = $this->instances[$abstract]; - } else { - if (isset($this->bind[$abstract])) { - $concrete = $this->bind[$abstract]; + return $this->instances[$abstract]; + } - if ($concrete instanceof Closure) { - $object = $this->invokeFunction($concrete, $vars); - } else { - $object = $this->make($concrete, $vars, $newInstance); - } + 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; } + /** + * 删除容器中的对象实例 + * @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 - * @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('function 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); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } catch (ReflectionException $e) { + throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()'); + } + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, $vars = []) + { $args = $this->bindParams($reflect, $vars); - return $reflect->invokeArgs(isset($class) ? $class : null, $args); + return $reflect->invokeArgs($instance, $args); } /** * 调用反射执行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); + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists: ' . $class, $class); } - - return $reflect->newInstanceArgs($args); } /** * 绑定参数 * @access protected * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 + * @param array $vars 参数 * @return array */ protected function bindParams($reflect, $vars = []) { - $args = []; + if ($reflect->getNumberOfParameters() == 0) { + return []; + } - if ($reflect->getNumberOfParameters() > 0) { - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; - $params = $reflect->getParameters(); + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); - foreach ($params as $param) { - $name = $param->getName(); - $class = $param->getClass(); + 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 ($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/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php index cc3953d24..28909c89f 100644 --- a/thinkphp/library/think/Controller.php +++ b/thinkphp/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); } } @@ -120,6 +118,10 @@ class Controller */ protected function fetch($template = '', $vars = [], $config = []) { + if ('' === $template) { + $template = Loader::parseName($this->request->action(true)); + } + return $this->view->fetch($template, $vars, $config); } @@ -232,11 +234,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/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php index 21770e081..6ce22f391 100644 --- a/thinkphp/library/think/Db.php +++ b/thinkphp/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/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php index 8a384e147..75003770b 100644 --- a/thinkphp/library/think/Debug.php +++ b/thinkphp/library/think/Debug.php @@ -223,9 +223,8 @@ class Debug if ($echo) { echo($output); return; - } else { - return $output; } + return $output; } public function inject(Response $response, &$content) diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php index 2c41794ff..ef5d9468d 100644 --- a/thinkphp/library/think/Env.php +++ b/thinkphp/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 (!isset($this->data[$name])) { - $this->data[$name] = $result; - } - - return $result; - } else { + if (false === $result) { return $default; } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; } /** diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php index c00a36a65..b8c8fc585 100644 --- a/thinkphp/library/think/Error.php +++ b/thinkphp/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/thinkphp/library/think/Facade.php b/thinkphp/library/think/Facade.php index 2cda381c7..c455f6626 100644 --- a/thinkphp/library/think/Facade.php +++ b/thinkphp/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); } diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php index 00e83a58b..e9985690c 100644 --- a/thinkphp/library/think/File.php +++ b/thinkphp/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') . DIRECTORY_SEPARATOR . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + } + } + } + + return $savename; + } + /** * 获取错误代码信息 - * @access public + * @access private * @param int $errorNo 错误号 */ private function error($errorNo) diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php index 30b673073..b82fb5a09 100644 --- a/thinkphp/library/think/Hook.php +++ b/thinkphp/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/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php index ba4f700ab..2168c13a5 100644 --- a/thinkphp/library/think/Lang.php +++ b/thinkphp/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/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php index 76fbde936..168ed1f86 100644 --- a/thinkphp/library/think/Loader.php +++ b/thinkphp/library/think/Loader.php @@ -58,29 +58,44 @@ class Loader // 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - // 注册命名空间定义 - self::addNamespace([ - 'think' => __DIR__ . '/', - 'traits' => __DIR__ . '/../traits/', - ]); + $path = realpath(dirname($_SERVER['SCRIPT_FILENAME'])); - $path = dirname($_SERVER['SCRIPT_FILENAME']); - if (is_file('./think')) { - $rootPath = realpath($path) . '/'; + if ('cli-server' == PHP_SAPI || !is_file('./think')) { + $rootPath = dirname($path) . DIRECTORY_SEPARATOR; } else { - $rootPath = realpath($path . '/../') . '/'; + $rootPath = $path . DIRECTORY_SEPARATOR; } - // 加载类库映射文件 - if (is_file($rootPath . 'runtime/classmap.php')) { - self::addClassMap(__include_file($rootPath . 'runtime/classmap.php')); - } - - self::$composerPath = $rootPath . 'vendor/composer/'; + self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; // 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 = 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); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => __DIR__, + 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', + ]); + + // 加载类库映射文件 + if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); } // 自动加载extend目录 @@ -346,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/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php index 49258141b..0e8248dfd 100644 --- a/thinkphp/library/think/Log.php +++ b/thinkphp/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,41 +197,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 (!$this->check($this->config)) { - // 检测日志写入权限 - return false; - } - - 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]; - } - } - } - - $result = $this->driver->save($log); - if ($result) { - $this->log = []; - } - - return $result; + if (empty($this->log) || !$this->allowWrite) { + return true; } - return true; + if (is_null($this->driver)) { + $this->init($this->app['config']->pull('log')); + } + + if (!$this->check($this->config)) { + // 检测日志写入权限 + return false; + } + + 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]; + } + } + } + + $result = $this->driver->save($log); + if ($result) { + $this->log = []; + } + + return $result; } /** diff --git a/thinkphp/library/think/http/middleware/Dispatcher.php b/thinkphp/library/think/Middleware.php similarity index 52% rename from thinkphp/library/think/http/middleware/Dispatcher.php rename to thinkphp/library/think/Middleware.php index 4218915db..b59b72375 100644 --- a/thinkphp/library/think/http/middleware/Dispatcher.php +++ b/thinkphp/library/think/Middleware.php @@ -9,18 +9,17 @@ // | Author: Slince // +---------------------------------------------------------------------- -namespace think\http\middleware; +namespace think; -use think\Request; -use think\Response; - -class Dispatcher implements DispatcherInterface +class Middleware { - protected $queue; + protected $queue = []; - public function __construct($middlewares = []) + public function import(array $middlewares = []) { - $this->queue = (array) $middlewares; + foreach ($middlewares as $middleware) { + $this->add($middleware); + } } /** @@ -28,16 +27,26 @@ class Dispatcher implements DispatcherInterface */ public function add($middleware) { - $this->assertValid($middleware); + if (is_null($middleware)) { + return; + } + + $middleware = $this->buildMiddleware($middleware); + $this->queue[] = $middleware; } /** * {@inheritdoc} */ - public function insert($middleware) + public function unshift($middleware) { - $this->assertValid($middleware); + if (is_null($middleware)) { + return; + } + + $middleware = $this->buildMiddleware($middleware); + array_unshift($this->queue, $middleware); } @@ -54,8 +63,30 @@ 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 buildMiddleware($middleware) + { + if (is_array($middleware)) { + list($middleware, $param) = $middleware; + } + + if ($middleware instanceof \Closure) { + return [$middleware, null]; + } + + if (!is_string($middleware)) { + throw new \InvalidArgumentException('The middleware is invalid'); + } + + $class = false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware; + + if (strpos($class, ':')) { + list($class, $param) = explode(':', $class, 2); + } + + return [[Container::get($class), 'handle'], isset($param) ? $param : null]; } protected function resolve() @@ -64,7 +95,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'); @@ -72,15 +105,9 @@ 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'); } }; } - protected function assertValid($middleware) - { - if (!is_callable($middleware)) { - throw new \InvalidArgumentException('The middleware is invalid'); - } - } } diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php index 3b673464e..433e6d096 100644 --- a/thinkphp/library/think/Model.php +++ b/thinkphp/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 = []) @@ -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]); } } } @@ -861,13 +859,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 +947,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/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php index 3fccdf9e6..e2f9b898f 100644 --- a/thinkphp/library/think/Paginator.php +++ b/thinkphp/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 */ @@ -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(); diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php index 1868c5f40..66b88ce9c 100644 --- a/thinkphp/library/think/Request.php +++ b/thinkphp/library/think/Request.php @@ -251,6 +251,12 @@ class Request */ protected $isCheckCache; + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + /** * 架构函数 * @access public @@ -279,9 +285,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); } /** @@ -403,10 +409,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() { @@ -437,10 +461,10 @@ class Request { if (is_null($domain)) { return $this->panDomain; - } else { - $this->panDomain = $domain; - return $this; } + + $this->panDomain = $domain; + return $this; } /** @@ -592,7 +616,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; @@ -1081,37 +1105,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; @@ -1125,6 +1124,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 @@ -1214,6 +1253,7 @@ class Request } else { $type = 's'; } + // 按.拆分成多维数组进行判断 foreach (explode('.', $name) as $val) { if (isset($data[$val])) { @@ -1223,6 +1263,7 @@ class Request return $default; } } + if (is_object($data)) { return $data; } @@ -1256,9 +1297,9 @@ class Request { if (is_null($filter)) { return $this->filter; - } else { - $this->filter = $filter; } + + $this->filter = $filter; } protected function getFilter($filter, $default) @@ -1478,9 +1519,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 +1536,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 +1596,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 +1696,9 @@ class Request { if (!empty($route)) { $this->routeInfo = $route; - } else { - return $this->routeInfo; } + + return $this->routeInfo; } /** @@ -1675,6 +1716,20 @@ class Request return $this->dispatch; } + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey() + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); + } + + return $this->secureKey; + } + /** * 设置或者获取当前的模块名 * @access public @@ -1686,9 +1741,9 @@ class Request if (!is_null($module)) { $this->module = $module; return $this; - } else { - return $this->module ?: ''; } + + return $this->module ?: ''; } /** @@ -1702,9 +1757,9 @@ class Request if (!is_null($controller)) { $this->controller = $controller; return $this; - } else { - return $this->controller ?: ''; } + + return $this->controller ?: ''; } /** @@ -1715,12 +1770,13 @@ class Request */ public function action($action = null) { - if (!is_null($action)) { + if (!is_null($action) && !is_bool($action)) { $this->action = $action; return $this; - } else { - return $this->action ?: ''; } + + $name = $this->action ?: ''; + return true === $action ? $name : strtolower($name); } /** @@ -1734,9 +1790,9 @@ class Request if (!is_null($lang)) { $this->langset = $lang; return $this; - } else { - return $this->langset ?: ''; } + + return $this->langset ?: ''; } /** @@ -1800,66 +1856,67 @@ class Request $except = []; } - if (false !== $key && $this->isGet() && !$this->isCheckCache) { - // 标记请求缓存检查 - $this->isCheckCache = true; - if (false === $expire) { - // 关闭当前缓存 + if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) { + // 关闭当前缓存 + return; + } + + // 标记请求缓存检查 + $this->isCheckCache = true; + + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { return; } + } - foreach ($except as $rule) { - if (0 === stripos($this->url(), $rule)) { - return; + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); } } - - if ($key instanceof \Closure) { - $key = call_user_func_array($key, [$this]); - } elseif (true === $key) { - // 自动缓存功能 - $key = '__URL__'; - } elseif (strpos($key, '|')) { - list($key, $fun) = explode('|', $key); - } - - // 特殊规则替换 - if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); - } - - if (false !== strpos($key, ':')) { - $param = $this->param(); - foreach ($param as $item => $val) { - if (is_string($val) && false !== strpos($key, ':' . $item)) { - $key = str_replace(':' . $item, $val, $key); - } - } - } elseif (strpos($key, ']')) { - if ('[' . $this->ext() . ']' == $key) { - // 缓存某个后缀的请求 - $key = md5($this->url()); - } else { - return; - } - } - - if (isset($fun)) { - $key = $fun($key); - } - $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]; } /** @@ -1872,4 +1929,15 @@ class Request return $this->cache; } + /** + * 获取请求数据的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->param($name); + } + } diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php index e554ab313..071d075c7 100644 --- a/thinkphp/library/think/Response.php +++ b/thinkphp/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/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php index ae7919e22..81f914b9f 100644 --- a/thinkphp/library/think/Route.php +++ b/thinkphp/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,17 +72,11 @@ class Route protected $domain; /** - * 当前分组 - * @var string + * 当前分组对象 + * @var RuleGroup */ protected $group; - /** - * 路由标识 - * @var array - */ - protected $name = []; - /** * 路由绑定 * @var array @@ -100,27 +95,76 @@ class Route */ protected $cross; - /** - * 当前路由标识 - * @var string - */ - protected $ruleName; - /** * 路由别名 * @var array */ protected $alias = []; - public function __construct(Request $request, Config $config) + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = true; + + /** + * (分组)路由规则是否合并解析 + * @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(); $this->setDefaultDomain(); } + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + $this->lazy = $lazy; + 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 @@ -137,22 +181,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) - { - $group = new RuleGroup($this); - // 注册分组到当前域名 - $domain->addRule($group); - - return $group; + $this->group = $domain; } /** @@ -204,22 +233,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,33 +248,22 @@ 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; + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); - $domain = new Domain($this, $domainName, $route, $option, $pattern); - - if (is_null($route)) { - // 获取原始分组 - $originGroup = $this->group; - // 设置当前域名 - $this->domain = $domainName; - $this->group = $this->createTopGroup($domain); - - // 解析域名路由规则 - $this->parseGroupRule($domain, $rule); - - // 还原默认域名 - $this->domain = $this->host; - // 还原默认分组 - $this->group = $originGroup; + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); } - $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; @@ -275,32 +277,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 @@ -315,11 +291,14 @@ class Route * 设置路由绑定 * @access public * @param string $bind 绑定信息 + * @param string $domain 域名 * @return $this */ - public function bind($bind) + public function bind($bind, $domain = null) { - $this->bind[$this->domain] = $bind; + $domain = is_null($domain) ? $this->domain : $domain; + + $this->bind[$domain] = $bind; return $this; } @@ -355,19 +334,6 @@ class Route return $result; } - /** - * 设置当前路由标识 - * @access public - * @param string $name 路由命名标识 - * @return $this - */ - public function name($name) - { - $this->ruleName = $name; - - return $this; - } - /** * 读取路由标识 * @access public @@ -376,13 +342,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); } /** @@ -393,7 +353,7 @@ class Route */ public function setName($name) { - $this->name = $name; + Container::get('rule_name')->import($name); return $this; } @@ -465,68 +425,9 @@ 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 = []) { - // 读取路由标识 - 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; - } - - $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); - } - - // 添加到当前分组 - $this->group->addRule($ruleItem, $method); - - if (!empty($option['cross_domain'])) { - $this->setCrossDomainRule($ruleItem, $method); - } - - return $ruleItem; - } - - /** - * 设置路由标识 用于URL反解生成 - * @access public - * @param string $rule 路由规则 - * @param string $name 路由标识 - * @param array $option 路由参数 - * @return void - */ - public function setRuleName($rule, $name, $option = []) - { - $vars = $this->parseVar($rule); - - if (isset($option['ext'])) { - $suffix = $option['ext']; - } elseif ($this->group->getOption('ext')) { - $suffix = $this->group->getOption('ext'); - } else { - $suffix = null; - } - - $this->name[strtolower($name)][] = [$rule, $vars, $this->domain, $suffix]; + return $this->group->addRule($rule, $route, $method, $option, $pattern); } /** @@ -539,10 +440,10 @@ 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->addRule($rule, $method); + $this->cross->addRuleItem($rule, $method); return $this; } @@ -556,23 +457,9 @@ class Route * @param array $pattern 变量规则 * @return void */ - public function rules($rules, $method = '*', $option = [], $pattern = []) + public function rules($rules, $method = '*', array $option = [], array $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); } /** @@ -584,49 +471,28 @@ 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; $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; - } - - // 注册子分组 - $this->group->addRule($group); - - if (!empty($option['cross_domain'])) { - $this->setCrossDomainRule($group); - } - - return $group; + return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); } /** * 注册路由 * @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); } @@ -635,12 +501,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); } @@ -649,12 +515,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); } @@ -663,12 +529,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); } @@ -677,12 +543,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); } @@ -691,12 +557,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); } @@ -710,32 +576,31 @@ class Route * @param array $pattern 变量规则 * @return Resource */ - public function resource($rule, $route = '', $option = [], $pattern = []) + public function resource($rule, $route = '', array $option = [], array $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)) + ->lazy($this->lazy); } /** - * 注册控制器路由 操作方法对应不同的请求后缀 + * 注册控制器路由 操作方法对应不同的请求前缀 * @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 = []) + public function controller($rule, $route = '', array $option = [], array $pattern = []) { + $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); + foreach ($this->methodPrefix as $type => $val) { - $this->$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + $item = $this->$type(':action', $val . ':action'); + $group->addRuleItem($item, $type); } - return $this; + return $group->prefix($route . '/'); } /** @@ -748,7 +613,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); } @@ -757,13 +622,13 @@ class Route * 注册重定向路由 * @access public * @param string|array $rule 路由规则 - * @param string $template 路由模板地址 + * @param string $route 路由地址 * @param array $status 状态码 * @param array $option 路由参数 * @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); } @@ -771,20 +636,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; } /** @@ -875,9 +738,9 @@ 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(); + return $this->group->addMissRule($route, $method, $option); } /** @@ -888,7 +751,7 @@ class Route */ public function auto($route) { - return $this->rule('', $route)->isAuto(); + return $this->group->addAutoRule($route); } /** @@ -910,7 +773,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); } @@ -920,16 +783,15 @@ 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->autoSearchController]); } /** * 检测域名的路由规则 * @access protected - * @param string $host 当前主机地址 * @return Domain */ protected function checkDomain() @@ -981,52 +843,6 @@ class Route return $item; } - /** - * 分析路由规则中的变量 - * @access public - * @param string $rule 路由规则 - * @return array - */ - public 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); - if ('$' == substr($name, -1)) { - $name = substr($name, 0, -1); - } - - $var[$name] = $optional ? 2 : 1; - } - } - - return $var; - } - /** * 设置全局的路由分组参数 * @access public diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php index b97c83904..ef4a197f4 100644 --- a/thinkphp/library/think/Template.php +++ b/thinkphp/library/think/Template.php @@ -85,33 +85,22 @@ 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'] = 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'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); - $this->storage = new $class(); - } + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); - /** - * 字符串替换 避免正则混淆 - * @access private - * @param string $str - * @return string - */ - private function stripPreg($str) - { - return str_replace( - ['{', '}', '(', ')', '|', '[', ']', '-', '+', '*', '.', '^', '?'], - ['\{', '\}', '\(', '\)', '\|', '\[', '\]', '\-', '\+', '\*', '\.', '\^', '\?'], - $str); + $this->storage = new $class(); } /** @@ -153,8 +142,6 @@ class Template $this->config = array_merge($this->config, $config); } elseif (isset($this->config[$config])) { return $this->config[$config]; - } else { - return; } } @@ -168,20 +155,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; - } - } - - return $data; } + + $data = $this->data; + + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + + return $data; } /** @@ -217,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)) { // 缓存无效 重新模板编译 @@ -311,18 +298,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 +393,6 @@ class Template $this->storage->write($cacheFile, $content); $this->includeFile = []; - - return; } /** @@ -490,8 +464,6 @@ class Template // 还原被替换的Literal标签 $this->parseLiteral($content, true); - - return; } /** @@ -508,10 +480,8 @@ class Template // PHP语法检查 if ($this->config['tpl_deny_php'] && false !== strpos($content, 'parseTag($content, $hide ? '' : $tagLib); - - return; } /** @@ -827,9 +791,9 @@ class Template if (!empty($name) && isset($array[$name])) { return $array[$name]; - } else { - return $array; } + + return $array; } /** @@ -1268,9 +1232,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/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php index e7cf75d3f..a7281df46 100644 --- a/thinkphp/library/think/Url.php +++ b/thinkphp/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)); @@ -320,20 +318,21 @@ 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 . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); unset($vars[$key]); - - $result = [$url, $domain, $suffix]; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $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 . '?>'], '', $url); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; } else { break; } diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php index e078c88c1..90c5d8333 100644 --- a/thinkphp/library/think/Validate.php +++ b/thinkphp/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); } @@ -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; } /** @@ -1321,7 +1322,7 @@ class Validate $rule = '/^' . $rule . '$/'; } - return 1 === preg_match($rule, (string) $value); + return is_scalar($value) && 1 === preg_match($rule, (string) $value); } /** diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php index d4c520334..3ff1a0128 100644 --- a/thinkphp/library/think/cache/driver/File.php +++ b/thinkphp/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; } @@ -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) { + } } /** @@ -269,7 +272,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/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php index ad4de7af6..2f019608c 100644 --- a/thinkphp/library/think/cache/driver/Redis.php +++ b/thinkphp/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; + } } diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php index 4ca0c4ffa..9896aea56 100644 --- a/thinkphp/library/think/console/command/Clear.php +++ b/thinkphp/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/thinkphp/library/think/console/command/RunServer.php b/thinkphp/library/think/console/command/RunServer.php index 60edfd98b..4dd610b6e 100644 --- a/thinkphp/library/think/console/command/RunServer.php +++ b/thinkphp/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/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php index d702bd1af..e4cc5bb57 100644 --- a/thinkphp/library/think/console/command/make/Controller.php +++ b/thinkphp/library/think/console/command/make/Controller.php @@ -24,17 +24,24 @@ 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('plain')) { - return __DIR__ . '/stubs/controller.plain.stub'; + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; } - return __DIR__ . '/stubs/controller.stub'; + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; } protected function getClassName($name) diff --git a/thinkphp/library/think/console/command/make/Middleware.php b/thinkphp/library/think/console/command/make/Middleware.php new file mode 100644 index 000000000..bfe821b03 --- /dev/null +++ b/thinkphp/library/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, 'http') . '\middleware'; + } +} diff --git a/thinkphp/library/think/console/command/make/Model.php b/thinkphp/library/think/console/command/make/Model.php index d4e9b5dd0..03e6b3fcd 100644 --- a/thinkphp/library/think/console/command/make/Model.php +++ b/thinkphp/library/think/console/command/make/Model.php @@ -26,7 +26,7 @@ class Model extends Make protected function getStub() { - return __DIR__ . '/stubs/model.stub'; + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; } protected function getNamespace($appNamespace, $module) diff --git a/thinkphp/library/think/console/command/make/stubs/controller.api.stub b/thinkphp/library/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 000000000..aed9edfbd --- /dev/null +++ b/thinkphp/library/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ +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; diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php index 0b1885833..d5f06d00f 100644 --- a/thinkphp/library/think/console/command/optimize/Route.php +++ b/thinkphp/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/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php index 179970137..61620badd 100644 --- a/thinkphp/library/think/console/command/optimize/Schema.php +++ b/thinkphp/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/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php index 4f98ac952..239a02138 100644 --- a/thinkphp/library/think/db/Builder.php +++ b/thinkphp/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)) { @@ -168,12 +174,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 +233,7 @@ abstract class Builder { $item = []; $options = $query->getOptions(); + foreach ((array) $tables as $key => $table) { if (!is_numeric($key)) { $key = $this->connection->parseSqlTable($key); @@ -387,7 +393,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)) { // 处理带非单词字符的字段名 @@ -845,7 +851,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/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php index c7d3cd452..142e80443 100644 --- a/thinkphp/library/think/db/Connection.php +++ b/thinkphp/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 = []; @@ -193,9 +210,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')); } /** @@ -351,7 +368,8 @@ 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; } else { @@ -365,6 +383,7 @@ abstract class Connection // 记录字段类型 $type[$key] = $val['type']; $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { $pk[] = $key; } @@ -472,49 +491,55 @@ 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($config['params']) && is_array($config['params'])) { - $params = $config['params'] + $this->params; - } else { - $params = $this->params; - } - - // 记录当前字段属性大小写设置 - $this->attrCase = $params[PDO::ATTR_CASE]; - - try { - if (empty($config['dsn'])) { - $config['dsn'] = $this->parseDsn($config); - } - - if ($config['debug']) { - $startTime = microtime(true); - } - - $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); - - if ($config['debug']) { - // 记录数据库连接信息 - $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); - } - } catch (\PDOException $e) { - if ($autoConnection) { - $this->log($e->getMessage(), 'error'); - return $this->connect($autoConnection, $linkNum); - } else { - throw $e; - } - } + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; } - return $this->links[$linkNum]; + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + if ($config['debug']) { + $startTime = microtime(true); + } + + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + + if ($config['debug']) { + // 记录数据库连接信息 + $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } } /** @@ -535,9 +560,9 @@ abstract class Connection { if (!$this->linkID) { return false; - } else { - return $this->linkID; } + + return $this->linkID; } /** @@ -783,11 +808,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($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 +820,57 @@ 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); } $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; - } - } - $query->setOption('data', $data); - $query->setOption('limit', 1); - - // 生成查询SQL - $sql = $this->builder->select($query); - - $bind = $query->getBind(); - - if (!empty($options['fetch_sql'])) { - // 获取实际执行的SQL语句 - return $this->getRealSql($sql, $bind); - } - - // 事件回调 - if ($result = $query->trigger('before_find')) { + if (is_string($pk) && !is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; } else { - // 执行查询 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + $item[$pk] = $data; + } + $data = $item; + } - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } + $query->setOption('data', $data); + $query->setOption('limit', 1); - $result = isset($resultSet[0]) ? $resultSet[0] : null; + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 事件回调 + $result = $query->trigger('before_find'); + + if (!$result) { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; } - if (isset($cache) && $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); } return $result; @@ -885,42 +911,41 @@ 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); + } + + $resultSet = $query->trigger('before_select'); + + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; } + } - if ($resultSet = $query->trigger('before_select')) { - } else { - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - } - - if (isset($cache) && false !== $resultSet) { - // 缓存数据集 - $this->cacheData($key, $resultSet, $cache); - } + if (!empty($options['cache']) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $options['cache']); } return $resultSet; @@ -1008,6 +1033,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 +1058,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 +1078,6 @@ abstract class Connection // 分析查询表达式 $options = $query->getOptions(); - // 生成SQL语句 $table = $this->parseSqlTable($table); $sql = $this->builder->selectInsert($query, $fields, $table); @@ -1062,12 +1085,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 +1115,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]); } unset($data[$pk]); } elseif (is_array($pk)) { @@ -1118,7 +1139,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]); } // 更新数据 @@ -1131,34 +1152,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); - - if ($result) { - if (is_string($pk) && isset($where[$pk])) { - $data[$pk] = $where[$pk]; - } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $data[$pk] = $val; - } - - $query->setOption('data', $data); - $query->trigger('after_update'); - } - - return $result; } + + // 检测缓存 + $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); + + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + + $query->setOption('data', $data); + $query->trigger('after_update'); + } + + return $result; } /** @@ -1179,9 +1200,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); } 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]); } if (true !== $data && empty($options['where'])) { @@ -1239,50 +1260,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) { + return $result; + } } - if (false === $result) { - if (isset($options['field'])) { - $query->removeOption('field'); - } + if (isset($options['field'])) { + $query->removeOption('field'); + } - if (is_string($field)) { - $field = array_map('trim', explode(',', $field)); - } + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } - $query->setOption('field', $field); - $query->setOption('limit', 1); - // 生成查询SQL - $sql = $this->builder->select($query); + $query->setOption('field', $field); + $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(); - // 执行查询操作 - $pdo = $this->query($sql, $bind, $options['master'], true); + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); - $result = $pdo->fetchColumn(); + $result = $pdo->fetchColumn(); - if (isset($cache) && false !== $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); } return false !== $result ? $result : $default; @@ -1315,84 +1330,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) { + return $result; + } } - if (false === $result) { - if (isset($options['field'])) { - $query->removeOption('field'); - } + if (isset($options['field'])) { + $query->removeOption('field'); + } - if (is_null($field)) { - $field = '*'; - } elseif ($key && '*' != $field) { - $field = $key . ',' . $field; - } + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } - if (is_string($field)) { - $field = array_map('trim', explode(',', $field)); - } + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } - $query->setOption('field', $field); + $query->setOption('field', $field); - // 生成查询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); + } - // 执行查询操作 - $pdo = $this->query($sql, $bind, $options['master'], true); + // 执行查询操作 + $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 (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 ('*' == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; - if (strpos($key, '.')) { - list($alias, $key) = explode('.', $key); - } - - if (2 == $count) { - $column = $key2; - } elseif (1 == $count) { - $column = $key1; - } else { - $column = null; - } - - $result = array_column($resultSet, $column, $key); - } else { - $result = []; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); } - } - if (isset($cache) && isset($guid)) { - // 缓存数据 - $this->cacheData($guid, $result, $cache); + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; } } + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + return $result; } @@ -1784,22 +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', - ]; - $error = $e->getMessage(); - foreach ($info as $msg) { + foreach ($this->breakMatchStr as $msg) { if (false !== stripos($error, $msg)) { return true; } @@ -2046,15 +2049,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($value, $options, $bind = []) + protected function getCacheKey(Query $query, $value) { if (is_scalar($value)) { $data = $value; @@ -2062,14 +2080,16 @@ abstract class Connection $data = $value[2]; } + $prefix = 'think:' . $this->getConfig('database') . '.'; + if (isset($data)) { - return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $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; } } diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php index 99093acd2..1d96724fc 100644 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/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 @@ -292,7 +309,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); @@ -355,6 +373,16 @@ class Query return $this->connection->getLastInsID($sequence); } + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->connection->getNumRows(); + } + /** * 获取最近一次查询的sql语句 * @access public @@ -424,7 +452,7 @@ class Query * 获取数据库的配置参数 * @access public * @param string $name 参数名称 - * @return boolean + * @return mixed */ public function getConfig($name = '') { @@ -507,18 +535,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); - } - - $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; - - return $tableName; } + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + + return $tableName; } /** @@ -844,6 +871,7 @@ class Query { if (is_array($join)) { $table = $join; + $alias = array_shift($join); } else { $join = trim($join); @@ -1030,7 +1058,7 @@ class Query */ public function exp($field, $value) { - $this->data($field, ['exp', $value]); + $this->data($field, ['exp', $value, $this->secureKey]); return $this; } @@ -1047,9 +1075,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 = []; @@ -1162,7 +1190,7 @@ class Query */ public function whereNull($field, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'null', null); + return $this->parseWhereExp($logic, $field, 'null', null, [], true); } /** @@ -1174,7 +1202,7 @@ class Query */ public function whereNotNull($field, $logic = 'AND') { - return $this->parseWhereExp($logic, $field, 'notnull', null); + return $this->parseWhereExp($logic, $field, 'notnull', null, [], true); } /** @@ -1213,7 +1241,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); } /** @@ -1226,7 +1254,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); } /** @@ -1239,7 +1267,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); } /** @@ -1252,7 +1280,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); } /** @@ -1265,7 +1293,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); } /** @@ -1278,7 +1306,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); } /** @@ -1321,25 +1349,27 @@ 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); + 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 查询表达式 * @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, array $param = [], $strict = false) { if ($field instanceof $this) { $this->options['where'] = $field->getOptions('where'); @@ -1352,56 +1382,22 @@ class Query $field = $this->options['via'] . '.' . $field; } - if ($field instanceof \Closure) { - $where = is_string($op) ? [$op, $field] : $field; + if ($strict) { + // 使用严格模式查询 + $where = [$field, $op, $condition]; + if ('exp' == strtolower($op) && !empty($param)) { + // 参数绑定 + $this->bind($param); + } + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof \Closure) { + $where = $field; $field = ''; - } elseif (is_string($field) && preg_match('/[,=\<\'\"\(\s]/', $field)) { - $where = ['', 'exp', $field]; - if (is_array($op)) { - // 参数绑定 - $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', '']; - } - } 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])) { - // 参数绑定 - $this->bind($param[2]); - } + } elseif (is_string($field)) { + // 解析条件单元 + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); } if (!empty($where)) { @@ -1415,6 +1411,80 @@ class Query 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 (preg_match('/[,=\<\'\"\(\s]/', $field)) { + $where = ['', 'exp', $field]; + if (is_array($op)) { + // 参数绑定 + $this->bind($op); + } + } elseif (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } 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($bind); + } + } else { + $where = $field ? [$field, $op, $condition] : null; + } + + return $where; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems($field, $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if (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; + } + /** * 去除某个查询条件 * @access public @@ -1622,6 +1692,7 @@ class Query } } } + $this->options['table'] = $table; return $this; @@ -1648,33 +1719,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; + } + + $field = empty($order) ? $field : [$field => $order]; + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); } } + } - if (!isset($this->options['order'])) { - $this->options['order'] = []; - } + if (!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; - } + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; } return $this; @@ -1733,7 +1806,7 @@ class Query /** * 指定group查询 * @access public - * @param string $group GROUP + * @param string|array $group GROUP * @return $this */ public function group($group) @@ -1986,9 +2059,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)) { @@ -2008,9 +2082,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); } /** @@ -2019,18 +2091,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); } /** @@ -2044,7 +2115,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; @@ -2080,6 +2151,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 @@ -2102,9 +2186,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; } /** @@ -2679,10 +2762,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); } /** diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php index cea8d4ae2..d13ac0168 100644 --- a/thinkphp/library/think/db/builder/Mysql.php +++ b/thinkphp/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/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php index ed4c62684..85f354af0 100644 --- a/thinkphp/library/think/debug/Html.php +++ b/thinkphp/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); diff --git a/thinkphp/library/think/http/middleware/MissingResponseException.php b/thinkphp/library/think/facade/Middleware.php similarity index 50% rename from thinkphp/library/think/http/middleware/MissingResponseException.php rename to thinkphp/library/think/facade/Middleware.php index 7cdcb33df..468279642 100644 --- a/thinkphp/library/think/http/middleware/MissingResponseException.php +++ b/thinkphp/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/thinkphp/library/think/http/middleware/DispatcherInterface.php b/thinkphp/library/think/http/middleware/DispatcherInterface.php deleted file mode 100644 index 1693f6093..000000000 --- a/thinkphp/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/thinkphp/library/think/http/tests/middleware/DispatcherTest.php b/thinkphp/library/think/http/tests/middleware/DispatcherTest.php deleted file mode 100644 index 298cbccc1..000000000 --- a/thinkphp/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/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php index 3ec4cd1c4..f71509222 100644 --- a/thinkphp/library/think/log/driver/File.php +++ b/thinkphp/library/think/log/driver/File.php @@ -24,6 +24,7 @@ class File 'file_size' => 2097152, 'path' => '', 'apart_level' => [], + 'max_files' => 0, ]; protected $writed = []; @@ -36,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; } } @@ -49,11 +52,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') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; } $path = dirname($destination); @@ -72,9 +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 . 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); @@ -103,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) { } diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php index 3a9b60f59..1b6061a27 100644 --- a/thinkphp/library/think/model/Collection.php +++ b/thinkphp/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 diff --git a/thinkphp/library/think/model/concern/Attribute.php b/thinkphp/library/think/model/concern/Attribute.php index dc104481c..c91cd835a 100644 --- a/thinkphp/library/think/model/concern/Attribute.php +++ b/thinkphp/library/think/model/concern/Attribute.php @@ -139,37 +139,38 @@ trait Attribute { if (is_string($data)) { $this->data[$data] = $value; + return $this; + } + + // 清空数据 + $this->data = []; + + if (is_object($data)) { + $data = get_object_vars($data); + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + } + + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } elseif (is_array($value)) { + foreach ($value as $name) { + if (isset($data[$name])) { + $this->data[$name] = $data[$name]; + } + } } else { - // 清空数据 - $this->data = []; - - if (is_object($data)) { - $data = get_object_vars($data); - } - - if ($this->disuse) { - // 废弃字段 - foreach ((array) $this->disuse as $key) { - if (array_key_exists($key, $data)) { - unset($data[$key]); - } - } - } - - if (true === $value) { - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - } elseif (is_array($value)) { - foreach ($value as $name) { - if (isset($data[$name])) { - $this->data[$name] = $data[$name]; - } - } - } else { - $this->data = $data; - } + $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); } /** @@ -454,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; + } - if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + /** + * 获取关联属性值 + * @access protected + * @param string $name 属性名 + * @param array $item 数据 + * @return mixed + */ + protected function getRelationAttribute($name, &$item) + { + $relation = $this->isRelationAttr($name); - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; + if ($relation) { + $modelRelation = $this->$relation(); + if ($modelRelation instanceof Relation) { + $value = $this->getRelationData($modelRelation); - if (isset($item[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $item[$key] = $value ? $value->getAttr($attr) : null; - } + if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; } - return false; } - // 保存关联对象值 - $this->relation[$name] = $value; - - return $value; + 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); } /** @@ -541,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, '\\')) { diff --git a/thinkphp/library/think/model/concern/RelationShip.php b/thinkphp/library/think/model/concern/RelationShip.php index 516119589..2c993bd20 100644 --- a/thinkphp/library/think/model/concern/RelationShip.php +++ b/thinkphp/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; } /** @@ -142,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(); @@ -152,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/thinkphp/library/think/model/concern/SoftDelete.php b/thinkphp/library/think/model/concern/SoftDelete.php index 16b75b728..473f9e3ea 100644 --- a/thinkphp/library/think/model/concern/SoftDelete.php +++ b/thinkphp/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; } /** diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php index bcbab2ac1..bf2dbe53a 100644 --- a/thinkphp/library/think/model/relation/BelongsTo.php +++ b/thinkphp/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(); @@ -69,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; } @@ -125,6 +127,8 @@ class BelongsTo extends OneToOne } if (!empty($range)) { + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere([ [$localKey, 'in', $range], ], $localKey, $relation, $subRelation, $closure); @@ -168,6 +172,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); @@ -221,4 +227,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/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php index 915dea47c..02c743c59 100644 --- a/thinkphp/library/think/model/relation/HasOne.php +++ b/thinkphp/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(); @@ -66,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))); @@ -134,6 +139,8 @@ class HasOne extends OneToOne } if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere([ [$foreignKey, 'in', $range], ], $foreignKey, $relation, $subRelation, $closure); @@ -176,7 +183,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); @@ -197,4 +207,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; + } + } } diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php index b3a4f39f2..2b489113d 100644 --- a/thinkphp/library/think/model/relation/MorphMany.php +++ b/thinkphp/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); diff --git a/thinkphp/library/think/process/pipes/Windows.php b/thinkphp/library/think/process/pipes/Windows.php index bba7e9b86..1b8b0d4f2 100644 --- a/thinkphp/library/think/process/pipes/Windows.php +++ b/thinkphp/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; diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php index fc32aa567..0b4cb28e0 100644 --- a/thinkphp/library/think/response/View.php +++ b/thinkphp/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/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php index f92b99c77..9c1681a4a 100644 --- a/thinkphp/library/think/response/Xml.php +++ b/thinkphp/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']); } diff --git a/thinkphp/library/think/route/AliasRule.php b/thinkphp/library/think/route/AliasRule.php new file mode 100644 index 000000000..217046596 --- /dev/null +++ b/thinkphp/library/think/route/AliasRule.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class AliasRule extends Domain +{ + protected $route; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由别名 + * @param string $route 路由绑定 + * @param array $option 路由参数 + */ + public function __construct(Route $router, RuleGroup $parent, $name, $route, $option = []) + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->option = $option; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $depr = '/', $completeMatch = false) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + list($action, $bind) = array_pad(explode('|', $url, 2), 2, ''); + + if (isset($this->option['allow']) && !in_array($action, $this->option['allow'])) { + // 允许操作 + return false; + } elseif (isset($this->option['except']) && in_array($action, $this->option['except'])) { + // 排除操作 + return false; + } + + if (isset($this->option['method'][$action])) { + $this->option['method'] = $this->option['method'][$action]; + } + + // 匹配后执行的行为 + $this->afterMatchGroup($request); + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + } + + $this->parseBindAppendParam($this->route); + + if (0 === strpos($this->route, '\\')) { + // 路由到类 + return $this->bindToClass($bind, substr($this->route, 1)); + } elseif (0 === strpos($this->route, '@')) { + // 路由到控制器类 + return $this->bindToController($bind, substr($this->route, 1)); + } else { + // 路由到模块/控制器 + return $this->bindToModule($bind, $this->route); + } + } + + /** + * 设置允许的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ + public function allow($action = []) + { + return $this->option('allow', $action); + } + + /** + * 设置排除的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ + public function except($action = []) + { + return $this->option('except', $action); + } + + /** + * 获取当前的路由 + * @access public + * @return string + */ + public function getRoute() + { + return $this->route; + } +} diff --git a/thinkphp/library/think/route/Domain.php b/thinkphp/library/think/route/Domain.php index d8f3e4f28..12ef39f30 100644 --- a/thinkphp/library/think/route/Domain.php +++ b/thinkphp/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 { @@ -26,7 +24,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 +32,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; @@ -51,30 +49,11 @@ 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->rule = null; - } - // 检测别名路由 - 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绑定 @@ -87,6 +66,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 @@ -97,45 +89,11 @@ class Domain extends RuleGroup */ private function checkRouteAlias($request, $url, $depr) { - $array = explode('|', $url); - $alias = array_shift($array); - $item = $this->router->getAlias($alias); + $alias = strpos($url, '|') ? strstr($url, '|', true) : $url; - if (is_array($item)) { - list($rule, $option) = $item; - $action = $array[0]; + $item = $this->router->getAlias($alias); - 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 ? $item->check($request, $url, $depr) : false; } /** @@ -145,41 +103,53 @@ 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)) { + + $this->parseBindAppendParam($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); } } return false; } + protected function parseBindAppendParam(&$bind) + { + if (false !== strpos($bind, '?')) { + list($bind, $query) = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + /** * 绑定到类 - * @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'); @@ -192,15 +162,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'); @@ -214,15 +182,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'); @@ -235,15 +201,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'); diff --git a/thinkphp/library/think/route/Resource.php b/thinkphp/library/think/route/Resource.php index 7072623c7..6873132ee 100644 --- a/thinkphp/library/think/route/Resource.php +++ b/thinkphp/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,23 +49,28 @@ class Resource extends RuleGroup $this->pattern = $pattern; $this->option = $option; $this->rest = $rest; + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } } /** - * 检测分组路由 + * 解析资源路由规则 * @access public - * @param Request $request 请求对象 - * @param string $url 访问地址 - * @param string $depr 路径分隔符 - * @param bool $completeMatch 路由是否完全匹配 - * @return Dispatch + * @param mixed $rule 路由规则 + * @return void */ - public function check($request, $url, $depr = '/', $completeMatch = false) + public function parseGroupRule($rule) { + $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); } /** @@ -84,17 +89,12 @@ 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; } - // 注册分组 - $group = $this->router->getGroup(); - - $this->router->setGroup($this); - // 注册资源路由 foreach ($this->rest as $key => $val) { if ((isset($option['only']) && !in_array($key, $option['only'])) @@ -102,18 +102,16 @@ 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; - $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/thinkphp/library/think/route/Rule.php b/thinkphp/library/think/route/Rule.php index 7fe8d3e84..92f1ce9de 100644 --- a/thinkphp/library/think/route/Rule.php +++ b/thinkphp/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 = '/'); @@ -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; } @@ -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 @@ -220,7 +203,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]; @@ -231,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 @@ -255,7 +257,8 @@ abstract class Rule */ public function response($response) { - return $this->option('response', $response); + $this->option['response'][] = $response; + return $this; } /** @@ -267,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; } @@ -280,6 +279,26 @@ abstract class Rule return $this; } + /** + * 指定路由中间件 + * @access public + * @param string|array|\Closure $middleware + * @param mixed $param + * @return $this + */ + public function middleware($middleware, $param = null) + { + if (is_null($param) && is_array($middleware)) { + $this->option['middleware'] = $middleware; + } else { + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $param]; + } + } + + return $this; + } + /** * 设置路由缓存 * @access public @@ -416,7 +435,7 @@ abstract class Rule } if ($allow && $this->parent) { - $this->parent->addRule($this, 'options'); + $this->parent->addRuleItem($this, 'options'); } return $this->option('cross_domain', $allow); @@ -484,6 +503,8 @@ abstract class Rule } $this->option = array_merge($parentOption, $this->option); + + return $this->option; } /** @@ -574,34 +595,15 @@ 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); } } } - // 绑定模型数据 - 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, '/'); @@ -629,6 +631,43 @@ abstract class Rule return $this->dispatch($request, $route, $option); } + protected function afterMatchRule($request, $option = [], $matches = []) + { + // 添加中间件 + if (!empty($option['middleware'])) { + foreach ($option['middleware'] as $middleware) { + Container::get('middleware')->add($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 @@ -694,6 +733,8 @@ abstract class Rule */ protected function checkAfter($after) { + Container::get('log')->notice('路由后置行为建议使用中间件替代!'); + $hook = Container::get('hook'); $result = null; @@ -711,9 +752,9 @@ abstract class Rule return new ResponseDispatch($result); } elseif ($result instanceof Dispatch) { return $result; - } else { - return false; } + + return false; } /** @@ -738,24 +779,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($request, substr($route, 1)); } else { // 路由到模块/控制器/操作 - $result = $this->parseModule($route); + $result = $this->dispatchModule($request, $route); } return $result; @@ -764,18 +794,57 @@ 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 Request $request Request对象 + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController($request, $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 Request $request Request对象 + * @param string $route 路由地址 + * @return ModuleDispatch + */ + protected function dispatchModule($request, $route) + { + 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; + $method = $request->method(); if ($config->get('use_action_prefix') && $this->router->getMethodPrefix($method)) { $prefix = $this->router->getMethodPrefix($method); @@ -890,6 +959,107 @@ abstract class Rule return [$path, $var]; } + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '') + { + foreach ($match as $name) { + $replace[] = $this->buildNameRegex($name, $pattern, $suffix); + } + + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $rule) { + $rule = rtrim($rule, '/'); + } + + $regex = str_replace($match, $replace, $rule); + $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); + + return $regex . ($completeMatch ? '$' : ''); + } + + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param string $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex($name, $pattern, $suffix) + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return $prefix . preg_quote($name, '/'); + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = '\w+'; + } + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } else { + $name = substr($name, 1, -1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + /** * 设置路由参数 * @access public diff --git a/thinkphp/library/think/route/RuleGroup.php b/thinkphp/library/think/route/RuleGroup.php index 3f8317414..333c49977 100644 --- a/thinkphp/library/think/route/RuleGroup.php +++ b/thinkphp/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; @@ -43,35 +44,51 @@ class RuleGroup extends Rule // 完整名称 protected $fullName; + // 所在域名 + protected $domain; + /** * 架构函数 * @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->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this); + } } /** * 设置分组的路由规则 * @access public - * @return $this + * @return void */ protected function setFullName() { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + if ($this->parent && $this->parent->getFullName()) { $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); } else { @@ -80,15 +97,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; } /** @@ -103,87 +118,53 @@ 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($url)) { return false; } - if ($this->fullName) { - // 分组URL匹配检查 - $pos = strpos(str_replace('<', ':', $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); } - $group = $this->router->getGroup(); - - $this->router->setGroup($this); - - $this->router->parseGroupRule($this, $this->rule); - - $this->router->setGroup($group); - - $this->rule = null; - } - - // 分组匹配后执行的行为 - - // 指定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->parseGroupRule($this->rule); } // 获取当前路由规则 $method = strtolower($request->method()); - $rules = array_merge($this->rules['*'], $this->rules[$method]); + $rules = $this->getMethodRules($method); + + if (count($rules) == 0) { + return false; + } if ($this->parent) { // 合并分组参数 $this->mergeGroupOptions(); + // 合并分组变量规则 + $this->pattern = array_merge($this->parent->getPattern(), $this->pattern); } if (isset($this->option['complete_match'])) { $completeMatch = $this->option['complete_match']; } - if (!empty($this->option['append'])) { - $request->route($this->option['append']); - } - - if (isset($rules[$url])) { - // 快速定位 - $item = $rules[$url]; - $result = $item->check($request, $url, $depr, $completeMatch); + 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); @@ -192,10 +173,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 { @@ -206,37 +187,275 @@ class RuleGroup extends Rule } /** - * 设置自动路由 + * 获取当前请求的路由规则(包括子分组、资源路由) + * @access protected + * @param string $method + * @return array + */ + protected function getMethodRules($method) + { + return array_merge($this->rules['*'], $this->rules[$method]); + } + + /** + * 分组URL匹配检查 + * @access protected + * @param string $url + * @return bool + */ + protected function checkUrl($url) + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + + /** + * 延迟解析分组的路由规则 * @access public - * @param RuleItem $rule 路由规则 + * @param bool $lazy 路由是否延迟解析 * @return $this */ - public function setAutoRule(RuleItem $rule) + public function lazy($lazy = true) { - $this->auto = $rule; + if (!$lazy && !is_object($this->rule)) { + $this->parseGroupRule($this->rule); + $this->rule = null; + } + return $this; } /** - * 设置为MISS路由 + * 解析分组和域名的路由规则及绑定 * @access public - * @param RuleItem $rule 路由规则 - * @return $this + * @param mixed $rule 路由规则 + * @return void */ - public function setMissRule(RuleItem $rule) + public function parseGroupRule($rule) { - $this->miss = $rule; - return $this; + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_array($rule)) { + $this->addRules($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); + } + + $this->router->setGroup($origin); + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex($request, &$rules, $url, $depr, $completeMatch) + { + $url = $depr . str_replace('|', $depr, $url); + + foreach ($rules as $key => $item) { + if ($item instanceof RuleItem) { + $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } + + $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkRule($request, $url, []); + } + + unset($rules[$key]); + continue; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; + } + } + } + + 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; + } + } + } + + return $items[$pos]->checkRule($request, $url, $var); + } + + return false; + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + } + + /** + * 获取分组的MISS路由 + * @access public + * @return RuleItem|null + */ + public function getMissRule() + { + return $this->miss; + } + + /** + * 获取分组的自动路由 + * @access public + * @return string + */ + public function getAutoRule() + { + return $this->auto; + } + + /** + * 注册自动路由 + * @access public + * @param string $route 路由规则 + * @return void + */ + public function addAutoRule($route) + { + $this->auto = $route; + } + + /** + * 注册MISS路由 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem + */ + public function addMissRule($route, $method = '*', $option = []) + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option); + + $this->miss = $ruleItem; + + return $ruleItem; } /** * 添加分组下的路由规则或者子分组 * @access public - * @param 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, $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; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function addRules($rules, $method = '*', $option = [], $pattern = []) + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (is_array($val)) { + $route = array_shift($val); + $option = $val ? array_shift($val) : []; + $pattern = $val ? array_shift($val) : []; + } else { + $route = $val; + } + + $this->addRule($key, $route, $method, $option, $pattern); + } + } + + public function addRuleItem($rule, $method = '*') { if (strpos($method, '|')) { $rule->method($method); @@ -256,13 +475,24 @@ 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; } 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 @@ -283,9 +513,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)] : []; } } diff --git a/thinkphp/library/think/route/RuleItem.php b/thinkphp/library/think/route/RuleItem.php index e26637f62..b7873b0cf 100644 --- a/thinkphp/library/think/route/RuleItem.php +++ b/thinkphp/library/think/route/RuleItem.php @@ -11,6 +11,8 @@ namespace think\route; +use think\Container; +use think\Exception; use think\Route; class RuleItem extends Rule @@ -19,7 +21,7 @@ class RuleItem extends Rule * 路由规则 * @var string */ - protected $name; + protected $rule; /** * 路由地址 @@ -37,23 +39,29 @@ class RuleItem extends Rule * 架构函数 * @access public * @param Route $router 路由实例 - * @param RuleGroup $group 路由所属分组对象 - * @param string|array $name 路由规则 + * @param RuleGroup $parent 上级对象 + * @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 $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; $this->option = $option; $this->pattern = $pattern; - $this->setRule($name); + $this->setRule($rule); + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this, $method); + } } /** @@ -71,7 +79,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, '/') : ''); + } + + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 获取当前路由规则 + * @access public + * @return string + */ + public function getRule() + { + return $this->rule; } /** @@ -85,25 +116,53 @@ class RuleItem extends Rule } /** - * 设置为自动路由 + * 获取当前路由的请求类型 * @access public + * @return string + */ + public function getMethod() + { + return strtolower($this->method); + } + + /** + * 检查后缀 + * @access public + * @param string $ext * @return $this */ - public function isAuto() + public function ext($ext = '') { - $this->parent->setAutoRule($this); + $this->option('ext', $ext); + $this->setRuleName(true); + return $this; } /** - * 设置为MISS路由 - * @access public - * @return $this + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void */ - public function isMiss() + protected function setRuleName($first = false) { - $this->parent->setMissRule($this); - return $this; + if ($this->name) { + $vars = $this->parseVar($this->rule); + $name = strtolower($this->name); + + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix]; + + Container::get('rule_name')->set($name, $value, $first); + } } /** @@ -111,16 +170,13 @@ class RuleItem extends Rule * @access public * @param Request $request 请求对象 * @param string $url 访问地址 + * @param array $match 匹配路由变量 * @param string $depr 路径分隔符 * @param bool $completeMatch 路由是否完全匹配 - * @return Dispatch + * @return Dispatch|false */ - public function check($request, $url, $depr = '/', $completeMatch = false) + public function checkRule($request, $url, $match = null, $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,165 +188,127 @@ 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, '/'); - $url = rtrim($url, '|'); - } + $option = $this->mergeGroupOptions(); // 检查前置行为 if (isset($option['before']) && false === $this->checkBefore($option['before'])) { return false; } + $url = $this->urlSuffixCheck($request, $url, $option); + + if (is_null($match)) { + $match = $this->match($url, $option, $depr, $completeMatch); + } + + if (false !== $match) { + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); + } + + return false; + } + + /** + * 检测路由(含路由匹配) + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $depr = '/', $completeMatch = false) + { + return $this->checkRule($request, $url, null, $depr, $completeMatch); + } + + /** + * URL后缀及Slash检查 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck($request, $url, $option = []) + { + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + if (isset($option['ext'])) { // 路由ext参数 优先于系统配置的URL伪静态后缀参数 $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); } - return $this->checkRule($request, $url, $depr, $completeMatch, $option); - } - - /** - * 检测路由规则 - * @access private - * @param Request $request 请求对象 - * @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 = []) - { - // 检查完整规则定义 - if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->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($this->name, '/'); - - // 多余参数是否合并 - $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($option['complete_match'])) { - $completeMatch = $option['complete_match']; - } - - if ($len1 >= $len2 || strpos($this->name, '[')) { - // 完整匹配 - if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->name, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->name, '[')))) { - return false; - } - - $pattern = array_merge($this->parent->getPattern(), $this->pattern); - - if (false !== $match = $this->match($url, $pattern)) { - // 匹配到路由规则 - return $this->parseRule($request, $this->name, $this->route, $url, $option, $match); - } - } - - return false; + 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) + private function match($url, $option, $depr, $completeMatch) { - $m2 = explode('/', $this->name); - $m1 = explode('|', $url); + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } - $var = []; + $pattern = array_merge($this->parent->getPattern(), $this->pattern); - foreach ($m2 as $key => $val) { - // val中定义了多个变量 - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - $value = []; - $replace = []; + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } - 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; - } + $var = []; + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); - $val = str_replace($matches[0], $replace, $val); + if ($depr == $rule && $depr != $url) { + return false; + } - 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 (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $var; } + return false; + } - if (0 === strpos($val, '[:')) { - // 可选参数 - $val = substr($val, 1, -1); - $optional = true; - } else { - $optional = false; - } + $slash = preg_quote('/-' . $depr, '/'); - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - - if (!$optional && !isset($m1[$key])) { - return false; - } - - if (isset($m1[$key]) && isset($pattern[$name])) { - // 检查变量规则 - if ($pattern[$name] instanceof \Closure) { - $result = call_user_func_array($pattern[$name], [$m1[$key]]); - if (false === $result) { - return false; - } - } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { - return false; - } - } - - $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; - } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { return false; } } + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); + + try { + if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; + } + } + } + // 成功匹配后返回URL中的动态变量数组 return $var; } diff --git a/thinkphp/library/think/route/RuleName.php b/thinkphp/library/think/route/RuleName.php new file mode 100644 index 000000000..460ccab39 --- /dev/null +++ b/thinkphp/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; + } + +} diff --git a/thinkphp/library/think/route/dispatch/Module.php b/thinkphp/library/think/route/dispatch/Module.php index 20bb812df..93d5bf103 100644 --- a/thinkphp/library/think/route/dispatch/Module.php +++ b/thinkphp/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; @@ -54,7 +55,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 +73,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'); @@ -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); } } diff --git a/vendor/autoload.php b/vendor/autoload.php index 767d32234..3cc741d8f 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8::getLoader(); +return ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece::getLoader(); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index c088a09cb..d7a24189a 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8 +class ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece { private static $loader; @@ -19,15 +19,15 @@ class ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8 return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -48,19 +48,19 @@ class ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8 $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$files; + $includeFiles = Composer\Autoload\ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire5b9bf45703a8bbf57b5d1b4f5a7493a8($fileIdentifier, $file); + composerRequirecf980ce2ebc944e5d847ad4152559ece($fileIdentifier, $file); } return $loader; } } -function composerRequire5b9bf45703a8bbf57b5d1b4f5a7493a8($fileIdentifier, $file) +function composerRequirecf980ce2ebc944e5d847ad4152559ece($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 908a89740..6d426f836 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8 +class ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece { public static $files = array ( '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php', @@ -257,9 +257,9 @@ class ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 7b4e00b1e..e12607dca 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -175,17 +175,17 @@ }, { "name": "topthink/framework", - "version": "v5.1.5", - "version_normalized": "5.1.5.0", + "version": "5.1.6", + "version_normalized": "5.1.6.0", "source": { "type": "git", "url": "https://github.com/top-think/framework.git", - "reference": "f81c4282cdf401be44fbe1a28b3e3df425e3017f" + "reference": "98aaf52dd364af7fdecc7214224678d180676b4f" }, "dist": { "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/framework/f81c4282cdf401be44fbe1a28b3e3df425e3017f.zip", - "reference": "f81c4282cdf401be44fbe1a28b3e3df425e3017f", + "url": "https://files.phpcomposer.com/files/top-think/framework/98aaf52dd364af7fdecc7214224678d180676b4f.zip", + "reference": "98aaf52dd364af7fdecc7214224678d180676b4f", "shasum": "" }, "require": { @@ -201,7 +201,7 @@ "sebastian/phpcpd": "2.*", "squizlabs/php_codesniffer": "2.*" }, - "time": "2018-02-02T05:39:38+00:00", + "time": "2018-03-26T07:10:00+00:00", "type": "think-framework", "installation-source": "dist", "notification-url": "https://packagist.org/downloads/",