├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Exception │ └── ValidateException.php ├── Facade │ └── Validate.php ├── Helper │ └── Str.php ├── Install.php ├── Validate.php ├── Validate │ └── ValidateRule.php ├── config │ └── plugin │ │ └── tinywan │ │ └── validate │ │ ├── app.php │ │ └── bootstrap.php └── helper.php └── tests ├── UserValidate.php └── ValidateTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | .idea 4 | .vscode 5 | .phpunit* 6 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webman Validate 插件 2 | 3 | [![Latest Stable Version](http://poser.pugx.org/tinywan/validate/v)](https://packagist.org/packages/tinywan/validate) 4 | [![Total Downloads](http://poser.pugx.org/tinywan/validate/downloads)](https://packagist.org/packages/tinywan/validate) 5 | [![webman-event](https://img.shields.io/github/v/release/tinywan/validate?include_prereleases)]() 6 | [![webman-event](https://img.shields.io/badge/build-passing-brightgreen.svg)]() 7 | [![webman-event](https://img.shields.io/github/last-commit/tinywan/validate/main)]() 8 | [![webman-event](https://img.shields.io/github/v/tag/tinywan/validate?color=ff69b4)]() 9 | 10 | 基于ThinkPHP6修改的可用于 [webman](https://www.workerman.net/doc/webman/) 的通用validate数据验证器。 11 | 12 | ## 安装 13 | 14 | ```shell 15 | composer require tinywan/validate 16 | ``` 17 | 18 | ## 基础用法 19 | 20 | ~~~php 21 | 'require|max:25', 30 | 'age' => 'require|number|between:1,120', 31 | 'email' => 'require|email' 32 | ]; 33 | 34 | protected array $message = [ 35 | 'name.require' => '名称必须', 36 | 'name.max' => '名称最多不能超过25个字符', 37 | 'age.require' => '年龄必须是数字', 38 | 'age.number' => '年龄必须是数字', 39 | 'age.between' => '年龄只能在1-120之间', 40 | 'email.require' => '邮箱必须是数字', 41 | 'email.email' => '邮箱格式错误' 42 | ]; 43 | } 44 | ~~~ 45 | 46 | 验证器调用代码如下: 47 | 48 | ~~~php 49 | $data = [ 50 | 'name' => 'Tinywan', 51 | 'age' => 24, 52 | 'email' => 'Tinywan@163.com' 53 | ]; 54 | $validate = new app\index\validate\UserValidate; 55 | 56 | if (!$validate->check($data)) { 57 | var_dump($validate->getError()); 58 | } 59 | ~~~ 60 | 61 | ## 助手函数(推荐) 62 | 63 | ```php 64 | $data = [ 65 | 'name' => 'Tinywan', 66 | 'age' => 24, 67 | 'email' => 'Tinywan@163.com' 68 | ]; 69 | validate($data, \app\index\validate\UserValidate::class); 70 | ``` 71 | > 验证错误会自动抛出异常 72 | 73 | ## 使用面板Facade 74 | 75 | ```php 76 | $validate = \Tinywan\Validate\Facade\Validate::rule('age', 'number|between:1,120') 77 | ->rule([ 78 | 'name' => 'require|max:25', 79 | 'email' => 'email' 80 | ]); 81 | $data = [ 82 | 'name' => 'tinywan', 83 | 'email' => 'tinywan@gmail.com' 84 | ]; 85 | if (!$validate->check($data)) { 86 | var_dump($validate->getError()); 87 | } 88 | ``` 89 | 90 | 更多用法可以参考6.0完全开发手册的[验证](https://www.kancloud.cn/manual/thinkphp6_0/1037623)章节 91 | 92 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinywan/validate", 3 | "description": "validate for webman plugin", 4 | "keywords": ["validate", "webman", "plugin"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Tinywan", 10 | "email": "756684177@qq.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.4", 15 | "ext-mbstring": "*", 16 | "ext-ctype": "*" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Tinywan\\Validate\\": "src" 21 | }, 22 | "files": [ 23 | "src/helper.php" 24 | ] 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Tinywan\\Tests\\": "tests/" 29 | } 30 | }, 31 | "require-dev": { 32 | "phpunit/phpunit": "^9.0", 33 | "workerman/webman": "^1.0" 34 | } 35 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Exception/ValidateException.php: -------------------------------------------------------------------------------- 1 | error = $error; 25 | $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; 26 | } 27 | 28 | /** 29 | * 获取验证错误信息 30 | * @access public 31 | * @return array|string 32 | */ 33 | public function getError() 34 | { 35 | return $this->error; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Facade/Validate.php: -------------------------------------------------------------------------------- 1 | {$name}(...$arguments); 100 | } 101 | } -------------------------------------------------------------------------------- /src/Helper/Str.php: -------------------------------------------------------------------------------- 1 | 10) { 111 | $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5); 112 | } 113 | if ($type != 4) { 114 | $chars = str_shuffle($chars); 115 | $str = substr($chars, 0, $length); 116 | } else { 117 | for ($i = 0; $i < $length; $i++) { 118 | $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1); 119 | } 120 | } 121 | return $str; 122 | } 123 | 124 | /** 125 | * 字符串转小写 126 | * 127 | * @param string $value 128 | * @return string 129 | */ 130 | public static function lower(string $value): string 131 | { 132 | return mb_strtolower($value, 'UTF-8'); 133 | } 134 | 135 | /** 136 | * 字符串转大写 137 | * 138 | * @param string $value 139 | * @return string 140 | */ 141 | public static function upper(string $value): string 142 | { 143 | return mb_strtoupper($value, 'UTF-8'); 144 | } 145 | 146 | /** 147 | * 获取字符串的长度 148 | * 149 | * @param string $value 150 | * @return int 151 | */ 152 | public static function length(string $value): int 153 | { 154 | return mb_strlen($value); 155 | } 156 | 157 | /** 158 | * 截取字符串 159 | * 160 | * @param string $string 161 | * @param int $start 162 | * @param int|null $length 163 | * @return string 164 | */ 165 | public static function substr(string $string, int $start, int $length = null): string 166 | { 167 | return mb_substr($string, $start, $length, 'UTF-8'); 168 | } 169 | 170 | /** 171 | * 驼峰转下划线 172 | * 173 | * @param string $value 174 | * @param string $delimiter 175 | * @return string 176 | */ 177 | public static function snake(string $value, string $delimiter = '_'): string 178 | { 179 | $key = $value; 180 | 181 | if (isset(static::$snakeCache[$key][$delimiter])) { 182 | return static::$snakeCache[$key][$delimiter]; 183 | } 184 | 185 | if (!ctype_lower($value)) { 186 | $value = preg_replace('/\s+/u', '', $value); 187 | 188 | $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); 189 | } 190 | 191 | return static::$snakeCache[$key][$delimiter] = $value; 192 | } 193 | 194 | /** 195 | * 下划线转驼峰(首字母小写) 196 | * 197 | * @param string $value 198 | * @return string 199 | */ 200 | public static function camel(string $value): string 201 | { 202 | if (isset(static::$camelCache[$value])) { 203 | return static::$camelCache[$value]; 204 | } 205 | 206 | return static::$camelCache[$value] = lcfirst(static::studly($value)); 207 | } 208 | 209 | /** 210 | * 下划线转驼峰(首字母大写) 211 | * 212 | * @param string $value 213 | * @return string 214 | */ 215 | public static function studly(string $value): string 216 | { 217 | $key = $value; 218 | 219 | if (isset(static::$studlyCache[$key])) { 220 | return static::$studlyCache[$key]; 221 | } 222 | 223 | $value = ucwords(str_replace(['-', '_'], ' ', $value)); 224 | 225 | return static::$studlyCache[$key] = str_replace(' ', '', $value); 226 | } 227 | 228 | /** 229 | * 转为首字母大写的标题格式 230 | * 231 | * @param string $value 232 | * @return string 233 | */ 234 | public static function title(string $value): string 235 | { 236 | return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); 237 | } 238 | } -------------------------------------------------------------------------------- /src/Install.php: -------------------------------------------------------------------------------- 1 | 'config/plugin/tinywan/validate', 14 | ); 15 | 16 | /** 17 | * Install 18 | * @return void 19 | */ 20 | public static function install() 21 | { 22 | static::installByRelation(); 23 | } 24 | 25 | /** 26 | * Uninstall 27 | * @return void 28 | */ 29 | public static function uninstall() 30 | { 31 | self::uninstallByRelation(); 32 | } 33 | 34 | /** 35 | * installByRelation 36 | * @return void 37 | */ 38 | public static function installByRelation() 39 | { 40 | foreach (static::$pathRelation as $source => $dest) { 41 | if ($pos = strrpos($dest, '/')) { 42 | $parent_dir = base_path().'/'.substr($dest, 0, $pos); 43 | if (!is_dir($parent_dir)) { 44 | mkdir($parent_dir, 0777, true); 45 | } 46 | } 47 | //symlink(__DIR__ . "/$source", base_path()."/$dest"); 48 | copy_dir(__DIR__ . "/$source", base_path()."/$dest"); 49 | } 50 | } 51 | 52 | /** 53 | * uninstallByRelation 54 | * @return void 55 | */ 56 | public static function uninstallByRelation() 57 | { 58 | foreach (static::$pathRelation as $source => $dest) { 59 | $path = base_path()."/$dest"; 60 | if (!is_dir($path) && !is_file($path)) { 61 | continue; 62 | } 63 | /*if (is_link($path) { 64 | unlink($path); 65 | }*/ 66 | remove_dir($path); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Validate.php: -------------------------------------------------------------------------------- 1 | ' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', 38 | ]; 39 | 40 | /** 41 | * 当前验证规则 42 | * @var array 43 | */ 44 | protected array $rule = []; 45 | 46 | /** 47 | * 验证提示信息 48 | * @var array 49 | */ 50 | protected array $message = []; 51 | 52 | /** 53 | * 验证字段描述 54 | * @var array 55 | */ 56 | protected array $field = []; 57 | 58 | /** 59 | * 默认规则提示 60 | * @var array 61 | */ 62 | protected array $typeMsg = [ 63 | 'require' => ':attribute require', 64 | 'must' => ':attribute must', 65 | 'number' => ':attribute must be numeric', 66 | 'integer' => ':attribute must be integer', 67 | 'float' => ':attribute must be float', 68 | 'boolean' => ':attribute must be bool', 69 | 'email' => ':attribute not a valid email address', 70 | 'mobile' => ':attribute not a valid mobile', 71 | 'array' => ':attribute must be a array', 72 | 'accepted' => ':attribute must be yes,on or 1', 73 | 'date' => ':attribute not a valid datetime', 74 | 'file' => ':attribute not a valid file', 75 | 'image' => ':attribute not a valid image', 76 | 'alpha' => ':attribute must be alpha', 77 | 'alphaNum' => ':attribute must be alpha-numeric', 78 | 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', 79 | 'activeUrl' => ':attribute not a valid domain or ip', 80 | 'chs' => ':attribute must be chinese', 81 | 'chsAlpha' => ':attribute must be chinese or alpha', 82 | 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', 83 | 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', 84 | 'url' => ':attribute not a valid url', 85 | 'ip' => ':attribute not a valid ip', 86 | 'dateFormat' => ':attribute must be dateFormat of :rule', 87 | 'in' => ':attribute must be in :rule', 88 | 'notIn' => ':attribute be notin :rule', 89 | 'between' => ':attribute must between :1 - :2', 90 | 'notBetween' => ':attribute not between :1 - :2', 91 | 'length' => 'size of :attribute must be :rule', 92 | 'max' => 'max size of :attribute must be :rule', 93 | 'min' => 'min size of :attribute must be :rule', 94 | 'after' => ':attribute cannot be less than :rule', 95 | 'before' => ':attribute cannot exceed :rule', 96 | 'expire' => ':attribute not within :rule', 97 | 'allowIp' => 'access IP is not allowed', 98 | 'denyIp' => 'access IP denied', 99 | 'confirm' => ':attribute out of accord with :2', 100 | 'different' => ':attribute cannot be same with :2', 101 | 'egt' => ':attribute must greater than or equal :rule', 102 | 'gt' => ':attribute must greater than :rule', 103 | 'elt' => ':attribute must less than or equal :rule', 104 | 'lt' => ':attribute must less than :rule', 105 | 'eq' => ':attribute must equal :rule', 106 | 'unique' => ':attribute has exists', 107 | 'regex' => ':attribute not conform to the rules', 108 | 'method' => 'invalid Request method', 109 | 'token' => 'invalid token', 110 | 'fileSize' => 'filesize not match', 111 | 'fileExt' => 'extensions to upload is not allowed', 112 | 'fileMime' => 'mimetype to upload is not allowed', 113 | ]; 114 | 115 | /** 116 | * 当前验证场景 117 | * @var string 118 | */ 119 | protected string $currentScene = ''; 120 | 121 | /** 122 | * 内置正则验证规则 123 | * @var array 124 | */ 125 | protected array $defaultRegex = [ 126 | 'alpha' => '/^[A-Za-z]+$/', 127 | 'alphaNum' => '/^[A-Za-z0-9]+$/', 128 | 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', 129 | 'chs' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}]+$/u', 130 | 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z]+$/u', 131 | 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9]+$/u', 132 | 'chsDash' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9\_\-]+$/u', 133 | 'mobile' => '/^1[3-9]\d{9}$/', 134 | 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/', 135 | 'zip' => '/\d{6}/', 136 | ]; 137 | 138 | /** 139 | * Filter_var 规则 140 | * @var array 141 | */ 142 | protected array $filter = [ 143 | 'email' => FILTER_VALIDATE_EMAIL, 144 | 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], 145 | 'integer' => FILTER_VALIDATE_INT, 146 | 'url' => FILTER_VALIDATE_URL, 147 | 'macAddr' => FILTER_VALIDATE_MAC, 148 | 'float' => FILTER_VALIDATE_FLOAT, 149 | ]; 150 | 151 | /** 152 | * 验证场景定义 153 | * @var array 154 | */ 155 | protected array $scene = []; 156 | 157 | /** 158 | * 验证失败错误信息 159 | * @var string|array 160 | */ 161 | protected $error = []; 162 | 163 | /** 164 | * 是否批量验证 165 | * @var bool 166 | */ 167 | protected bool $batch = false; 168 | 169 | /** 170 | * 验证失败是否抛出异常 171 | * @var bool 172 | */ 173 | protected bool $failException = false; 174 | 175 | /** 176 | * 场景需要验证的规则 177 | * @var array 178 | */ 179 | protected array $only = []; 180 | 181 | /** 182 | * 场景需要移除的验证规则 183 | * @var array 184 | */ 185 | protected array $remove = []; 186 | 187 | /** 188 | * 场景需要追加的验证规则 189 | * @var array 190 | */ 191 | protected array $append = []; 192 | 193 | /** 194 | * 验证正则定义 195 | * @var array 196 | */ 197 | protected array $regex = []; 198 | 199 | /** 200 | * 请求对象 201 | * @var Request 202 | */ 203 | protected Request $request; 204 | 205 | /** 206 | * @var Closure[] 207 | */ 208 | protected static array $maker = []; 209 | 210 | /** 211 | * 构造方法 212 | * @access public 213 | */ 214 | public function __construct() 215 | { 216 | if (!empty(static::$maker)) { 217 | foreach (static::$maker as $maker) { 218 | call_user_func($maker, $this); 219 | } 220 | } 221 | } 222 | 223 | /** 224 | * 设置服务注入 225 | * @access public 226 | * @param Closure $maker 227 | * @return void 228 | */ 229 | public static function maker(Closure $maker) 230 | { 231 | static::$maker[] = $maker; 232 | } 233 | 234 | /** 235 | * 设置Request对象 236 | * @access public 237 | * @param Request $request Request对象 238 | * @return void 239 | */ 240 | public function setRequest(Request $request) 241 | { 242 | $this->request = $request; 243 | } 244 | 245 | /** 246 | * 添加字段验证规则 247 | * @access protected 248 | * @param string|array $name 字段名称或者规则数组 249 | * @param mixed $rule 验证规则或者字段描述信息 250 | * @return $this 251 | */ 252 | public function rule($name, $rule = '') 253 | { 254 | if (is_array($name)) { 255 | $this->rule = $name + $this->rule; 256 | if (is_array($rule)) { 257 | $this->field = array_merge($this->field, $rule); 258 | } 259 | } else { 260 | $this->rule[$name] = $rule; 261 | } 262 | 263 | return $this; 264 | } 265 | 266 | /** 267 | * 注册验证(类型)规则 268 | * @access public 269 | * @param string $type 验证规则类型 270 | * @param callable|null $callback callback方法(或闭包) 271 | * @param string|null $message 验证失败提示信息 272 | * @return $this 273 | */ 274 | public function extend(string $type, callable $callback = null, ?string $message = null): Validate 275 | { 276 | $this->type[$type] = $callback; 277 | 278 | if ($message) { 279 | $this->typeMsg[$type] = $message; 280 | } 281 | 282 | return $this; 283 | } 284 | 285 | /** 286 | * 设置验证规则的默认提示信息 287 | * @access public 288 | * @param string|array $type 验证规则类型名称或者数组 289 | * @param string|null $msg 验证提示信息 290 | * @return void 291 | */ 292 | public function setTypeMsg($type, ?string $msg = null): void 293 | { 294 | if (is_array($type)) { 295 | $this->typeMsg = array_merge($this->typeMsg, $type); 296 | } else { 297 | $this->typeMsg[$type] = $msg; 298 | } 299 | } 300 | 301 | /** 302 | * 设置提示信息 303 | * @access public 304 | * @param array $message 错误信息 305 | * @return Validate 306 | */ 307 | public function message(array $message): Validate 308 | { 309 | $this->message = array_merge($this->message, $message); 310 | 311 | return $this; 312 | } 313 | 314 | /** 315 | * 设置验证场景 316 | * @access public 317 | * @param string $name 场景名 318 | * @return $this 319 | */ 320 | public function scene(string $name): Validate 321 | { 322 | // 设置当前场景 323 | $this->currentScene = $name; 324 | 325 | return $this; 326 | } 327 | 328 | /** 329 | * 判断是否存在某个验证场景 330 | * @access public 331 | * @param string $name 场景名 332 | * @return bool 333 | */ 334 | public function hasScene(string $name): bool 335 | { 336 | return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); 337 | } 338 | 339 | /** 340 | * 设置批量验证 341 | * @access public 342 | * @param bool $batch 是否批量验证 343 | * @return $this 344 | */ 345 | public function batch(bool $batch = true): Validate 346 | { 347 | $this->batch = $batch; 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * 设置验证失败后是否抛出异常 354 | * @access protected 355 | * @param bool $fail 是否抛出异常 356 | * @return $this 357 | */ 358 | public function failException(bool $fail = true): Validate 359 | { 360 | $this->failException = $fail; 361 | 362 | return $this; 363 | } 364 | 365 | /** 366 | * 指定需要验证的字段列表 367 | * @access public 368 | * @param array $fields 字段名 369 | * @return $this 370 | */ 371 | public function only(array $fields): Validate 372 | { 373 | $this->only = $fields; 374 | 375 | return $this; 376 | } 377 | 378 | /** 379 | * 移除某个字段的验证规则 380 | * @access public 381 | * @param string|array $field 字段名 382 | * @param mixed $rule 验证规则 true 移除所有规则 383 | * @return $this 384 | */ 385 | public function remove($field, $rule = null): Validate 386 | { 387 | if (is_array($field)) { 388 | foreach ($field as $key => $rule) { 389 | if (is_int($key)) { 390 | $this->remove($rule); 391 | } else { 392 | $this->remove($key, $rule); 393 | } 394 | } 395 | } else { 396 | if (is_string($rule)) { 397 | $rule = explode('|', $rule); 398 | } 399 | 400 | $this->remove[$field] = $rule; 401 | } 402 | 403 | return $this; 404 | } 405 | 406 | /** 407 | * 追加某个字段的验证规则 408 | * @access public 409 | * @param string|array $field 字段名 410 | * @param mixed $rule 验证规则 411 | * @return $this 412 | */ 413 | public function append($field, $rule = null): Validate 414 | { 415 | if (is_array($field)) { 416 | foreach ($field as $key => $rule) { 417 | $this->append($key, $rule); 418 | } 419 | } else { 420 | if (is_string($rule)) { 421 | $rule = explode('|', $rule); 422 | } 423 | 424 | $this->append[$field] = $rule; 425 | } 426 | 427 | return $this; 428 | } 429 | 430 | /** 431 | * 数据自动验证 432 | * @access public 433 | * @param array $data 数据 434 | * @param array $rules 验证规则 435 | * @return bool 436 | */ 437 | public function check(array $data, array $rules = []): bool 438 | { 439 | $this->error = []; 440 | 441 | if ($this->currentScene) { 442 | $this->getScene($this->currentScene); 443 | } 444 | 445 | if (empty($rules)) { 446 | // 读取验证规则 447 | $rules = $this->rule; 448 | } 449 | 450 | foreach ($this->append as $key => $rule) { 451 | if (!isset($rules[$key])) { 452 | $rules[$key] = $rule; 453 | unset($this->append[$key]); 454 | } 455 | } 456 | 457 | foreach ($rules as $key => $rule) { 458 | // field => 'rule1|rule2...' field => ['rule1','rule2',...] 459 | if (strpos($key, '|')) { 460 | // 字段|描述 用于指定属性名称 461 | [$key, $title] = explode('|', $key); 462 | } else { 463 | $title = $this->field[$key] ?? $key; 464 | } 465 | 466 | // 场景检测 467 | if (!empty($this->only) && !in_array($key, $this->only)) { 468 | continue; 469 | } 470 | 471 | // 获取数据 支持二维数组 472 | $value = $this->getDataValue($data, $key); 473 | 474 | // 字段验证 475 | if ($rule instanceof Closure) { 476 | $result = call_user_func_array($rule, [$value, $data]); 477 | } elseif ($rule instanceof ValidateRule) { 478 | // 验证因子 479 | $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); 480 | } else { 481 | $result = $this->checkItem($key, $value, $rule, $data, $title); 482 | } 483 | 484 | if (true !== $result) { 485 | // 没有返回true 则表示验证失败 486 | if (!empty($this->batch)) { 487 | // 批量验证 488 | $this->error[$key] = $result; 489 | } elseif ($this->failException) { 490 | throw new ValidateException($result); 491 | } else { 492 | $this->error = $result; 493 | return false; 494 | } 495 | } 496 | } 497 | 498 | if (!empty($this->error)) { 499 | if ($this->failException) { 500 | throw new ValidateException($this->error); 501 | } 502 | return false; 503 | } 504 | 505 | return true; 506 | } 507 | 508 | /** 509 | * 根据验证规则验证数据 510 | * @access public 511 | * @param mixed $value 字段值 512 | * @param mixed $rules 验证规则 513 | * @return bool 514 | */ 515 | public function checkRule($value, $rules): bool 516 | { 517 | if ($rules instanceof Closure) { 518 | return call_user_func_array($rules, [$value]); 519 | } elseif ($rules instanceof ValidateRule) { 520 | $rules = $rules->getRule(); 521 | } elseif (is_string($rules)) { 522 | $rules = explode('|', $rules); 523 | } 524 | 525 | foreach ($rules as $key => $rule) { 526 | if ($rule instanceof Closure) { 527 | $result = call_user_func_array($rule, [$value]); 528 | } else { 529 | // 判断验证类型 530 | [$type, $rule] = $this->getValidateType($key, $rule); 531 | 532 | $callback = $this->type[$type] ?? [$this, $type]; 533 | 534 | $result = call_user_func_array($callback, [$value, $rule]); 535 | } 536 | 537 | if (true !== $result) { 538 | if ($this->failException) { 539 | throw new ValidateException($result); 540 | } 541 | 542 | return $result; 543 | } 544 | } 545 | 546 | return true; 547 | } 548 | 549 | /** 550 | * 验证单个字段规则 551 | * @access protected 552 | * @param string $field 字段名 553 | * @param mixed $value 字段值 554 | * @param mixed $rules 验证规则 555 | * @param array $data 数据 556 | * @param string $title 字段描述 557 | * @param array $msg 提示信息 558 | * @return mixed 559 | */ 560 | protected function checkItem(string $field, $value, $rules, array $data, string $title = '', array $msg = []) 561 | { 562 | if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { 563 | // 字段已经移除 无需验证 564 | return true; 565 | } 566 | 567 | // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] 568 | if (is_string($rules)) { 569 | $rules = explode('|', $rules); 570 | } 571 | 572 | if (isset($this->append[$field])) { 573 | // 追加额外的验证规则 574 | $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); 575 | unset($this->append[$field]); 576 | } 577 | 578 | if (empty($rules)) { 579 | return true; 580 | } 581 | 582 | $i = 0; 583 | foreach ($rules as $key => $rule) { 584 | if ($rule instanceof Closure) { 585 | $result = call_user_func_array($rule, [$value, $data]); 586 | $info = is_numeric($key) ? '' : $key; 587 | } else { 588 | // 判断验证类型 589 | [$type, $rule, $info] = $this->getValidateType($key, $rule); 590 | 591 | if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { 592 | } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) { 593 | // 规则已经移除 594 | $i++; 595 | continue; 596 | } 597 | 598 | if (isset($this->type[$type])) { 599 | $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]); 600 | } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { 601 | $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); 602 | } else { 603 | $result = true; 604 | } 605 | } 606 | 607 | if (false === $result) { 608 | // 验证失败 返回错误信息 609 | if (!empty($msg[$i])) { 610 | $message = $msg[$i]; 611 | if (is_string($message) && strpos($message, '{%') === 0) { 612 | $message = substr($message, 2, -1); 613 | } 614 | } else { 615 | $message = $this->getRuleMsg($field, $title, $info, $rule); 616 | } 617 | 618 | return $message; 619 | } elseif (true !== $result) { 620 | // 返回自定义错误信息 621 | if (is_string($result) && false !== strpos($result, ':')) { 622 | $result = str_replace(':attribute', $title, $result); 623 | 624 | if (strpos($result, ':rule') && is_scalar($rule)) { 625 | $result = str_replace(':rule', (string) $rule, $result); 626 | } 627 | } 628 | 629 | return $result; 630 | } 631 | $i++; 632 | } 633 | 634 | return $result ?? true; 635 | } 636 | 637 | /** 638 | * 获取当前验证类型及规则 639 | * @access public 640 | * @param mixed $key 641 | * @param mixed $rule 642 | * @return array 643 | */ 644 | protected function getValidateType($key, $rule): array 645 | { 646 | // 判断验证类型 647 | if (!is_numeric($key)) { 648 | if (isset($this->alias[$key])) { 649 | // 判断别名 650 | $key = $this->alias[$key]; 651 | } 652 | return [$key, $rule, $key]; 653 | } 654 | 655 | if (strpos($rule, ':')) { 656 | [$type, $rule] = explode(':', $rule, 2); 657 | if (isset($this->alias[$type])) { 658 | // 判断别名 659 | $type = $this->alias[$type]; 660 | } 661 | $info = $type; 662 | } elseif (method_exists($this, $rule)) { 663 | $type = $rule; 664 | $info = $rule; 665 | $rule = ''; 666 | } else { 667 | $type = 'is'; 668 | $info = $rule; 669 | } 670 | 671 | return [$type, $rule, $info]; 672 | } 673 | 674 | /** 675 | * 验证是否和某个字段的值一致 676 | * @access public 677 | * @param mixed $value 字段值 678 | * @param mixed $rule 验证规则 679 | * @param array $data 数据 680 | * @param string $field 字段名 681 | * @return bool 682 | */ 683 | public function confirm($value, $rule, array $data = [], string $field = ''): bool 684 | { 685 | if ('' == $rule) { 686 | if (strpos($field, '_confirm')) { 687 | $rule = strstr($field, '_confirm', true); 688 | } else { 689 | $rule = $field . '_confirm'; 690 | } 691 | } 692 | 693 | return $this->getDataValue($data, $rule) === $value; 694 | } 695 | 696 | /** 697 | * 验证是否和某个字段的值是否不同 698 | * @access public 699 | * @param mixed $value 字段值 700 | * @param mixed $rule 验证规则 701 | * @param array $data 数据 702 | * @return bool 703 | */ 704 | public function different($value, $rule, array $data = []): bool 705 | { 706 | return $this->getDataValue($data, $rule) != $value; 707 | } 708 | 709 | /** 710 | * 验证是否大于等于某个值 711 | * @access public 712 | * @param mixed $value 字段值 713 | * @param mixed $rule 验证规则 714 | * @param array $data 数据 715 | * @return bool 716 | */ 717 | public function egt($value, $rule, array $data = []): bool 718 | { 719 | return $value >= $this->getDataValue($data, $rule); 720 | } 721 | 722 | /** 723 | * 验证是否大于某个值 724 | * @access public 725 | * @param mixed $value 字段值 726 | * @param mixed $rule 验证规则 727 | * @param array $data 数据 728 | * @return bool 729 | */ 730 | public function gt($value, $rule, array $data = []): bool 731 | { 732 | return $value > $this->getDataValue($data, $rule); 733 | } 734 | 735 | /** 736 | * 验证是否小于等于某个值 737 | * @access public 738 | * @param mixed $value 字段值 739 | * @param mixed $rule 验证规则 740 | * @param array $data 数据 741 | * @return bool 742 | */ 743 | public function elt($value, $rule, array $data = []): bool 744 | { 745 | return $value <= $this->getDataValue($data, $rule); 746 | } 747 | 748 | /** 749 | * 验证是否小于某个值 750 | * @access public 751 | * @param mixed $value 字段值 752 | * @param mixed $rule 验证规则 753 | * @param array $data 数据 754 | * @return bool 755 | */ 756 | public function lt($value, $rule, array $data = []): bool 757 | { 758 | return $value < $this->getDataValue($data, $rule); 759 | } 760 | 761 | /** 762 | * 验证是否等于某个值 763 | * @access public 764 | * @param mixed $value 字段值 765 | * @param mixed $rule 验证规则 766 | * @return bool 767 | */ 768 | public function eq($value, $rule): bool 769 | { 770 | return $value == $rule; 771 | } 772 | 773 | /** 774 | * 必须验证 775 | * @access public 776 | * @param mixed $value 字段值 777 | * @return bool 778 | */ 779 | public function must($value): bool 780 | { 781 | return !empty($value) || '0' == $value; 782 | } 783 | 784 | /** 785 | * 验证字段值是否为有效格式 786 | * @access public 787 | * @param mixed $value 字段值 788 | * @param string $rule 验证规则 789 | * @param array $data 数据 790 | * @return bool 791 | */ 792 | public function is($value, string $rule, array $data = []): bool 793 | { 794 | switch (Str::camel($rule)) { 795 | case 'require': 796 | // 必须 797 | $result = !empty($value) || '0' == $value; 798 | break; 799 | case 'accepted': 800 | // 接受 801 | $result = in_array($value, ['1', 'on', 'yes']); 802 | break; 803 | case 'date': 804 | // 是否是一个有效日期 805 | $result = false !== strtotime($value); 806 | break; 807 | case 'activeUrl': 808 | // 是否为有效的网址 809 | $result = checkdnsrr($value); 810 | break; 811 | case 'boolean': 812 | case 'bool': 813 | // 是否为布尔值 814 | $result = in_array($value, [true, false, 0, 1, '0', '1'], true); 815 | break; 816 | case 'number': 817 | $result = ctype_digit((string) $value); 818 | break; 819 | case 'alphaNum': 820 | $result = ctype_alnum($value); 821 | break; 822 | case 'array': 823 | // 是否为数组 824 | $result = is_array($value); 825 | break; 826 | case 'file': 827 | $result = $value instanceof File; 828 | break; 829 | case 'image': 830 | $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); 831 | break; 832 | case 'token': 833 | $result = $this->token('__token__', $data); 834 | break; 835 | default: 836 | if (isset($this->type[$rule])) { 837 | // 注册的验证规则 838 | $result = call_user_func_array($this->type[$rule], [$value]); 839 | } elseif (function_exists('ctype_' . $rule)) { 840 | // ctype验证规则 841 | $ctypeFun = 'ctype_' . $rule; 842 | $result = $ctypeFun($value); 843 | } elseif (isset($this->filter[$rule])) { 844 | // Filter_var验证规则 845 | $result = $this->filter($value, $this->filter[$rule]); 846 | } else { 847 | // 正则验证 848 | $result = $this->regex($value, $rule); 849 | } 850 | } 851 | 852 | return $result; 853 | } 854 | 855 | // 判断图像类型 856 | protected function getImageType($image) 857 | { 858 | if (function_exists('exif_imagetype')) { 859 | return exif_imagetype($image); 860 | } 861 | 862 | try { 863 | $info = getimagesize($image); 864 | return $info ? $info[2] : false; 865 | } catch (\Exception $e) { 866 | return false; 867 | } 868 | } 869 | 870 | /** 871 | * 验证表单令牌 872 | * @access public 873 | * @param mixed $rule 验证规则 874 | * @param array $data 数据 875 | * @return bool 876 | */ 877 | public function token(string $rule, array $data): bool 878 | { 879 | $rule = !empty($rule) ? $rule : '__token__'; 880 | return $this->request->checkToken($rule, $data); 881 | } 882 | 883 | /** 884 | * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 885 | * @access public 886 | * @param mixed $value 字段值 887 | * @param mixed $rule 验证规则 888 | * @return bool 889 | */ 890 | public function activeUrl(string $value, string $rule = 'MX'): bool 891 | { 892 | if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { 893 | $rule = 'MX'; 894 | } 895 | 896 | return checkdnsrr($value, $rule); 897 | } 898 | 899 | /** 900 | * 验证是否有效IP 901 | * @access public 902 | * @param mixed $value 字段值 903 | * @param mixed $rule 验证规则 ipv4 ipv6 904 | * @return bool 905 | */ 906 | public function ip($value, string $rule = 'ipv4'): bool 907 | { 908 | if (!in_array($rule, ['ipv4', 'ipv6'])) { 909 | $rule = 'ipv4'; 910 | } 911 | 912 | return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); 913 | } 914 | 915 | /** 916 | * 检测上传文件后缀 917 | * @access public 918 | * @param File $file 919 | * @param array|string $ext 允许后缀 920 | * @return bool 921 | */ 922 | protected function checkExt(File $file, $ext): bool 923 | { 924 | if (is_string($ext)) { 925 | $ext = explode(',', $ext); 926 | } 927 | 928 | return in_array(strtolower($file->getExtension()), $ext); 929 | } 930 | 931 | /** 932 | * 检测上传文件大小 933 | * @access public 934 | * @param File $file 935 | * @param int $size 最大大小 936 | * @return bool 937 | */ 938 | protected function checkSize(File $file, int $size): bool 939 | { 940 | return $file->getSize() <= (int) $size; 941 | } 942 | 943 | /** 944 | * 检测上传文件类型 945 | * @access public 946 | * @param File $file 947 | * @param array|string $mime 允许类型 948 | * @return bool 949 | */ 950 | protected function checkMime(File $file, $mime): bool 951 | { 952 | if (is_string($mime)) { 953 | $mime = explode(',', $mime); 954 | } 955 | 956 | return in_array(strtolower((string)$file->getMTime()), $mime); 957 | } 958 | 959 | /** 960 | * 验证时间和日期是否符合指定格式 961 | * @access public 962 | * @param mixed $value 字段值 963 | * @param mixed $rule 验证规则 964 | * @return bool 965 | */ 966 | public function dateFormat($value, $rule): bool 967 | { 968 | $info = date_parse_from_format($rule, $value); 969 | return 0 == $info['warning_count'] && 0 == $info['error_count']; 970 | } 971 | 972 | /** 973 | * 验证是否唯一 974 | * @access public 975 | * @param mixed $value 字段值 976 | * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 977 | * @param array $data 数据 978 | * @param string $field 验证字段名 979 | * @return bool 980 | */ 981 | public function unique($value, $rule, array $data = [], string $field = ''): bool 982 | { 983 | if (is_string($rule)) { 984 | $rule = explode(',', $rule); 985 | } 986 | 987 | if (false !== strpos($rule[0], '\\')) { 988 | // 指定模型类 989 | $db = new $rule[0]; 990 | } else { 991 | $db = Db::table($rule[0]); 992 | } 993 | 994 | $key = $rule[1] ?? $field; 995 | $map = []; 996 | 997 | if (strpos($key, '^')) { 998 | // 支持多个字段验证 999 | $fields = explode('^', $key); 1000 | foreach ($fields as $key) { 1001 | if (isset($data[$key])) { 1002 | $map[] = [$key, '=', $data[$key]]; 1003 | } 1004 | } 1005 | } elseif (isset($data[$field])) { 1006 | $map[] = [$key, '=', $data[$field]]; 1007 | } else { 1008 | $map = []; 1009 | } 1010 | 1011 | $pk = !empty($rule[3]) ? $rule[3] : ($db instanceof Model ? $db->getKeyName() : 'id'); 1012 | 1013 | if (is_string($pk)) { 1014 | if (isset($rule[2])) { 1015 | $map[] = [$pk, '<>', $rule[2]]; 1016 | } elseif (isset($data[$pk])) { 1017 | $map[] = [$pk, '<>', $data[$pk]]; 1018 | } 1019 | } 1020 | 1021 | if ($db->where($map)->first()) { 1022 | return false; 1023 | } 1024 | 1025 | return true; 1026 | } 1027 | 1028 | /** 1029 | * 使用filter_var方式验证 1030 | * @access public 1031 | * @param mixed $value 字段值 1032 | * @param mixed $rule 验证规则 1033 | * @return bool 1034 | */ 1035 | public function filter($value, $rule): bool 1036 | { 1037 | if (is_string($rule) && strpos($rule, ',')) { 1038 | [$rule, $param] = explode(',', $rule); 1039 | } elseif (is_array($rule)) { 1040 | $param = $rule[1] ?? 0; 1041 | $rule = $rule[0]; 1042 | } else { 1043 | $param = 0; 1044 | } 1045 | 1046 | return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); 1047 | } 1048 | 1049 | /** 1050 | * 验证某个字段等于某个值的时候必须 1051 | * @access public 1052 | * @param mixed $value 字段值 1053 | * @param mixed $rule 验证规则 1054 | * @param array $data 数据 1055 | * @return bool 1056 | */ 1057 | public function requireIf($value, $rule, array $data = []): bool 1058 | { 1059 | [$field, $val] = explode(',', $rule); 1060 | 1061 | if ($this->getDataValue($data, $field) == $val) { 1062 | return !empty($value) || '0' == $value; 1063 | } 1064 | 1065 | return true; 1066 | } 1067 | 1068 | /** 1069 | * 通过回调方法验证某个字段是否必须 1070 | * @access public 1071 | * @param mixed $value 字段值 1072 | * @param mixed $rule 验证规则 1073 | * @param array $data 数据 1074 | * @return bool 1075 | */ 1076 | public function requireCallback($value, $rule, array $data = []): bool 1077 | { 1078 | $result = call_user_func_array([$this, $rule], [$value, $data]); 1079 | 1080 | if ($result) { 1081 | return !empty($value) || '0' == $value; 1082 | } 1083 | 1084 | return true; 1085 | } 1086 | 1087 | /** 1088 | * 验证某个字段有值的情况下必须 1089 | * @access public 1090 | * @param mixed $value 字段值 1091 | * @param mixed $rule 验证规则 1092 | * @param array $data 数据 1093 | * @return bool 1094 | */ 1095 | public function requireWith($value, $rule, array $data = []): bool 1096 | { 1097 | $val = $this->getDataValue($data, $rule); 1098 | 1099 | if (!empty($val)) { 1100 | return !empty($value) || '0' == $value; 1101 | } 1102 | 1103 | return true; 1104 | } 1105 | 1106 | /** 1107 | * 验证某个字段没有值的情况下必须 1108 | * @access public 1109 | * @param mixed $value 字段值 1110 | * @param mixed $rule 验证规则 1111 | * @param array $data 数据 1112 | * @return bool 1113 | */ 1114 | public function requireWithout($value, $rule, array $data = []): bool 1115 | { 1116 | $val = $this->getDataValue($data, $rule); 1117 | 1118 | if (empty($val)) { 1119 | return !empty($value) || '0' == $value; 1120 | } 1121 | 1122 | return true; 1123 | } 1124 | 1125 | /** 1126 | * 验证是否在范围内 1127 | * @access public 1128 | * @param mixed $value 字段值 1129 | * @param mixed $rule 验证规则 1130 | * @return bool 1131 | */ 1132 | public function in($value, $rule): bool 1133 | { 1134 | return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); 1135 | } 1136 | 1137 | /** 1138 | * 验证是否不在某个范围 1139 | * @access public 1140 | * @param mixed $value 字段值 1141 | * @param mixed $rule 验证规则 1142 | * @return bool 1143 | */ 1144 | public function notIn($value, $rule): bool 1145 | { 1146 | return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); 1147 | } 1148 | 1149 | /** 1150 | * between验证数据 1151 | * @access public 1152 | * @param mixed $value 字段值 1153 | * @param mixed $rule 验证规则 1154 | * @return bool 1155 | */ 1156 | public function between($value, $rule): bool 1157 | { 1158 | if (is_string($rule)) { 1159 | $rule = explode(',', $rule); 1160 | } 1161 | [$min, $max] = $rule; 1162 | 1163 | return $value >= $min && $value <= $max; 1164 | } 1165 | 1166 | /** 1167 | * 使用notbetween验证数据 1168 | * @access public 1169 | * @param mixed $value 字段值 1170 | * @param mixed $rule 验证规则 1171 | * @return bool 1172 | */ 1173 | public function notBetween($value, $rule): bool 1174 | { 1175 | if (is_string($rule)) { 1176 | $rule = explode(',', $rule); 1177 | } 1178 | [$min, $max] = $rule; 1179 | 1180 | return $value < $min || $value > $max; 1181 | } 1182 | 1183 | /** 1184 | * 验证数据长度 1185 | * @access public 1186 | * @param mixed $value 字段值 1187 | * @param mixed $rule 验证规则 1188 | * @return bool 1189 | */ 1190 | public function length($value, $rule): bool 1191 | { 1192 | if (is_array($value)) { 1193 | $length = count($value); 1194 | } elseif ($value instanceof File) { 1195 | $length = $value->getSize(); 1196 | } else { 1197 | $length = mb_strlen((string) $value); 1198 | } 1199 | 1200 | if (is_string($rule) && strpos($rule, ',')) { 1201 | // 长度区间 1202 | [$min, $max] = explode(',', $rule); 1203 | return $length >= $min && $length <= $max; 1204 | } 1205 | 1206 | // 指定长度 1207 | return $length == $rule; 1208 | } 1209 | 1210 | /** 1211 | * 验证数据最大长度 1212 | * @access public 1213 | * @param mixed $value 字段值 1214 | * @param mixed $rule 验证规则 1215 | * @return bool 1216 | */ 1217 | public function max($value, $rule): bool 1218 | { 1219 | if (is_array($value)) { 1220 | $length = count($value); 1221 | } elseif ($value instanceof File) { 1222 | $length = $value->getSize(); 1223 | } else { 1224 | $length = mb_strlen((string) $value); 1225 | } 1226 | 1227 | return $length <= $rule; 1228 | } 1229 | 1230 | /** 1231 | * 验证数据最小长度 1232 | * @access public 1233 | * @param mixed $value 字段值 1234 | * @param mixed $rule 验证规则 1235 | * @return bool 1236 | */ 1237 | public function min($value, $rule): bool 1238 | { 1239 | if (is_array($value)) { 1240 | $length = count($value); 1241 | } elseif ($value instanceof File) { 1242 | $length = $value->getSize(); 1243 | } else { 1244 | $length = mb_strlen((string) $value); 1245 | } 1246 | 1247 | return $length >= $rule; 1248 | } 1249 | 1250 | /** 1251 | * 验证日期 1252 | * @access public 1253 | * @param mixed $value 字段值 1254 | * @param mixed $rule 验证规则 1255 | * @param array $data 数据 1256 | * @return bool 1257 | */ 1258 | public function after($value, $rule, array $data = []): bool 1259 | { 1260 | return strtotime($value) >= strtotime($rule); 1261 | } 1262 | 1263 | /** 1264 | * 验证日期 1265 | * @access public 1266 | * @param mixed $value 字段值 1267 | * @param mixed $rule 验证规则 1268 | * @return bool 1269 | */ 1270 | public function before($value, $rule): bool 1271 | { 1272 | return strtotime($value) <= strtotime($rule); 1273 | } 1274 | 1275 | /** 1276 | * 验证日期 1277 | * @access public 1278 | * @param mixed $value 字段值 1279 | * @param mixed $rule 验证规则 1280 | * @param array $data 数据 1281 | * @return bool 1282 | */ 1283 | public function afterWith($value, $rule, array $data = []): bool 1284 | { 1285 | $rule = $this->getDataValue($data, $rule); 1286 | return !is_null($rule) && strtotime($value) >= strtotime($rule); 1287 | } 1288 | 1289 | /** 1290 | * 验证日期 1291 | * @access public 1292 | * @param mixed $value 字段值 1293 | * @param mixed $rule 验证规则 1294 | * @param array $data 数据 1295 | * @return bool 1296 | */ 1297 | public function beforeWith($value, $rule, array $data = []): bool 1298 | { 1299 | $rule = $this->getDataValue($data, $rule); 1300 | return !is_null($rule) && strtotime($value) <= strtotime($rule); 1301 | } 1302 | 1303 | /** 1304 | * 验证有效期 1305 | * @access public 1306 | * @param mixed $value 字段值 1307 | * @param mixed $rule 验证规则 1308 | * @return bool 1309 | */ 1310 | public function expire($value, $rule): bool 1311 | { 1312 | if (is_string($rule)) { 1313 | $rule = explode(',', $rule); 1314 | } 1315 | 1316 | [$start, $end] = $rule; 1317 | 1318 | if (!is_numeric($start)) { 1319 | $start = strtotime($start); 1320 | } 1321 | 1322 | if (!is_numeric($end)) { 1323 | $end = strtotime($end); 1324 | } 1325 | 1326 | return time() >= $start && time() <= $end; 1327 | } 1328 | 1329 | /** 1330 | * 验证IP许可 1331 | * @access public 1332 | * @param mixed $value 字段值 1333 | * @param mixed $rule 验证规则 1334 | * @return bool 1335 | */ 1336 | public function allowIp($value, $rule): bool 1337 | { 1338 | return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); 1339 | } 1340 | 1341 | /** 1342 | * 验证IP禁用 1343 | * @access public 1344 | * @param mixed $value 字段值 1345 | * @param mixed $rule 验证规则 1346 | * @return bool 1347 | */ 1348 | public function denyIp($value, $rule): bool 1349 | { 1350 | return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); 1351 | } 1352 | 1353 | /** 1354 | * 使用正则验证数据 1355 | * @access public 1356 | * @param mixed $value 字段值 1357 | * @param mixed $rule 验证规则 正则规则或者预定义正则名 1358 | * @return bool 1359 | */ 1360 | public function regex($value, $rule): bool 1361 | { 1362 | if (isset($this->regex[$rule])) { 1363 | $rule = $this->regex[$rule]; 1364 | } elseif (isset($this->defaultRegex[$rule])) { 1365 | $rule = $this->defaultRegex[$rule]; 1366 | } 1367 | 1368 | if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { 1369 | // 不是正则表达式则两端补上/ 1370 | $rule = '/^' . $rule . '$/'; 1371 | } 1372 | 1373 | return is_scalar($value) && 1 === preg_match($rule, (string) $value); 1374 | } 1375 | 1376 | /** 1377 | * 获取错误信息 1378 | * @return array|string 1379 | */ 1380 | public function getError() 1381 | { 1382 | return $this->error; 1383 | } 1384 | 1385 | /** 1386 | * 获取数据值 1387 | * @access protected 1388 | * @param array $data 数据 1389 | * @param string $key 数据标识 支持二维 1390 | * @return mixed 1391 | */ 1392 | protected function getDataValue(array $data, $key) 1393 | { 1394 | if (is_numeric($key)) { 1395 | $value = $key; 1396 | } elseif (is_string($key) && strpos($key, '.')) { 1397 | // 支持多维数组验证 1398 | foreach (explode('.', $key) as $key) { 1399 | if (!isset($data[$key])) { 1400 | $value = null; 1401 | break; 1402 | } 1403 | $value = $data = $data[$key]; 1404 | } 1405 | } else { 1406 | $value = $data[$key] ?? null; 1407 | } 1408 | 1409 | return $value; 1410 | } 1411 | 1412 | /** 1413 | * 获取验证规则的错误提示信息 1414 | * @access protected 1415 | * @param string $attribute 字段英文名 1416 | * @param string $title 字段描述名 1417 | * @param string $type 验证规则名称 1418 | * @param mixed $rule 验证规则数据 1419 | * @return string|array 1420 | */ 1421 | protected function getRuleMsg(string $attribute, string $title, string $type, $rule) 1422 | { 1423 | if (isset($this->message[$attribute . '.' . $type])) { 1424 | $msg = $this->message[$attribute . '.' . $type]; 1425 | } elseif (isset($this->message[$attribute][$type])) { 1426 | $msg = $this->message[$attribute][$type]; 1427 | } elseif (isset($this->message[$attribute])) { 1428 | $msg = $this->message[$attribute]; 1429 | } elseif (isset($this->typeMsg[$type])) { 1430 | $msg = $this->typeMsg[$type]; 1431 | } elseif (0 === strpos($type, 'require')) { 1432 | $msg = $this->typeMsg['require']; 1433 | } else { 1434 | $msg = $title . 'not conform to the rules'; 1435 | } 1436 | 1437 | if (is_array($msg)) { 1438 | return $this->errorMsgIsArray($msg, $rule, $title); 1439 | } 1440 | 1441 | return $this->parseErrorMsg($msg, $rule, $title); 1442 | } 1443 | 1444 | /** 1445 | * 获取验证规则的错误提示信息 1446 | * @access protected 1447 | * @param string $msg 错误信息 1448 | * @param mixed $rule 验证规则数据 1449 | * @param string $title 字段描述名 1450 | * @return array 1451 | */ 1452 | protected function parseErrorMsg(string $msg, $rule, string $title) 1453 | { 1454 | if (is_array($msg)) { 1455 | return $this->errorMsgIsArray($msg, $rule, $title); 1456 | } 1457 | 1458 | // rule若是数组则转为字符串 1459 | if (is_array($rule)) { 1460 | $rule = implode(',', $rule); 1461 | } 1462 | 1463 | if (is_scalar($rule) && false !== strpos($msg, ':')) { 1464 | // 变量替换 1465 | if (is_string($rule) && strpos($rule, ',')) { 1466 | $array = array_pad(explode(',', $rule), 3, ''); 1467 | } else { 1468 | $array = array_pad([], 3, ''); 1469 | } 1470 | 1471 | $msg = str_replace( 1472 | [':attribute', ':1', ':2', ':3'], 1473 | [$title, $array[0], $array[1], $array[2]], 1474 | $msg 1475 | ); 1476 | 1477 | if (strpos($msg, ':rule')) { 1478 | $msg = str_replace(':rule', (string) $rule, $msg); 1479 | } 1480 | } 1481 | 1482 | return $msg; 1483 | } 1484 | 1485 | /** 1486 | * 错误信息数组处理 1487 | * @access protected 1488 | * @param array $msg 错误信息 1489 | * @param mixed $rule 验证规则数据 1490 | * @param string $title 字段描述名 1491 | * @return array 1492 | */ 1493 | protected function errorMsgIsArray(array $msg, $rule, string $title): array 1494 | { 1495 | foreach ($msg as $key => $val) { 1496 | if (is_string($val)) { 1497 | $msg[$key] = $this->parseErrorMsg($val, $rule, $title); 1498 | } 1499 | } 1500 | return $msg; 1501 | } 1502 | 1503 | /** 1504 | * 获取数据验证的场景 1505 | * @access protected 1506 | * @param string $scene 验证场景 1507 | * @return void 1508 | */ 1509 | protected function getScene(string $scene): void 1510 | { 1511 | $this->only = $this->append = $this->remove = []; 1512 | 1513 | if (method_exists($this, 'scene' . $scene)) { 1514 | call_user_func([$this, 'scene' . $scene]); 1515 | } elseif (isset($this->scene[$scene])) { 1516 | // 如果设置了验证适用场景 1517 | $this->only = $this->scene[$scene]; 1518 | } 1519 | } 1520 | 1521 | /** 1522 | * 动态方法 直接调用is方法进行验证 1523 | * @access public 1524 | * @param string $method 方法名 1525 | * @param array $args 调用参数 1526 | * @return bool 1527 | */ 1528 | public function __call(string $method, array $args) 1529 | { 1530 | if ('is' == strtolower(substr($method, 0, 2))) { 1531 | $method = substr($method, 2); 1532 | } 1533 | 1534 | $args[] = lcfirst($method); 1535 | 1536 | return call_user_func_array([$this, 'is'], $args); 1537 | } 1538 | } 1539 | -------------------------------------------------------------------------------- /src/Validate/ValidateRule.php: -------------------------------------------------------------------------------- 1 | rule[$name] = $rule; 95 | } else { 96 | $this->rule[] = $name; 97 | } 98 | 99 | $this->message[] = $msg; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * 获取验证规则 106 | * @access public 107 | * @return array 108 | */ 109 | public function getRule(): array 110 | { 111 | return $this->rule; 112 | } 113 | 114 | /** 115 | * 获取验证字段名称 116 | * @access public 117 | * @return string 118 | */ 119 | public function getTitle(): string 120 | { 121 | return $this->title ?: ''; 122 | } 123 | 124 | /** 125 | * 获取验证提示 126 | * @access public 127 | * @return array 128 | */ 129 | public function getMsg(): array 130 | { 131 | return $this->message; 132 | } 133 | 134 | /** 135 | * 设置验证字段名称 136 | * @access public 137 | * @return $this 138 | */ 139 | public function title(string $title): ValidateRule 140 | { 141 | $this->title = $title; 142 | 143 | return $this; 144 | } 145 | 146 | public function __call($method, $args) 147 | { 148 | if ('is' == strtolower(substr($method, 0, 2))) { 149 | $method = substr($method, 2); 150 | } 151 | 152 | array_unshift($args, lcfirst($method)); 153 | 154 | return call_user_func_array([$this, 'addItem'], $args); 155 | } 156 | 157 | public static function __callStatic($method, $args) 158 | { 159 | $rule = new static(); 160 | 161 | if ('is' == strtolower(substr($method, 0, 2))) { 162 | $method = substr($method, 2); 163 | } 164 | 165 | array_unshift($args, lcfirst($method)); 166 | 167 | return call_user_func_array([$rule, 'addItem'], $args); 168 | } 169 | } -------------------------------------------------------------------------------- /src/config/plugin/tinywan/validate/app.php: -------------------------------------------------------------------------------- 1 | true, 4 | ]; -------------------------------------------------------------------------------- /src/config/plugin/tinywan/validate/bootstrap.php: -------------------------------------------------------------------------------- 1 | rule($validate); 26 | } else { 27 | if (strpos($validate, '.')) { 28 | [$validate, $scene] = explode('.', $validate); 29 | } 30 | $v = new $validate(); 31 | if (!empty($scene)) { 32 | $v->scene($scene); 33 | } 34 | } 35 | 36 | return $v->message($message)->batch($batch)->failException($failException)->check($data); 37 | } 38 | } -------------------------------------------------------------------------------- /tests/UserValidate.php: -------------------------------------------------------------------------------- 1 | 'require|max:25', 18 | 'age' => 'require|number|between:1,120', 19 | 'email' => 'require|email' 20 | ]; 21 | 22 | protected array $message = [ 23 | 'name.require' => '名称必须', 24 | 'name.max' => '名称最多不能超过25个字符', 25 | 'age.require' => '年龄必须是数字', 26 | 'age.number' => '年龄必须是数字', 27 | 'age.between' => '年龄只能在1-120之间', 28 | 'email.require' => '邮箱必须是数字', 29 | 'email.email' => '邮箱格式错误' 30 | ]; 31 | } -------------------------------------------------------------------------------- /tests/ValidateTest.php: -------------------------------------------------------------------------------- 1 | 'Tinywan', 23 | 'age' => 24, 24 | 'email' => 'Tinywan@163.com' 25 | ]; 26 | $validate = new UserValidate(); 27 | self::assertIsBool($validate->check($data)); 28 | self::assertTrue($validate->check($data)); 29 | } 30 | 31 | /** 32 | * @desc: 助手函数测试 33 | */ 34 | public function testHelper() 35 | { 36 | $data = [ 37 | 'name' => 'Tinywan', 38 | 'age' => 24, 39 | 'email' => 'Tinywan@163.com' 40 | ]; 41 | self::assertIsBool(validate($data, UserValidate::class)); 42 | self::assertFalse(validate($data, UserValidate::class)); 43 | } 44 | 45 | /** 46 | * @desc: 助手面板 47 | */ 48 | public function testFacade() 49 | { 50 | $validate = \Tinywan\Validate\Facade\Validate::rule('age', 'number|between:1,120') 51 | ->rule([ 52 | 'name' => 'require|max:25', 53 | 'email' => 'email' 54 | ]); 55 | $data = [ 56 | 'name' => 'tinywan', 57 | 'email' => 'tinywan@gmail.com' 58 | ]; 59 | self::assertIsBool($validate->check($data)); 60 | } 61 | } --------------------------------------------------------------------------------