diff --git a/base.php b/base.php index 2d0fbf1e..edca2aef 100644 --- a/base.php +++ b/base.php @@ -23,9 +23,10 @@ defined('MODE_PATH') or define('MODE_PATH', THINK_PATH . 'mode' . DS); // 系统 defined('CORE_PATH') or define('CORE_PATH', LIB_PATH . 'think' . DS); defined('TRAIT_PATH') or define('TRAIT_PATH', LIB_PATH . 'traits' . DS); defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); +defined('ROOT_PATH') or define('ROOT_PATH', dirname(APP_PATH) . DS); defined('APP_NAMESPACE') or define('APP_NAMESPACE', 'app'); defined('COMMON_MODULE') or define('COMMON_MODULE', 'common'); -defined('RUNTIME_PATH') or define('RUNTIME_PATH', realpath(APP_PATH) . DS . 'runtime' . DS); +defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); diff --git a/convention.php b/convention.php index 3854ddfe..d4d94782 100644 --- a/convention.php +++ b/convention.php @@ -52,6 +52,8 @@ return [ 'default_controller' => 'Index', // 默认操作名 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', // 默认的空控制器名 'empty_controller' => 'Error', // 操作方法后缀 diff --git a/library/think/App.php b/library/think/App.php index 924412a0..38ae4e60 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -28,6 +28,9 @@ class App */ public static function run() { + // 加载环境变量配置文件 + Config::loadEnv(APP_PATH . 'env' . EXT); + // 初始化应用(公共模块) self::initModule(COMMON_MODULE, Config::get()); @@ -130,6 +133,7 @@ class App break; case 'PUT': parse_str(file_get_contents('php://input'), $vars); + $vars = array_merge($_GET, $vars); break; default: $vars = $_GET; diff --git a/library/think/Config.php b/library/think/Config.php index 451b66ed..74949e07 100644 --- a/library/think/Config.php +++ b/library/think/Config.php @@ -29,7 +29,6 @@ class Config /** * 解析配置文件或内容 - * * @param string $config 配置文件路径或内容 * @param string $type 配置解析类型 * @param string $range 作用域 @@ -46,7 +45,6 @@ class Config /** * 加载配置文件(PHP格式) - * * @param string $file 配置文件名 * @param string $name 配置名(如设置即表示二级配置) * @param string $range 作用域 @@ -63,9 +61,28 @@ class Config return is_file($file) ? self::set(include $file, $name, $range) : self::$config[$range]; } + /** + * 加载环境变量配置文件 + * @param string $file 配置文件名 + * @param string $name 配置名(如设置即表示二级配置) + * @return void + */ + public static function loadEnv($file, $name = '') + { + if (is_file($file)) { + $env = include $file; + foreach ($env as $key => $val) { + if (!empty($name)) { + $_ENV[ENV_PREFIX . $name . '_' . $key] = $val; + } else { + $_ENV[ENV_PREFIX . $key] = $val; + } + } + } + } + /** * 检测配置是否存在 - * * @param string $name 配置参数名(支持二级配置 .号分割) * @param string $range 作用域 * @return bool @@ -85,7 +102,6 @@ class Config /** * 获取配置参数 为空则获取所有配置 - * * @param string $name 配置参数名(支持二级配置 .号分割) * @param string $range 作用域 * @return mixed @@ -119,7 +135,6 @@ class Config /** * 设置配置参数 name为数组则为批量设置 - * * @param string $name 配置参数名(支持二级配置 .号分割) * @param mixed $value 配置值 * @param string $range 作用域 diff --git a/library/think/Controller.php b/library/think/Controller.php index 7b259ce1..cd60e4be 100644 --- a/library/think/Controller.php +++ b/library/think/Controller.php @@ -147,21 +147,33 @@ class Controller * @param array $data 数据 * @param string|array $validate 验证器名或者验证规则数组 * @param array $message 提示信息 + * @param mixed $callback 回调方法(闭包) * @return void */ - public function validate($data, $validate, $message = []) + public function validate($data, $validate, $message = [], $callback = null) { if (is_array($validate)) { - $v = Loader::validate(); + $v = Loader::validate(Config::get('default_validate')); $v->rule($validate); } else { + if (strpos($validate, '.')) { + // 支持场景 + list($validate, $scene) = explode('.', $validate); + } $v = Loader::validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } } if (is_array($message)) { $v->message($message); } + if (is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + if (!$v->check($data)) { return $v->getError(); } else { diff --git a/library/think/Model.php b/library/think/Model.php index 69747601..16a0d302 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -54,8 +54,6 @@ class Model protected $scope = []; // 字段映射定义 protected $map = []; - // 字段验证规则定义 - protected $rule = []; /** * 架构函数 @@ -991,12 +989,18 @@ class Model if (!empty($this->options['validate'])) { $info = $this->options['validate']; if (is_array($info)) { - $validate = Loader::validate(); + $validate = Loader::validate(Config::get('default_validate')); $validate->rule($info['rule']); $validate->message($info['msg']); } else { - $name = is_string($info) ? $info : $this->name; + $name = is_string($info) ? $info : $this->name; + if (strpos($name, '.')) { + list($name, $scene) = explode('.', $name); + } $validate = Loader::validate($name); + if (!empty($scene)) { + $validate->scene($scene); + } } if (!$validate->check($data)) { $this->error = $validate->getError(); @@ -1726,8 +1730,8 @@ class Model public function order($field, $order = null) { if (!empty($field)) { - if (!is_array($field)) { - $field = empty($order) ? [$field] : [(string) $field => (string) $order]; + if (is_string($field)) { + $field = empty($order) ? $field : [$field => $order]; } $this->options['order'] = $field; } diff --git a/library/think/Validate.php b/library/think/Validate.php index 12c4d135..907536dd 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -32,58 +32,52 @@ class Validate // 验证规则默认提示信息 protected $typeMsg = [ - 'require' => '不能为空', - 'number' => '必须是数字', - 'float' => '必须是浮点数', - 'boolean' => '必须是布尔值', - 'email' => '格式不符', - 'array' => '必须是数组', - 'accepted' => '必须是yes、on或者1', - 'date' => '格式不符合', - 'alpha' => '只能是字母', - 'alphaNum' => '只能是字母和数字', - 'alphaDash' => '只能是字母、数字和下划线_及破折号-', - 'activeUrl' => '不是有效的域名或者IP', - 'url' => '不是有效的URL地址', - 'ip' => '不是有效的IP地址', - 'dateFormat' => '必须使用日期格式 :rule', - 'in' => '必须在 :rule 范围内', - 'notIn' => '不能在 :rule 范围内', - 'between' => '只能在 :1 - :2 之间', - 'notBetween' => '不能在 :1 - :2 之间', - 'length' => '长度不符合要求 :rule', - 'max' => '长度不能超过 :rule', - 'min' => '长度不能小于 :rule', - 'after' => '日期不能小于 :rule', - 'before' => '日期不能超过 :rule', + 'require' => ':attribute不能为空', + 'number' => ':attribute必须是数字', + 'float' => ':attribute必须是浮点数', + 'boolean' => ':attribute必须是布尔值', + 'email' => ':attribute格式不符', + 'array' => ':attribute必须是数组', + 'accepted' => ':attribute必须是yes、on或者1', + 'date' => ':attribute格式不符合', + 'alpha' => ':attribute只能是字母', + 'alphaNum' => ':attribute只能是字母和数字', + 'alphaDash' => ':attribute只能是字母、数字和下划线_及破折号-', + 'activeUrl' => ':attribute不是有效的域名或者IP', + 'url' => ':attribute不是有效的URL地址', + 'ip' => ':attribute不是有效的IP地址', + 'dateFormat' => ':attribute必须使用日期格式 :rule', + 'in' => ':attribute必须在 :rule 范围内', + 'notIn' => ':attribute不能在 :rule 范围内', + 'between' => ':attribute只能在 :1 - :2 之间', + 'notBetween' => ':attribute不能在 :1 - :2 之间', + 'length' => ':attribute长度不符合要求 :rule', + 'max' => ':attribute长度不能超过 :rule', + 'min' => ':attribute长度不能小于 :rule', + 'after' => ':attribute日期不能小于 :rule', + 'before' => ':attribute日期不能超过 :rule', 'expire' => '不在有效期内 :rule', 'allowIp' => '不允许的IP访问', 'denyIp' => '禁止的IP访问', - 'confirm' => '和字段 :rule 不一致', - 'egt' => '必须大于等于 :rule', - 'gt' => '必须大于 :rule', - 'elt' => '必须小于等于 :rule', - 'lt' => '必须小于 :rule', - 'eq' => '必须等于 :rule', - 'unique' => '已存在', - 'regex' => '不符合指定规则', + 'confirm' => ':attribute和字段 :rule 不一致', + 'egt' => ':attribute必须大于等于 :rule', + 'gt' => ':attribute必须大于 :rule', + 'elt' => ':attribute必须小于等于 :rule', + 'lt' => ':attribute必须小于 :rule', + 'eq' => ':attribute必须等于 :rule', + 'unique' => ':attribute已存在', + 'regex' => ':attribute不符合指定规则', ]; // 当前验证场景 - protected $scene = null; + protected $currentScene = null; // 正则表达式 regex = ['zip'=>'\d{6}',...] protected $regex = []; - // 验证参数 - protected $config = [ - // 有值才验证 value_validate = [name1,name2,...] - 'value_validate' => [], - // 存在就验证 exists_validate = [name1,name2,...] - 'exists_validate' => [], - // 验证场景 scene = ['edit'=>'name1,name2,...'] - 'scene' => [], - ]; + // 验证场景 scene = ['edit'=>'name1,name2,...'] + protected $scene = []; + // 验证失败错误信息 protected $error = []; @@ -95,19 +89,11 @@ class Validate * @access public * @param array $rules 验证规则 * @param array $message 验证提示信息 - * @param array $config 验证参数 */ - public function __construct(array $rules = [], $message = [], $config = []) + public function __construct(array $rules = [], $message = []) { $this->rule = array_merge($this->rule, $rules); $this->message = array_merge($this->message, $message); - $this->config = array_merge($this->config, $config); - if (is_string($this->config['value_validate'])) { - $this->config['value_validate'] = explode(',', $this->config['value_validate']); - } - if (is_string($this->config['exists_validate'])) { - $this->config['exists_validate'] = explode(',', $this->config['exists_validate']); - } } /** @@ -194,38 +180,23 @@ class Validate return $this; } - /** - * 传入验证参数 - * @access public - * @param string|array $name 参数名或者数组 - * @param mixed $value 参数值 - * @return Validate - */ - public function config($name, $value = null) - { - if (is_array($name)) { - $this->config = array_merge($this->config, $name); - } else { - $this->config[$name] = $value; - } - return $this; - } - /** * 设置验证场景 * @access public - * @param string $name 场景名 + * @param string|array $name 场景名或者场景设置数组 * @param mixed $fields 要验证的字段 * @return Validate */ public function scene($name, $fields = null) { - if (is_null($fields)) { + if (is_array($name)) { + $this->scene = array_merge($this->scene, $name); + }if (is_null($fields)) { // 设置当前场景 - $this->scene = $name; + $this->currentScene = $name; } else { // 设置验证场景 - $this->config['scene'][$name] = $fields; + $this->scene[$name] = $fields; } return $this; } @@ -261,34 +232,43 @@ class Validate // 分析验证规则 $scene = $this->getScene($scene); - // 读取提示信息 - if (isset($rules['__message__'])) { - $this->message($rules['__message__']); - unset($rules['__message__']); - } - foreach ($rules as $key => $rule) { + foreach ($rules as $key => $item) { + // field => rule1|rule2... field=>['rule1','rule2',...] + if (is_numeric($key)) { + // [field,rule1|rule2,msg1|msg2] + $key = $item[0]; + $rule = $item[1]; + if (isset($item[2])) { + $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; + } else { + $msg = []; + } + } else { + $rule = $item; + $msg = []; + } if (strpos($key, '|')) { - // 支持 字段|描述 用于返回默认错误 + // 字段|描述 用于指定属性名称 list($key, $title) = explode('|', $key); } else { $title = $key; } + // 场景检测 - if (!empty($scene) && !in_array($key, $scene)) { - continue; + if (!empty($scene)) { + if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, &$data])) { + continue; + } elseif (is_array($scene) && !in_array($key, $scene)) { + continue; + } } + // 获取数据 支持二维数组 $value = $this->getDataValue($data, $key); - if ((isset($this->config['value_validate']) && in_array($key, $this->config['value_validate']) && '' == $value) - || (isset($this->config['exists_validate']) && in_array($key, $this->config['exists_validate']) && is_null($value))) { - // 不满足自动验证条件 - continue; - } - - // 验证字段规则 - $result = $this->checkItem($key, $value, $rule, $data, $title); + // 字段验证 + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); if (true !== $result) { // 没有返回true 则表示验证失败 @@ -316,9 +296,10 @@ class Validate * @param mixed $rules 验证规则 * @param array $data 数据 * @param string $title 字段描述 - * @return string|true + * @param array $msg 提示信息 + * @return mixed */ - protected function checkItem($field, $value, $rules, &$data, $title = '') + protected function checkItem($field, $value, $rules, &$data, $title = '', $msg = []) { if ($rules instanceof \Closure) { // 匿名函数验证 支持传入当前字段和所有字段两个数据 @@ -328,7 +309,7 @@ class Validate if (is_string($rules)) { $rules = explode('|', $rules); } - $error = []; + $i = 0; foreach ($rules as $key => $rule) { if ($rule instanceof \Closure) { $result = call_user_func_array($rule, [$value, &$data]); @@ -347,34 +328,34 @@ class Validate } else { $info = $type = $key; } - // 验证类型 - $callback = isset($this->type[$type]) ? $this->type[$type] : [$this, $type]; - // 验证数据 - $result = call_user_func_array($callback, [$value, $rule, &$data, $field]); + + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || !empty($value)) { + // 验证类型 + $callback = isset($this->type[$type]) ? $this->type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, &$data, $field]); + } else { + $result = true; + } } if (false === $result) { // 验证失败 返回错误信息 - if (isset($this->message[$field . '.' . $info])) { - $error[] = $this->message[$field . '.' . $info]; - } elseif (isset($this->message[$field . '.'])) { - $error[] = $this->message[$field . '.']; + if (isset($msg[$i])) { + $message = $msg[$i]; } else { - $error[] = $this->getTypeMsg($title ?: $field, $info, $rule); + $message = $this->getRuleMsg($field, $title, $info, $rule); } - } elseif (is_string($result)) { - $error[] = $result; - } elseif (is_array($result)) { - // 自定义错误信息数组 + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 return $result; } - } - if (!empty($error)) { - $result = implode(',', $error); + $i++; } } - // 验证失败返回错误信息 - return $result; + return true !== $result ? $result : true; } /** @@ -541,26 +522,38 @@ class Validate * 验证是否唯一 * @access protected * @param mixed $value 字段值 - * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 * @param array $data 数据 * @param string $field 验证字段名 * @return bool */ protected function unique($value, $rule, $data, $field) { - $rule = explode(',', $rule); + if (is_string($rule)) { + $rule = explode(',', $rule); + } $model = Loader::table($rule[0]); - $key = isset($rule[3]) ? $rule[3] : $model->getPk(); $field = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($field, '^')) { + // 支持多个字段验证 + $fields = explode('^', $field); + foreach ($fields as $field) { + $map[$field] = $data[$field]; + } + } elseif (strpos($field, '=')) { + parse_str($field, $map); + } else { + $map[$field] = $data[$field]; + } + + $key = strval(isset($rule[3]) ? $rule[3] : $model->getPk()); if (isset($rule[2])) { - $except = $rule[2]; + $map[$key] = ['neq', $rule[2]]; } elseif (isset($data[$key])) { - $except = $data[$key]; - } - $map[$field] = $value; - if (isset($except)) { - $map[$key] = ['neq', $except]; + $map[$key] = ['neq', $data[$key]]; } + if ($model->where($map)->field($key)->find()) { return false; } @@ -589,18 +582,18 @@ class Validate */ protected function filter($value, $rule) { - if (is_int($rule)) { - $param = null; - } elseif (is_string($rule) && strpos($rule, ',')) { + if (is_string($rule) && strpos($rule, ',')) { list($rule, $param) = explode(',', $rule); } elseif (is_array($rule)) { $param = isset($rule[1]) ? $rule[1] : null; + } else { + $param = null; } return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); } /** - * 验证某个字段的值等于某个值的时候必须 + * 验证某个字段等于某个值的时候必须 * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 @@ -617,6 +610,24 @@ class Validate } } + /** + * 验证某个字段有值的情况下必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + if (!empty($val)) { + return !empty($value); + } else { + return true; + } + } + /** * 验证是否在范围内 * @access protected @@ -835,28 +846,39 @@ class Validate } /** - * 获取验证规则的默认提示信息 + * 获取验证规则的错误提示信息 * @access protected - * @param string $attribute 字段名称 - * @param string $type 验证类型名称 - * @param mixed $rule 验证规则 + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 * @return string */ - protected function getTypeMsg($attribute, $type, $rule) + protected function getRuleMsg($attribute, $title, $type, $rule) { - if (isset($this->typeMsg[$type])) { + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset($this->typeMsg[$type])) { + $msg = $this->typeMsg[$type]; + } else { + $msg = $title . '规则错误'; + } + // TODO 多语言支持 + if (is_string($msg) && false !== strpos($msg, ':')) { + // 变量替换 if (strpos($rule, ',')) { $array = array_pad(explode(',', $rule), 3, ''); } else { $array = array_pad([], 3, ''); } - return (false === strpos($this->typeMsg[$type], ':attribute') ? $attribute : '') . str_replace( - [':rule', ':attribute', ':1', ':2', ':3'], - [(string) $rule, $attribute, $array[0], $array[1], $array[2]], - $this->typeMsg[$type]); - } else { - return $attribute . '规则错误'; + $msg = str_replace( + [':attribute', ':rule', ':1', ':2', ':3'], + [$title, (string) $rule, $array[0], $array[1], $array[2]], + $msg); } + return $msg; } /** @@ -869,12 +891,12 @@ class Validate { if (empty($scene)) { // 读取指定场景 - $scene = $this->scene; + $scene = $this->currentScene; } - if (!empty($scene) && isset($this->config['scene'][$scene])) { + if (!empty($scene) && isset($this->scene[$scene])) { // 如果设置了验证适用场景 - $scene = $this->config['scene'][$scene]; + $scene = $this->scene[$scene]; if (is_string($scene)) { $scene = explode(',', $scene); } diff --git a/library/think/log/driver/File.php b/library/think/log/driver/File.php index 28070e10..d4242f29 100644 --- a/library/think/log/driver/File.php +++ b/library/think/log/driver/File.php @@ -53,8 +53,9 @@ class File } else { $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); } - $runtime = number_format(microtime(true) - START_TIME, 6); + $runtime = microtime(true) - START_TIME; $reqs = number_format(1 / $runtime, 2); + $runtime = number_format($runtime, 6); $time_str = " [运行时间:{$runtime}s] [吞吐率:{$reqs}req/s]"; $memory_use = number_format((memory_get_usage() - START_MEM) / 1024, 2); $memory_str = " [内存消耗:{$memory_use}kb]"; diff --git a/library/think/log/driver/Sae.php b/library/think/log/driver/Sae.php index c50d18f4..95f5f650 100644 --- a/library/think/log/driver/Sae.php +++ b/library/think/log/driver/Sae.php @@ -41,8 +41,9 @@ class Sae } else { $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); } - $runtime = number_format(microtime(true) - START_TIME, 6); + $runtime = microtime(true) - START_TIME; $reqs = number_format(1 / $runtime, 2); + $runtime = number_format($runtime, 6); $time_str = " [运行时间:{$runtime}s] [吞吐率:{$reqs}req/s]"; $memory_use = number_format((memory_get_usage() - START_MEM) / 1024, 2); $memory_str = " [内存消耗:{$memory_use}kb]"; diff --git a/library/think/log/driver/Socket.php b/library/think/log/driver/Socket.php index 00843774..23d6f6cf 100644 --- a/library/think/log/driver/Socket.php +++ b/library/think/log/driver/Socket.php @@ -59,8 +59,9 @@ class Socket if (!$this->check()) { return false; } - $runtime = number_format(microtime(true) - START_TIME, 6); + $runtime = microtime(true) - START_TIME; $reqs = number_format(1 / $runtime, 2); + $runtime = number_format($runtime, 6); $time_str = " [运行时间:{$runtime}s][吞吐率:{$reqs}req/s]"; $memory_use = number_format((memory_get_usage() - START_MEM) / 1024, 2); $memory_str = " [内存消耗:{$memory_use}kb]"; diff --git a/library/think/log/driver/Trace.php b/library/think/log/driver/Trace.php index 8227f5b2..03401bc8 100644 --- a/library/think/log/driver/Trace.php +++ b/library/think/log/driver/Trace.php @@ -43,8 +43,9 @@ class Trace return false; } // 获取基本信息 - $runtime = number_format(microtime(true) - START_TIME, 6); + $runtime = microtime(true) - START_TIME; $reqs = number_format(1 / $runtime, 2); + $runtime = number_format($runtime, 6); $mem = number_format((memory_get_usage() - START_MEM) / 1024, 2); // 页面Trace信息 diff --git a/library/traits/model/View.php b/library/traits/model/View.php index 87d0f111..a304fedb 100644 --- a/library/traits/model/View.php +++ b/library/traits/model/View.php @@ -31,28 +31,31 @@ trait View { if (empty($this->trueTableName)) { $tableName = ''; - foreach ($this->viewFields as $key => $view) { + $len = 0; + foreach ($this->viewFields as $name => $view) { // 获取数据表名称 if (isset($view['_table'])) { // 2011/10/17 添加实际表名定义支持 可以实现同一个表的视图 $tableName .= $view['_table']; } else { - $class = $key . 'Model'; - $model = class_exists($class) ? new $class() : M($key); - $tableName .= $model->getTableName(); + $tableName .= \think\Loader::model($name)->getTableName(); } // 表别名定义 - $tableName .= !empty($view['_as']) ? ' ' . $view['_as'] : ' ' . $key; + $tableName .= !empty($view['_as']) ? ' ' . $view['_as'] : ' ' . $name; // 支持ON 条件定义 $tableName .= !empty($view['_on']) ? ' ON ' . $view['_on'] : ''; - // 指定JOIN类型 例如 RIGHT INNER LEFT 下一个表有效 - $type = !empty($view['_type']) ? $view['_type'] : ''; - $tableName .= ' ' . strtoupper($type) . ' JOIN '; - $len = strlen($type . '_JOIN '); + if (!empty($view['_type'])) { + // 指定JOIN类型 例如 RIGHT INNER LEFT 下一个表有效 + $type = strtoupper($view['_type']); + $tableName .= ' ' . $type . ' JOIN '; + if ($view == end($this->viewFields)) { + $len = strlen($type) + 7; + } + } } - $tableName = substr($tableName, 0, -$len); - $this->trueTableName = $tableName; + $this->trueTableName = $len ? substr($tableName, 0, -$len) : $tableName; } + return $this->trueTableName; } @@ -64,26 +67,43 @@ trait View */ protected function _options_filter(&$options) { - if (isset($options['field'])) { - $options['field'] = $this->checkFields($options['field']); - } else { - $options['field'] = $this->checkFields(); + $viewFields = []; + foreach ($this->viewFields as $name => $val) { + $k = isset($val['_as']) ? $val['_as'] : $name; + $val = $this->_checkFields($name, $val); + foreach ($val as $key => $field) { + if (is_numeric($key)) { + $viewFields[$k . '.' . $field] = $field; + } elseif ('_' != substr($key, 0, 1)) { + // 以_开头的为特殊定义 + if (false !== strpos($key, '*') || false !== strpos($key, '(') || false !== strpos($key, '.')) { + //如果包含* 或者 使用了sql方法 则不再添加前面的表名 + $viewFields[$key] = $field; + } else { + $viewFields[$k . '.' . $key] = $field; + } + } + } } + if (empty($options['field'])) { + $options['field'] = '*'; + } + $options['field'] = $this->checkFields($options['field'], $viewFields); if (isset($options['group'])) { - $options['group'] = $this->checkGroup($options['group']); + $options['group'] = $this->checkGroup($options['group'], $viewFields); } if (isset($options['where'])) { - $options['where'] = $this->checkCondition($options['where']); + $options['where'] = $this->checkCondition($options['where'], $viewFields); } if (isset($options['order'])) { - $options['order'] = $this->checkOrder($options['order']); + $options['order'] = $this->checkOrder($options['order'], $viewFields); } } /** * 检查是否定义了所有字段 * @access protected - * @param string $name 模型名称 + * @param string $name 模型名称 * @param array $fields 字段数组 * @return array */ @@ -94,161 +114,143 @@ trait View $fields = array_merge($fields, \think\Loader::model($name)->getFields()); unset($fields[$pos]); } + return $fields; } /** * 检查条件中的视图字段 * @access protected - * @param mixed $data 条件表达式 + * @param mixed $data 条件表达式 + * @param array $fields 视图字段数组 * @return array */ - protected function checkCondition($where) + protected function checkCondition($where, $viewFields) { if (is_array($where)) { - $view = []; - // 检查视图字段 - foreach ($this->viewFields as $key => $val) { - $k = isset($val['_as']) ? $val['_as'] : $key; - $val = $this->_checkFields($key, $val); - foreach ($where as $name => $value) { - if (false !== $field = array_search($name, $val, true)) { - // 存在视图字段 - $_key = is_numeric($field) ? $k . '.' . $name : $k . '.' . $field; - $view[$_key] = $value; - unset($where[$name]); - } + $_where = []; + foreach ($where as $field => $value) { + if (false !== $k = array_search($field, $viewFields, true)) { + // 存在视图字段 + $_where[$k] = $value; + } else { + $_where[$field] = $value; } } - $where = array_merge($where, $view); + return $_where; + } else { + return $where; } - return $where; } /** * 检查Order表达式中的视图字段 * @access protected - * @param string $order 字段 + * @param string|array $order 字段 + * @param array $fields 视图字段数组 * @return string */ - protected function checkOrder($order = '') + protected function checkOrder($order = '', $viewFields) { - if (is_string($order) && !empty($order)) { - $orders = explode(',', $order); - $_order = []; - foreach ($orders as $order) { - $array = explode(' ', $order); - $field = $array[0]; - $sort = isset($array[1]) ? $array[1] : 'ASC'; - // 解析成视图字段 - foreach ($this->viewFields as $name => $val) { - $k = isset($val['_as']) ? $val['_as'] : $name; - $val = $this->_checkFields($name, $val); - if (false !== $_field = array_search($field, $val, true)) { - // 存在视图字段 - $field = is_numeric($_field) ? $k . '.' . $field : $k . '.' . $_field; - break; - } - } - $_order[] = $field . ' ' . $sort; - } - $order = implode(',', $_order); + if (empty($order)) { + return ''; + } elseif (is_string($order)) { + $order = explode(',', $order); } - return $order; + $_order = []; + foreach ($order as $key => $field) { + if (is_numeric($key)) { + if ($pos = strpos($field, ' ')) { + $sort = substr($field, $pos); + $field = substr($field, 0, $pos); + } else { + $sort = ''; + } + } else { + $sort = ' ' . $field; + $field = $key; + } + if (false !== $k = array_search($field, $viewFields, true)) { + // 存在视图字段 + $field = $k . $sort; + } + $_order[] = $field; + } + + return $_order; } /** * 检查Group表达式中的视图字段 * @access protected * @param string $group 字段 + * @param array $fields 视图字段数组 * @return string */ - protected function checkGroup($group = '') + protected function checkGroup($group = '', $viewFields) { - if (!empty($group)) { - $groups = explode(',', $group); - $_group = []; - foreach ($groups as $field) { - // 解析成视图字段 - foreach ($this->viewFields as $name => $val) { - $k = isset($val['_as']) ? $val['_as'] : $name; - $val = $this->_checkFields($name, $val); - if (false !== $_field = array_search($field, $val, true)) { - // 存在视图字段 - $field = is_numeric($_field) ? $k . '.' . $field : $k . '.' . $_field; - break; - } - } - $_group[] = $field; - } - $group = implode(',', $_group); + if (empty($group)) { + return ''; + } elseif (is_string($group)) { + $group = explode(',', $group); } - return $group; + foreach ($group as &$field) { + if (false !== $k = array_search($field, $viewFields, true)) { + // 存在视图字段 + $field = $k; + } + } + + return implode(',', $group); } /** * 检查fields表达式中的视图字段 * @access protected * @param string $fields 字段 + * @param array $fields 视图字段数组 * @return string */ - protected function checkFields($fields = '') + protected function checkFields($fields = '*', $viewFields) { - if (empty($fields) || '*' == $fields) { - // 获取全部视图字段 - $fields = []; - foreach ($this->viewFields as $name => $val) { - $k = isset($val['_as']) ? $val['_as'] : $name; - $val = $this->_checkFields($name, $val); - foreach ($val as $key => $field) { - if (is_numeric($key)) { - $fields[] = $k . '.' . $field . ' AS ' . $field; - } elseif ('_' != substr($key, 0, 1)) { - // 以_开头的为特殊定义 - if (false !== strpos($key, '*') || false !== strpos($key, '(') || false !== strpos($key, '.')) { - //如果包含* 或者 使用了sql方法 则不再添加前面的表名 - $fields[] = $key . ' AS ' . $field; - } else { - $fields[] = $k . '.' . $key . ' AS ' . $field; - } - } - } - } - $fields = implode(',', $fields); - } else { - if (!is_array($fields)) { + var_dump($fields); + if (is_string($fields)) { + if ('*' == $fields) { + return $viewFields; + } else { $fields = explode(',', $fields); } - // 解析成视图字段 - $array = []; - foreach ($fields as $key => $field) { - if (strpos($field, '(') || strpos(strtolower($field), ' as ')) { - // 使用了函数或者别名 - $array[] = $field; - unset($fields[$key]); + } + $_fields = []; + foreach ($fields as $key => $field) { + if (is_numeric($key)) { + if ($pos = strpos($field, ' ')) { + $alias = substr($field, $pos); + $field = substr($field, 0, $pos); + } else { + $alias = ' AS ' . $field; } + } else { + $alias = ' AS ' . $field; + $field = $key; } - foreach ($this->viewFields as $name => $val) { - $k = isset($val['_as']) ? $val['_as'] : $name; - $val = $this->_checkFields($name, $val); - foreach ($fields as $key => $field) { - if (false !== $_field = array_search($field, $val, true)) { - // 存在视图字段 - if (is_numeric($_field)) { - $array[] = $k . '.' . $field . ' AS ' . $field; - } elseif ('_' != substr($_field, 0, 1)) { - if (false !== strpos($_field, '*') || false !== strpos($_field, '(') || false !== strpos($_field, '.')) { - //如果包含* 或者 使用了sql方法 则不再添加前面的表名 - $array[] = $_field . ' AS ' . $field; - } else { - $array[] = $k . '.' . $_field . ' AS ' . $field; - } + if (strpos($field, '(')) { + if (preg_match_all('/(?fetch($template, ['name' => 'ThinkPHP']); $controllerFetch = $controller->fetch($template, ['name' => 'ThinkPHP']); $this->assertEquals($controllerFetch, $viewFetch); + $controllerFetch = $controller->display($template, ['name' => 'ThinkPHP']); + $this->assertEquals($controllerFetch, $viewFetch); } public function testShow() @@ -155,4 +157,31 @@ class controllerTest extends \PHPUnit_Framework_TestCase $controller->engine('php'); $this->assertEquals('php', $view->engine); } + + public function testValidate() + { + $controller = new Foo; + $data = [ + 'username' => 'username', + 'nickname' => 'nickname', + 'password' => '123456', + 'repassword' => '123456', + 'email' => 'abc@abc.com', + 'sex' => '0', + 'age' => '20', + 'code' => '1234', + ]; + + $validate = [ + ['username', 'length:5,15', '用户名长度为5到15个字符'], + ['nickname', 'require', '请填昵称'], + ['password', '[\w-]{6,15}', '密码长度为6到15个字符'], + ['repassword', 'confirm:password', '两次密码不一到致'], + ['email', 'filter:validate_email', '邮箱格式错误'], + ['sex', 'in:0,1', '性别只能为为男或女'], + ['age', 'between:1,80', '年龄只能在10-80之间'], + ]; + $result = $controller->validate($data, $validate); + $this->assertTrue($result); + } } diff --git a/tests/thinkphp/library/think/dbTest.php b/tests/thinkphp/library/think/dbTest.php new file mode 100644 index 00000000..d3159b04 --- /dev/null +++ b/tests/thinkphp/library/think/dbTest.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- + +/** + * Db类测试 + */ + +namespace tests\thinkphp\library\think; + +use \think\Db; + +class dbTest extends \PHPUnit_Framework_TestCase +{ + public function testConnect() + { + Db::connect('mysql://root@127.0.0.1/test#utf8'); + Db::execute('show databases'); + } + +} diff --git a/tests/thinkphp/library/think/debugTest.php b/tests/thinkphp/library/think/debugTest.php index 3817dd48..14d2b884 100644 --- a/tests/thinkphp/library/think/debugTest.php +++ b/tests/thinkphp/library/think/debugTest.php @@ -118,7 +118,7 @@ class debugTest extends \PHPUnit_Framework_TestCase { $useMem = Debug::getUseMem(); - $this->assertLessThan(20, explode(" ", $useMem)[0]); + $this->assertLessThan(30, explode(" ", $useMem)[0]); } /** @@ -135,7 +135,7 @@ class debugTest extends \PHPUnit_Framework_TestCase $str .= "mem"; } $memPeak = Debug::getMemPeak($start, $end); - $this->assertLessThan(400, explode(" ", $memPeak)[0]); + $this->assertLessThan(500, explode(" ", $memPeak)[0]); } /** diff --git a/tests/thinkphp/library/think/loaderTest.php b/tests/thinkphp/library/think/loaderTest.php index 8ad5673f..7e02138d 100644 --- a/tests/thinkphp/library/think/loaderTest.php +++ b/tests/thinkphp/library/think/loaderTest.php @@ -42,6 +42,30 @@ class loaderTest extends \PHPUnit_Framework_TestCase $this->assertEquals(true, Loader::autoload('top\test\Hello')); } + public function testAddNamespaceAlias() + { + Loader::addNamespaceAlias('top', 'top\test'); + Loader::addNamespaceAlias(['top' => 'top\test', 'app' => 'app\index']); + //$this->assertEquals(true, Loader::autoload('top\Hello')); + } + + public function testTable() + { + Loader::table('', [ + 'connection' => [ + 'type' => 'mysql', + 'database' => 'test', + 'username' => 'root', + 'password' => '', + ]]); + Loader::db('mysql://root@127.0.0.1/test#utf8'); + } + + public function testInstance() + { + Loader::instance('\think\Validate'); + } + public function testImport() { $this->assertEquals(true, Loader::import('think.log.driver.Sae')); diff --git a/tests/thinkphp/library/think/modelTest.php b/tests/thinkphp/library/think/modelTest.php index d5dfa1f3..74f3595b 100644 --- a/tests/thinkphp/library/think/modelTest.php +++ b/tests/thinkphp/library/think/modelTest.php @@ -36,62 +36,30 @@ class modelTest extends \PHPUnit_Framework_TestCase public function testValidate() { - /* - $model = new Model('', $this->getConfig()); - $data = $_POST = [ - 'username' => 'username', - 'nickname' => 'nickname', - 'password' => '123456', - 'repassword' => '123456', - 'mobile' => '13800000000', - 'email' => 'abc@abc.com', - 'sex' => '0', - 'age' => '20', - 'code' => '1234', - ]; + $model = new Model('', $this->getConfig()); + $data = [ + 'username' => 'username', + 'nickname' => 'nickname', + 'password' => '123456', + 'repassword' => '123456', + 'email' => 'abc@abc.com', + 'sex' => '0', + 'age' => '20', + 'code' => '1234', + ]; - $validate = [ - '__pattern__' => [ - 'mobile' => '/^1(?:[358]\d|7[6-8])\d{8}$/', - 'require' => '/.+/', - ], - '__all__' => [ - 'code' => function ($value, $data) { - return '1234' != $value ? 'code error' : true; - }, - ], - 'user' => [ - ['username', [ & $this, 'checkName'], '用户名长度为5到15个字符', 'callback', 'username'], - ['username', function ($value, $data) { - return 'admin' == $value ? '此用户名已被使用' : true; - }], - 'nickname' => ['require', '请填昵称'], - 'password' => ['[\w-]{6,15}', '密码长度为6到15个字符'], - 'repassword' => ['password', '两次密码不一到致', 'confirm'], - 'mobile' => ['mobile', '手机号错误'], - 'email' => ['validate_email', '邮箱格式错误', 'filter'], - 'sex' => ['0,1', '性别只能为为男或女', 'in'], - 'age' => ['1,80', '年龄只能在10-80之间', 'between'], - '__option__' => [ - 'scene' => [ - 'add' => 'username,nickname,password,repassword,mobile,email,age,code', - 'edit' => 'nickname,password,repassword,mobile,email,sex,age,code', - ], - 'value_validate' => 'email', - 'exists_validate' => 'password,repassword,code', - 'patch' => true, - ], - ], - ]; - Config::set('validate', $validate); - $result = $model->validate('user.add')->create(); - $this->assertEmpty($model->getError()); + $validate = [ + ['username', 'length:5,15', '用户名长度为5到15个字符'], + ['nickname', 'require', '请填昵称'], + ['password', '[\w-]{6,15}', '密码长度为6到15个字符'], + ['repassword', 'confirm:password', '两次密码不一到致'], + ['email', 'filter:validate_email', '邮箱格式错误'], + ['sex', 'in:0,1', '性别只能为为男或女'], + ['age', 'between:1,80', '年龄只能在10-80之间'], + ]; + $result = $model->validate($validate)->create($data); + $this->assertEmpty($model->getError()); - unset($data['password'], $data['repassword']); - $data['email'] = ''; - $result = $model->validate('user.edit')->create($data); - $this->assertEmpty($model->getError()); - */ } public function checkName($value, $field) diff --git a/tests/thinkphp/library/think/validateTest.php b/tests/thinkphp/library/think/validateTest.php index 88398e7f..3d97e8a4 100644 --- a/tests/thinkphp/library/think/validateTest.php +++ b/tests/thinkphp/library/think/validateTest.php @@ -47,39 +47,40 @@ class validateTest extends \PHPUnit_Framework_TestCase public function testRule() { $rule = [ - 'name' => 'require|alphaNum|max:25', - 'account' => 'alphaDash|min:4|length:4,30', + 'name' => 'require|alphaNum|max:25|expire:2016-1-1,2026-1-1', + 'account' => 'requireIf:name,thinkphp|alphaDash|min:4|length:4,30', 'age' => 'number|between:1,120', - 'email' => 'email', - 'url' => 'activeUrl', + 'email' => 'requireWith:name|email', + 'host' => 'activeUrl', + 'url' => 'url', 'ip' => 'ip', - 'score' => 'float|gt:60', + 'score' => 'float|gt:60|notBetween:90,100|notIn:70,80|lt:100|elt:100|egt:60', 'status' => 'integer|in:0,1,2', 'begin_time' => 'after:2016-3-18', 'end_time' => 'before:2016-10-01', 'info' => 'require|array', + 'info.name' => 'require|length:8|alpha|same:thinkphp', 'value' => 'same:100', 'bool' => 'boolean', - ]; $data = [ 'name' => 'thinkphp', 'account' => 'liuchen', 'age' => 10, 'email' => 'thinkphp@qq.com', - 'url' => 'thinkphp.cn', + 'host' => 'thinkphp.cn', + 'url' => 'http://thinkphp.cn/topic', 'ip' => '114.34.54.5', 'score' => '89.15', 'status' => 1, 'begin_time' => '2016-3-20', 'end_time' => '2016-5-1', - 'info' => [1, 2, 3], + 'info' => [1, 2, 3, 'name' => 'thinkphp'], 'zip' => '200000', 'date' => '16-3-8', 'ok' => 'yes', 'value' => 100, 'bool' => 'true', - ]; $validate = new Validate($rule); $validate->rule('zip', '/^\d{6}$/'); @@ -104,19 +105,6 @@ class validateTest extends \PHPUnit_Framework_TestCase ]); } - public function testConfig() - { - $validate = new Validate(); - $validate->config('value_validate', ['name', 'age', 'email']); - $validate->config([ - 'value_validate' => ['name', 'age', 'email'], - 'exists_validate' => ['zip'], - 'scene' => [ - 'edit' => ['name', 'age', 'email'], - ], - ]); - } - public function testMake() { $rule = [ @@ -169,4 +157,25 @@ class validateTest extends \PHPUnit_Framework_TestCase $this->assertEquals(true, $result); } + public function testSetTypeMsg() + { + $rule = [ + 'name|名称' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ['sex', 'in:1,2', '性别错误'], + ]; + $data = [ + 'name' => '', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + 'sex' => '3', + ]; + $validate = new Validate($rule); + $validate->setTypeMsg('require', ':attribute必须'); + $result = $validate->batch()->check($data); + $this->assertFalse($result); + $this->assertEquals(['name' => '名称必须', 'sex' => '性别错误'], $validate->getError()); + } + }