ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] * * * * * #1 数据验证器 >>数据验证 >对客户端发起请求的数据进行验证过滤处理 >>数据验证接口包括 >验证规则注册接口 >验证规则使用接口 ## 1-1 数据验证器创建 ### Validate::make() >>单例模式,创建全局唯一验证器实例对象 ~~~ public static function make($rules = [], $message = []) { if (is_null(self::$instance)) { self::$instance = new self($rules, $message); } return self::$instance; } ~~~ ### Validate->__construct() >>构造函数,验证器构造函数 ~~~ public function __construct(array $rules = [], $message = []) { $this->rule = array_merge($this->rule, $rules); $this->message = array_merge($this->message, $message); } ~~~ # 2 数据验证使用接口 >>使用注册的(验证规则) 对 (请求数据的字段) 进行验证 >验证器可以在控制器,模型的数据处理过程中使用。 ## 2-1 数据字段设置验证规则 >>在验证器构造函数的参数选项中注册需要验证的字段对应的规则 ~~~ $rules = [ 'name' => 'require|max:25', 'age' => 'number|between:1,120', ]; $validate = new Validate($rules); ~~~ >如上表示对数据的name字段验证require和max:25两个规则 >age的字段验证nnumber和between:1,120两个规则 ## 2-2 数据字段使用验证规则 >>调用验证的check()方法对数据相应字段进行验证 ~~~ $rule = [ 'name' => 'require|max:25', 'age' => 'number|between:1,120', 'email' => 'email', ]; $msg = [ 'name.require' => '名称必须', 'name.max' => '名称最多不能超过25个字符', 'age.number' => '年龄必须是数字', 'age.between' => '年龄只能在1-120之间', 'email' => '邮箱格式错误', ]; $data = [ 'name' => 'thinkphp', 'age' => 10, 'email' => 'thinkphp@qq.com', ]; $validate = new Validate($rule, $msg); $result = $validate->check($data); ~~~ ## 2-3 控制器调用验证 >> 继承think\Controller的控制器,可以调用validate方法进行验证 ~~~ $result = $this->validate( [ 'name' => 'thinkphp', 'email' => 'thinkphp@qq.com', ], [ 'name' => 'require|max:25', 'email' => 'email', ]); if(true !== $result){ // 验证失败 输出错误信息 dump($result); } ~~~ > 也可以调用外部定义的验证器子类 ~~~ ; 创建验证器子类 use think\Validate; class User extends Validate { protected $rule = [ 'name' => 'require|max:25', 'email' => 'email', ]; protected $message = [ 'name.require' => '用户名必须', 'email' => '邮箱格式错误', ]; protected $scene = [ 'add' => ['name','email'], 'edit' => ['email'], ]; } ; 调用验证器子类 $result = $this->validate($data,'User'); if(true !== $result){ // 验证失败 输出错误信息 dump($result); } ~~~ ## 2-4 模型调用验证 >> 也可在模型中调用验证器对数据特定字段进行验证 ~~~ $User = new User; $result = $User->validate( [ 'name' => 'require|max:25', 'email' => 'email', ], [ 'name.require' => '名称必须', 'name.max' => '名称最多不能超过25个字符', 'email' => '邮箱格式错误', ] )->save($data); if(false === $result){ // 验证失败 输出错误信息 dump($User->getError()); } ~~~ >>对于验证器子类与数据模型同名的可以如下调用验证器 ~~~ ;创建同名验证器 namespace app\index\validate; use think\Validate; class User extends Validate { protected $rule = [ 'name' => 'require|max:25', 'email' => 'email', ]; protected $message = [ 'name.require' => '用户名必须', 'email' => '邮箱格式错误', ]; protected $scene = [ 'add' => ['name','email'], 'edit' => ['email'], ]; } ;同名模型调用验证器 $User = new User; // 调用当前模型对应的User验证器类进行数据验证 $result = $User->validate(true)->save($data); if(false === $result){ // 验证失败 输出错误信息 dump($User->getError()); } ~~~ >>如果验证器名与模型名不同,则如下调用 ~~~ $User = new User; // 调用Member验证器类进行数据验证 $result = $User->validate('Member')->save($data); if(false === $result){ // 验证失败 输出错误信息 dump($User->getError()); } ~~~ ## 2-5 验证接口 ### $validate->check() >>对数据$data,调用$rule进行验证处理,也可以传入场景标识 ~~~ public function check($data, $rules = [], $scene = '') { $this->error = []; if (empty($rules)) { // 读取验证规则 $rules = $this->rule; } // 分析验证规则 $scene = $this->getScene($scene); if (is_array($scene)) { // 处理场景验证字段 $change = []; $array = []; foreach ($scene as $k => $val) { if (is_numeric($k)) { $array[] = $val; } else { $array[] = $k; $change[$k] = $val; } } } 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)) { if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { continue; } elseif (is_array($scene)) { if (!in_array($key, $array)) { continue; } elseif (isset($change[$key])) { // 重载某个验证规则 $rule = $change[$key]; } } } // 获取数据 支持二维数组 $value = $this->getDataValue($data, $key); // 字段验证 $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); if (true !== $result) { // 没有返回true 则表示验证失败 if (!empty($this->batch)) { // 批量验证 if (is_array($result)) { $this->error = array_merge($this->error, $result); } else { $this->error[$key] = $result; } } else { $this->error = $result; return false; } } } return !empty($this->error) ? false : true; } ~~~ ### $validate->checkItem() >>对单个数据字典进行验证 ~~~ protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) { if ($rules instanceof \Closure) { // 匿名函数验证 支持传入当前字段和所有字段两个数据 $result = call_user_func_array($rules, [$value, $data]); } else { // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] if (is_string($rules)) { $rules = explode('|', $rules); } $i = 0; foreach ($rules as $key => $rule) { if ($rule instanceof \Closure) { $result = call_user_func_array($rule, [$value, $data]); } else { // 判断验证类型 if (is_numeric($key) && strpos($rule, ':')) { list($type, $rule) = explode(':', $rule, 2); if (isset($this->alias[$type])) { // 判断别名 $type = $this->alias[$type]; } $info = $type; } elseif (is_numeric($key)) { $type = 'is'; $info = $rule; } else { $info = $type = $key; } // 如果不是require 有数据才会行验证 if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { // 验证类型 $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; // 验证数据 $result = call_user_func_array($callback, [$value, $rule, $data, $field]); } else { $result = true; } } if (false === $result) { // 验证失败 返回错误信息 if (isset($msg[$i])) { $message = $msg[$i]; } else { $message = $this->getRuleMsg($field, $title, $info, $rule); } return $message; } elseif (true !== $result) { // 返回自定义错误信息 return $result; } $i++; } } return true !== $result ? $result : true; } ~~~ #3 数据验证注册接口 >>注册接口用来注册字段的验证规则类型 >内置的验证规则类型实现为验证器类Validate的相应方法,如gt,lt等 ## 3-1 验证规则注册 ### Validate::extend() >> 注册验证规则类型,调用extend()注册特定类型验证规则与相应的回调方法 ~~~ public static function extend($type, $callback = null) { if (is_array($type)) { self::$type = array_merge(self::$type, $type); } else { self::$type[$type] = $callback; } } ~~~ ### Validate::setTypeMsg() >> 注册验证规则类型的提示信息 ,可以获取和修改特定类型验证规则的提示信息 ~~~ public static function setTypeMsg($type, $msg = null) { if (is_array($type)) { self::$typeMsg = array_merge(self::$typeMsg, $type); } else { self::$typeMsg[$type] = $msg; } } ~~~ ### Validate->message() >> 注册特定字段的特定类型验证规则提示信息 ~~~ public function message($name, $message = '') { if (is_array($name)) { $this->message = array_merge($this->message, $name); } else { $this->message[$name] = $message; } return $this; } ~~~ >使用方法如下 ~~~ $rule = [ 'name' => 'require|max:25', 'age' => 'number|between:1,120', 'email' => 'email', ]; $msg = [ 'name.require' => '名称必须', 'name.max' => '名称最多不能超过25个字符', 'age.number' => '年龄必须是数字', 'age.between' => '年龄必须在1~120之间', 'email' => '邮箱格式错误', ]; $data = [ 'name' => 'thinkphp', 'age' => 121, 'email' => 'thinkphp@qq.com', ]; $validate = new Validate($rule); ;注册特定数据字段的验证提示信息 $validate->message($msg); $result = $validate->check($data); if(!$result){ echo $validate->getError(); } ~~~ ## 3-2 内置验证规则 >>Validate类内置了基础类型的验证规则 ### $validate->confirm() >> 验证字段的是等于某个值 ~~~ protected function confirm($value, $rule, $data) { return $this->getDataValue($data, $rule) == $value; } ~~~ ### $validate->different() >> 验证字段是否不等于某个值 ~~~ protected function different($value, $rule, $data) { return $this->getDataValue($data, $rule) != $value; } ~~~ ### $validate->egt() >>验证字段是否大于等于某个值 ~~~ protected function egt($value, $rule) { return $value >= $rule; } ~~~ ### $validate->gt() >> 验证字段是否等于某个值 ~~~ protected function gt($value, $rule) { return $value > $rule; } ~~~ ### $validate->elt() >> 验证字段是否小于等于某个值 ~~~ protected function elt($value, $rule) { return $value <= $rule; } ~~~ ### $validate->lt() >> 验证字段是否小于某个值 ~~~ protected function lt($value, $rule) { return $value < $rule; } ~~~ ### $validate->eq() >> 验证字段是否等于某个值 ~~~ protected function eq($value, $rule) { return $value == $rule; } ~~~ ### $validate->is() >> 验证字段是否为有效格式 ~~~ protected function is($value, $rule, $data = []) { switch ($rule) { case 'require': // 必须 $result = !empty($value) || '0' == $value; break; case 'accepted': // 接受 $result = in_array($value, ['1', 'on', 'yes']); break; case 'date': // 是否是一个有效日期 $result = false !== strtotime($value); break; case 'alpha': // 只允许字母 $result = $this->regex($value, '/^[A-Za-z]+$/'); break; case 'alphaNum': // 只允许字母和数字 $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); break; case 'alphaDash': // 只允许字母、数字和下划线 破折号 $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); break; case 'chs': // 只允许汉字 $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); break; case 'chsAlpha': // 只允许汉字、字母 $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); break; case 'chsAlphaNum': // 只允许汉字、字母和数字 $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); break; case 'chsDash': // 只允许汉字、字母、数字和下划线_及破折号- $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); break; case 'activeUrl': // 是否为有效的网址 $result = checkdnsrr($value); break; case 'ip': // 是否为IP地址 $result = $this->filter($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6); break; case 'url': // 是否为一个URL地址 $result = $this->filter($value, FILTER_VALIDATE_URL); break; case 'float': // 是否为float $result = $this->filter($value, FILTER_VALIDATE_FLOAT); break; case 'number': $result = is_numeric($value); break; case 'integer': // 是否为整型 $result = $this->filter($value, FILTER_VALIDATE_INT); break; case 'email': // 是否为邮箱地址 $result = $this->filter($value, FILTER_VALIDATE_EMAIL); break; case 'boolean': // 是否为布尔值 $result = in_array($value, [0, 1, true, false]); break; case 'array': // 是否为数组 $result = is_array($value); break; case 'file': $result = $value instanceof File; break; case 'image': $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); break; case 'token': $result = $this->token($value, '__token__', $data); break; default: if (isset(self::$type[$rule])) { // 注册的验证规则 $result = call_user_func_array(self::$type[$rule], [$value]); } else { // 正则验证 $result = $this->regex($value, $rule); } } return $result; } ~~~ ### $validate->getImageType() >>判断图像类型 ~~~ protected function getImageType($image) { if (function_exists('exif_imagetype')) { return exif_imagetype($image); } else { $info = getimagesize($image); return $info[2]; } } ~~~ ### $validate->activeUrl() >> 判断是否为合格的域名或IP ~~~ protected function activeUrl($value, $rule) { return checkdnsrr($value, $rule); } ~~~ ### $validate->ip() >> 验证是否为有效IP ~~~ protected function ip($value, $rule) { if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } return $this->filter($value, FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4); } ~~~ ### $validate->fileExt() >>验证上传文件的后缀 ~~~ protected function fileExt($file, $rule) { if (!($file instanceof File)) { return false; } if (is_string($rule)) { $rule = explode(',', $rule); } if (is_array($file)) { foreach ($file as $item) { if (!$item->checkExt($rule)) { return false; } } return true; } else { return $file->checkExt($rule); } } ~~~ ### $validate->fileMime() >> 验证上传文件类型 ~~~ protected function fileMime($file, $rule) { if (!($file instanceof File)) { return false; } if (is_string($rule)) { $rule = explode(',', $rule); } if (is_array($file)) { foreach ($file as $item) { if (!$item->checkMime($rule)) { return false; } } return true; } else { return $file->checkMime($rule); } } ~~~ ### $validate->fileSize() >>验证上传文件大小 ~~~ protected function fileSize($file, $rule) { if (!($file instanceof File)) { return false; } if (is_array($file)) { foreach ($file as $item) { if (!$item->checkSize($rule)) { return false; } } return true; } else { return $file->checkSize($rule); } } ~~~ ### $validate->image() >>验证上传图片的大小与类型 ~~~ protected function image($file, $rule) { if (!($file instanceof File)) { return false; } $rule = explode(',', $rule); list($width, $height, $type) = getimagesize($file->getRealPath()); if (isset($rule[2])) { $imageType = strtolower($rule[2]); if ('jpeg' == $imageType) { $imageType = 'jpg'; } if (image_type_to_extension($type, false) != $imageType) { return false; } } list($w, $h) = $rule; return $w == $width && $h == $height; } ~~~ ### $validate->method() >>验证当前请求类型 ~~~ protected function method($value, $rule) { $method = Request::instance()->method(); return strtoupper($rule) == $method; } ~~~ ### $validate->deteFormat() >> 验证时间和日期格式 ~~~ protected function dateFormat($value, $rule) { $info = date_parse_from_format($rule, $value); return 0 == $info['warning_count'] && 0 == $info['error_count']; } ~~~ ### $validate->unique() >> 验证字段是否唯一 ~~~ protected function unique($value, $rule, $data, $field) { if (is_string($rule)) { $rule = explode(',', $rule); } $db = Db::name($rule[0]); $key = isset($rule[1]) ? $rule[1] : $field; if (strpos($key, '^')) { // 支持多个字段验证 $fields = explode('^', $key); foreach ($fields as $key) { $map[$key] = $data[$key]; } } elseif (strpos($key, '=')) { parse_str($key, $map); } else { $map[$key] = $data[$field]; } $pk = strval(isset($rule[3]) ? $rule[3] : $db->getPk()); if (isset($rule[2])) { $map[$pk] = ['neq', $rule[2]]; } elseif (isset($data[$pk])) { $map[$pk] = ['neq', $data[$pk]]; } if ($db->where($map)->field($pk)->find()) { return false; } return true; } ~~~ ### $validate->in() ~~~ protected function in($value, $rule) { return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } ~~~ ### $validate->notIn() ~~~ protected function notIn($value, $rule) { return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); } ~~~ ### $validate->between() ~~~ protected function between($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($min, $max) = $rule; return $value >= $min && $value <= $max; } ~~~ ### $validate->notbetween() ~~~ protected function notBetween($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($min, $max) = $rule; return $value < $min || $value > $max; } ~~~ ### $validate->length() ~~~ protected function length($value, $rule) { if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { $length = $value->getSize(); } else { $length = mb_strlen((string) $value); } if (strpos($rule, ',')) { // 长度区间 list($min, $max) = explode(',', $rule); return $length >= $min && $length <= $max; } else { // 指定长度 return $length == $rule; } } ~~~ ### $validate->min() ~~~ protected function min($value, $rule) { if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { $length = $value->getSize(); } else { $length = mb_strlen((string) $value); } return $length >= $rule; } ~~~ ### $validate->max() ~~~ protected function max($value, $rule) { if (is_array($value)) { $length = count($value); } elseif ($value instanceof File) { $length = $value->getSize(); } else { $length = mb_strlen((string) $value); } return $length <= $rule; } ~~~ ### $validate->before() ~~~ protected function before($value, $rule) { return strtotime($value) <= strtotime($rule); } ~~~ ### $validate->after() ~~~ protected function after($value, $rule) { return strtotime($value) >= strtotime($rule); } ~~~ ### $validate->expire() ~~~ protected function expire($value, $rule) { if (is_string($rule)) { $rule = explode(',', $rule); } list($start, $end) = $rule; if (!is_numeric($start)) { $start = strtotime($start); } if (!is_numeric($end)) { $end = strtotime($end); } return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; } ~~~ ### $validate->allowIp() ~~~ protected function allowIp($value, $rule) { return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); } ~~~ ### $validate->denyIp() ~~~ protected function denyIp($value, $rule) { return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); } ~~~ ### $validate->regex() ~~~ protected function regex($value, $rule) { if (isset($this->regex[$rule])) { $rule = $this->regex[$rule]; } if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { // 不是正则表达式则两端补上/ $rule = '/^' . $rule . '$/'; } return 1 === preg_match($rule, (string) $value); } ~~~ ### $validate->token() ~~~ protected function token($value, $rule, $data) { $rule = !empty($rule) ? $rule : '__token__'; if (!isset($data[$rule]) || !Session::has($rule)) { // 令牌数据无效 return false; } // 令牌验证 if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { // 防止重复提交 Session::delete($rule); // 验证完成销毁session return true; } // 开启TOKEN重置 Session::delete($rule); return false; } ~~~