Passed
Push — 8.0 ( 75c977...e8c215 )
by liu
12:56
created

Validate::parseErrorMsg()   B

Complexity

Conditions 10
Paths 33

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 11.897

Importance

Changes 0
Metric Value
cc 10
eloc 20
c 0
b 0
f 0
nc 33
nop 3
dl 0
loc 37
ccs 11
cts 15
cp 0.7332
crap 11.897
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
// +----------------------------------------------------------------------
4
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
5
// +----------------------------------------------------------------------
6
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
7
// +----------------------------------------------------------------------
8
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
9
// +----------------------------------------------------------------------
10
// | Author: liu21st <[email protected]>
11
// +----------------------------------------------------------------------
12
declare (strict_types = 1);
13
14
namespace think;
15
16
use BackedEnum;
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Closure;
18
use think\contract\Enumable;
19
use think\exception\ValidateException;
20
use think\helper\Str;
21
use think\validate\ValidateRule;
22
use UnitEnum;
0 ignored issues
show
Bug introduced by
The type UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
24
/**
25
 * 数据验证类
26
 * @package think
27
 */
28
class Validate
29
{
30
    /**
31
     * 自定义验证类型
32
     * @var array
33
     */
34
    protected $type = [];
35
36
    /**
37
     * 验证类型别名
38
     * @var array
39
     */
40
    protected $alias = [
41
        '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', '<>' => 'neq',
42
    ];
43
44
    /**
45
     * 当前验证规则
46
     * @var array
47
     */
48
    protected $rule = [];
49
50
    /**
51
     * 验证提示信息
52
     * @var array
53
     */
54
    protected $message = [];
55
56
    /**
57
     * 验证字段描述
58
     * @var array
59
     */
60
    protected $field = [];
61
62
    /**
63
     * 默认规则提示
64
     * @var array
65
     */
66
    protected $typeMsg = [
67
        'require'     => ':attribute require',
68
        'must'        => ':attribute must',
69
        'number'      => ':attribute must be numeric',
70
        'integer'     => ':attribute must be integer',
71
        'float'       => ':attribute must be float',
72
        'string'      => ':attribute must be string',
73
        'boolean'     => ':attribute must be bool',
74
        'email'       => ':attribute not a valid email address',
75
        'mobile'      => ':attribute not a valid mobile',
76
        'array'       => ':attribute must be a array',
77
        'accepted'    => ':attribute must be yes,on,true or 1',
78
        'acceptedIf'  => ':attribute must be yes,on,true or 1',
79
        'declined'    => ':attribute must be no,off,false or 0',
80
        'declinedIf'  => ':attribute must be no,off,false or 0',
81
        'date'        => ':attribute not a valid datetime',
82
        'file'        => ':attribute not a valid file',
83
        'image'       => ':attribute not a valid image',
84
        'alpha'       => ':attribute must be alpha',
85
        'alphaNum'    => ':attribute must be alpha-numeric',
86
        'alphaDash'   => ':attribute must be alpha-numeric, dash, underscore',
87
        'activeUrl'   => ':attribute not a valid domain or ip',
88
        'chs'         => ':attribute must be chinese',
89
        'chsAlpha'    => ':attribute must be chinese or alpha',
90
        'chsAlphaNum' => ':attribute must be chinese,alpha-numeric',
91
        'chsDash'     => ':attribute must be chinese,alpha-numeric,underscore, dash',
92
        'url'         => ':attribute not a valid url',
93
        'ip'          => ':attribute not a valid ip',
94
        'dateFormat'  => ':attribute must be dateFormat of :rule',
95
        'in'          => ':attribute must be in :rule',
96
        'notIn'       => ':attribute be notin :rule',
97
        'between'     => ':attribute must between :1 - :2',
98
        'notBetween'  => ':attribute not between :1 - :2',
99
        'length'      => 'size of :attribute must be :rule',
100
        'max'         => 'max size of :attribute must be :rule',
101
        'min'         => 'min size of :attribute must be :rule',
102
        'after'       => ':attribute cannot be less than :rule',
103
        'before'      => ':attribute cannot exceed :rule',
104
        'expire'      => ':attribute not within :rule',
105
        'allowIp'     => 'access IP is not allowed',
106
        'denyIp'      => 'access IP denied',
107
        'confirm'     => ':attribute out of accord with :2',
108
        'different'   => ':attribute cannot be same with :2',
109
        'egt'         => ':attribute must greater than or equal :rule',
110
        'gt'          => ':attribute must greater than :rule',
111
        'elt'         => ':attribute must less than or equal :rule',
112
        'lt'          => ':attribute must less than :rule',
113
        'eq'          => ':attribute must equal :rule',
114
        'neq'         => ':attribute must not be equal to :rule',
115
        'unique'      => ':attribute has exists',
116
        'regex'       => ':attribute not conform to the rules',
117
        'method'      => 'invalid Request method',
118
        'token'       => 'invalid token',
119
        'fileSize'    => 'filesize not match',
120
        'fileExt'     => 'extensions to upload is not allowed',
121
        'fileMime'    => 'mimetype to upload is not allowed',
122
        'startWith'   => ':attribute must start with :rule',
123
        'endWith'     => ':attribute must end with :rule',
124
        'contain'     => ':attribute must contain :rule',
125
        'multipleOf'  => ':attribute must multiple :rule',
126
    ];
127
128
    /**
129
     * 当前验证场景
130
     * @var string
131
     */
132
    protected $currentScene;
133
134
    /**
135
     * 内置正则验证规则
136
     * @var array
137
     */
138
    protected $defaultRegex = [
139
        'alpha'       => '/^[A-Za-z]+$/',
140
        'alphaNum'    => '/^[A-Za-z0-9]+$/',
141
        'alphaDash'   => '/^[A-Za-z0-9\-\_]+$/',
142
        'chs'         => '/^[\p{Han}]+$/u',
143
        'chsAlpha'    => '/^[\p{Han}a-zA-Z]+$/u',
144
        'chsAlphaNum' => '/^[\p{Han}a-zA-Z0-9]+$/u',
145
        'chsDash'     => '/^[\p{Han}a-zA-Z0-9\_\-]+$/u',
146
        'mobile'      => '/^1[3-9]\d{9}$/',
147
        '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}$)/',
148
        'zip'         => '/\d{6}/',
149
    ];
150
151
    /**
152
     * Filter_var 规则
153
     * @var array
154
     */
155
    protected $filter = [
156
        'email'   => FILTER_VALIDATE_EMAIL,
157
        'ip'      => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6],
158
        'integer' => FILTER_VALIDATE_INT,
159
        'url'     => FILTER_VALIDATE_URL,
160
        'macAddr' => FILTER_VALIDATE_MAC,
161
        'float'   => FILTER_VALIDATE_FLOAT,
162
    ];
163
164
    /**
165
     * 验证场景定义
166
     * @var array
167
     */
168
    protected $scene = [];
169
170
    /**
171
     * 验证失败错误信息
172
     * @var string|array
173
     */
174
    protected $error = [];
175
176
    /**
177
     * 是否批量验证
178
     * @var bool
179
     */
180
    protected $batch = false;
181
182
    /**
183
     * 验证失败是否抛出异常
184
     * @var bool
185
     */
186
    protected $failException = false;
187
188
    /**
189
     * 必须验证的规则
190
     * @var array
191
     */
192
    protected $must = [];
193
194
    /**
195
     * 场景需要验证的规则
196
     * @var array
197
     */
198
    protected $only = [];
199
200
    /**
201
     * 场景需要移除的验证规则
202
     * @var array
203
     */
204
    protected $remove = [];
205
206
    /**
207
     * 场景需要追加的验证规则
208
     * @var array
209
     */
210
    protected $append = [];
211
212
    /**
213
     * 场景需要覆盖的验证规则
214
     * @var array
215
     */
216
    protected $replace = [];
217
218
    /**
219
     * 验证正则定义
220
     * @var array
221
     */
222
    protected $regex = [];
223
224
    /**
225
     * Db对象
226
     * @var Db
227
     */
228
    protected $db;
229
230
    /**
231
     * 语言对象
232
     * @var Lang
233
     */
234
    protected $lang;
235
236
    /**
237
     * 请求对象
238
     * @var Request
239
     */
240
    protected $request;
241
242
    /**
243
     * @var Closure[]
244
     */
245
    protected static $maker = [];
246
247
    /**
248
     * 构造方法
249
     * @access public
250
     */
251 18
    public function __construct()
252
    {
253 18
        if (!empty(static::$maker)) {
254
            foreach (static::$maker as $maker) {
255
                call_user_func($maker, $this);
256
            }
257
        }
258
    }
259
260
    /**
261
     * 设置服务注入
262
     * @access public
263
     * @param Closure $maker
264
     * @return void
265
     */
266
    public static function maker(Closure $maker)
267
    {
268
        static::$maker[] = $maker;
269
    }
270
271
    /**
272
     * 设置Lang对象
273
     * @access public
274
     * @param Lang $lang Lang对象
275
     * @return void
276
     */
277 6
    public function setLang(Lang $lang)
278
    {
279 6
        $this->lang = $lang;
280
    }
281
282
    /**
283
     * 设置Db对象
284
     * @access public
285
     * @param Db $db Db对象
286
     * @return void
287
     */
288
    public function setDb(Db $db)
289
    {
290
        $this->db = $db;
291
    }
292
293
    /**
294
     * 设置Request对象
295
     * @access public
296
     * @param Request $request Request对象
297
     * @return void
298
     */
299
    public function setRequest(Request $request)
300
    {
301
        $this->request = $request;
302
    }
303
304
    /**
305
     * 添加字段验证规则
306
     * @access protected
307
     * @param string|array $name 字段名称或者规则数组
308
     * @param mixed        $rule 验证规则或者字段描述信息
309
     * @return $this
310
     */
311 6
    public function rule(string | array $name, $rule = '')
312
    {
313 6
        if (is_array($name)) {
0 ignored issues
show
introduced by
The condition is_array($name) is always true.
Loading history...
314 6
            $this->rule = $name + $this->rule;
315 6
            if (is_array($rule)) {
316 4
                $this->field = array_merge($this->field, $rule);
317
            }
318
        } else {
319
            $this->rule[$name] = $rule;
320
        }
321
322 6
        return $this;
323
    }
324
325
    /**
326
     * 注册验证(类型)规则
327
     * @access public
328
     * @param string   $type     验证规则类型
329
     * @param callable $callback callback方法(或闭包)
330
     * @param string   $message  验证失败提示信息
331
     * @return $this
332
     */
333
    public function extend(string $type, callable $callback, ?string $message = null)
334
    {
335
        $this->type[$type] = $callback;
336
337
        if ($message) {
338
            $this->typeMsg[$type] = $message;
339
        }
340
341
        return $this;
342
    }
343
344
    /**
345
     * 设置验证规则的默认提示信息
346
     * @access public
347
     * @param string|array $type 验证规则类型名称或者数组
348
     * @param string       $msg  验证提示信息
349
     * @return void
350
     */
351
    public function setTypeMsg(string | array $type, ?string $msg = null): void
352
    {
353
        if (is_array($type)) {
0 ignored issues
show
introduced by
The condition is_array($type) is always true.
Loading history...
354
            $this->typeMsg = array_merge($this->typeMsg, $type);
355
        } else {
356
            $this->typeMsg[$type] = $msg;
357
        }
358
    }
359
360
    /**
361
     * 设置提示信息
362
     * @access public
363
     * @param array $message 错误信息
364
     * @return Validate
365
     */
366
    public function message(array $message)
367
    {
368
        $this->message = array_merge($this->message, $message);
369
370
        return $this;
371
    }
372
373
    /**
374
     * 设置验证场景
375
     * @access public
376
     * @param string $name 场景名
377
     * @return $this
378
     */
379
    public function scene(string $name)
380
    {
381
        // 设置当前场景
382
        $this->currentScene = $name;
383
384
        return $this;
385
    }
386
387
    /**
388
     * 判断是否存在某个验证场景
389
     * @access public
390
     * @param string $name 场景名
391
     * @return bool
392
     */
393
    public function hasScene(string $name): bool
394
    {
395
        return isset($this->scene[$name]) || method_exists($this, 'scene' . $name);
396
    }
397
398
    /**
399
     * 设置批量验证
400
     * @access public
401
     * @param bool $batch 是否批量验证
402
     * @return $this
403
     */
404
    public function batch(bool $batch = true)
405
    {
406
        $this->batch = $batch;
407
408
        return $this;
409
    }
410
411
    /**
412
     * 设置验证失败后是否抛出异常
413
     * @access protected
414
     * @param bool $fail 是否抛出异常
415
     * @return $this
416
     */
417
    public function failException(bool $fail = true)
418
    {
419
        $this->failException = $fail;
420
421
        return $this;
422
    }
423
424
    /**
425
     * 指定需要验证的字段列表
426
     * @access public
427
     * @param array $fields 字段名
428
     * @return $this
429
     */
430
    public function only(array $fields)
431
    {
432
        $this->only = $fields;
433
434
        return $this;
435
    }
436
437
    /**
438
     * 指定需要覆盖的字段验证规则
439
     * @access public
440
     * @param string $field 字段名
441
     * @param mixed  $rules 验证规则
442
     * @return $this
443
     */
444
    public function replace(string $field, $rules)
445
    {
446
        $this->replace[$field] = $rules;
447
448
        return $this;
449
    }
450
451
    /**
452
     * 移除某个字段的验证规则
453
     * @access public
454
     * @param string|array $field 字段名
455
     * @param mixed        $rule  验证规则 true 移除所有规则
456
     * @return $this
457
     */
458
    public function remove(string | array $field, $rule = null)
459
    {
460
        if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
461
            foreach ($field as $key => $rule) {
462
                if (is_int($key)) {
463
                    $this->remove($rule);
464
                } else {
465
                    $this->remove($key, $rule);
466
                }
467
            }
468
        } else {
469
            if (is_string($rule)) {
470
                $rule = explode('|', $rule);
471
            }
472
473
            $this->remove[$field] = $rule;
474
        }
475
476
        return $this;
477
    }
478
479
    /**
480
     * 追加某个字段的验证规则
481
     * @access public
482
     * @param string|array $field 字段名
483
     * @param mixed        $rule  验证规则
484
     * @return $this
485
     */
486
    public function append(string | array $field, $rule = null)
487
    {
488
        if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
489
            foreach ($field as $key => $rule) {
490
                $this->append($key, $rule);
491
            }
492
        } else {
493
            if (is_string($rule)) {
494
                $rule = explode('|', $rule);
495
            }
496
497
            $this->append[$field] = $rule;
498
        }
499
500
        return $this;
501
    }
502
503
    /**
504
     * 数据自动验证
505
     * @access public
506
     * @param array $data  数据
507
     * @param array $rules 验证规则
508
     * @return bool
509
     */
510 6
    public function check(array $data, array $rules = []): bool
511
    {
512 6
        $this->error = [];
513
514 6
        if ($this->currentScene) {
515
            $this->getScene($this->currentScene);
516
        }
517
518 6
        if (empty($rules)) {
519
            // 读取验证规则
520 6
            $rules = $this->rule;
521
        }
522
523 6
        foreach ($this->append as $key => $rule) {
524
            if (!isset($rules[$key])) {
525
                $rules[$key] = $rule;
526
                unset($this->append[$key]);
527
            }
528
        }
529
530 6
        foreach ($rules as $key => $rule) {
531
            // field => 'rule1|rule2...' field => ['rule1','rule2',...]
532 6
            if (str_contains($key, '|')) {
533
                // 字段|描述 用于指定属性名称
534
                [$key, $title] = explode('|', $key);
535
            } else {
536 6
                $title = $this->field[$key] ?? $key;
537
            }
538
539
            // 场景检测
540 6
            if (!empty($this->only) && (!in_array($key, $this->only) && !array_key_exists($key, $this->only))) {
541
                continue;
542
            }
543
544
            // 获取数据 支持二维数组
545 6
            $values = $this->getDataSet($data, $key);
546
547 6
            if (empty($values)) {
548
                if (is_string($rule)) {
549
                    $items = explode('|', $rule);
550
                } elseif (is_array($rule)) {
551
                    $items = $rule;
552
                }
553
554
                if (isset($items) && false !== array_search('require', $items)) {
555
                    $message = $this->getRuleMsg($key, $title, 'require', $rule);
556
                    throw new ValidateException($message, $key);
557
                }
558
            }
559
560
            // 字段数据因子验证
561 6
            foreach ($values as $value) {
562 6
                $result = $this->checkItem($key, $value, $rule, $data, $title);
563
564 6
                if (true !== $result) {
565
                    // 验证失败 记录错误信息
566 6
                    if (false === $result) {
567
                        $result = $this->getRuleMsg($key, $title, '', $rule);
568
                    }
569 6
570
                    $this->error[$key] = $result;
571
572 6
                    if (!empty($this->batch)) {
573 6
                        // 批量验证
574
                    } elseif ($this->failException) {
575
                        throw new ValidateException($result, $key);
576
                    } else {
577
                        return false;
578
                    }
579
                }
580
            }
581
        }
582
583
        if (!empty($this->error)) {
584
            if ($this->failException) {
585
                throw new ValidateException($this->error);
586
            }
587
            return false;
588
        }
589
590
        return true;
591
    }
592
593
    /**
594
     * 根据验证规则验证数据
595
     * @access public
596
     * @param mixed $value 字段值
597
     * @param mixed $rules 验证规则
598
     * @return bool
599
     */
600
    public function checkRule($value, $rules): bool
601
    {
602
        if ($rules instanceof Closure) {
603
            return call_user_func_array($rules, [$value]);
604
        } elseif ($rules instanceof ValidateRule) {
605
            $rules = $rules->getRule();
606
        } elseif (is_string($rules)) {
607
            $rules = explode('|', $rules);
608
        }
609
610
        foreach ($rules as $key => $rule) {
611
            if ($rule instanceof Closure) {
612
                $result = call_user_func_array($rule, [$value]);
613
            } else {
614
                // 判断验证类型
615
                [$type, $rule, $callback] = $this->getValidateType($key, $rule);
616
617
                $result = call_user_func_array($callback, [$value, $rule]);
618
            }
619
620
            if (true !== $result) {
621
                if ($this->failException) {
622
                    if (false === $result) {
623
                        $result = $this->getRuleMsg('', '', $type, $rule);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.
Loading history...
624
                    }
625
                    throw new ValidateException($result, $type);
626
                }
627
628
                return $result;
629
            }
630
        }
631
632
        return true;
633
    }
634
635
    /**
636
     * 验证单个字段规则
637
     * @access protected
638
     * @param string $field 字段名
639 6
     * @param mixed  $value 字段值
640
     * @param mixed  $rules 验证规则
641 6
     * @param array  $data  数据
642
     * @param string $title 字段描述
643
     * @param array  $msg   提示信息
644
     * @return mixed
645 6
     */
646
    protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = []): mixed
647
    {
648
        if ($rules instanceof Closure) {
649
            return call_user_func_array($rules, [$value, $data]);
650
        }
651 6
652
        if ($rules instanceof ValidateRule) {
653
            $title = $rules->getTitle() ?: $title;
654
            $msg   = $rules->getMsg();
655
            $rules = $rules->getRule();
656 6
        }
657
658 6
        if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
659
            // 字段已经移除 无需验证
660
            return true;
661
        }
662
663 6
        if (isset($this->replace[$field])) {
664 6
            $rules = $this->replace[$field];
665
        } elseif (isset($this->only[$field])) {
666
            $rules = $this->only[$field];
667 6
        }
668
669
        // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
670
        if (is_string($rules)) {
671
            $rules = explode('|', $rules);
672
        }
673 6
674
        if (isset($this->append[$field])) {
675
            // 追加额外的验证规则
676
            $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
677 6
            unset($this->append[$field]);
678 6
        }
679 6
680
        if (empty($rules)) {
681
            return true;
682 6
        }
683
684
        $i = 0;
685
        foreach ($rules as $key => $rule) {
686
            if ($rule instanceof Closure) {
687 6
                $result = call_user_func_array($rule, [$value, $data]);
688
                $type   = is_numeric($key) ? '' : $key;
689 6
            } elseif (is_subclass_of($rule, UnitEnum::class) || is_subclass_of($rule, Enumable::class)) {
690 6
                $result = $this->enum($value, $rule);
691
                $type   = is_numeric($key) ? '' : $key;
692
            } else {
693
                // 判断验证类型
694
                [$type, $rule, $callback] = $this->getValidateType($key, $rule);
695
696 6
                if (isset($this->append[$field]) && in_array($type, $this->append[$field])) {
697 6
                } elseif (isset($this->remove[$field]) && in_array($type, $this->remove[$field])) {
698
                    // 规则已经移除
699
                    $i++;
700
                    continue;
701
                }
702
703 6
                if ('must' == $type || str_starts_with($type, 'require') || in_array($type, $this->must) || (!is_null($value) && '' !== $value)) {
704
                    $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]);
705 6
                } else {
706
                    $result = true;
707
                }
708
            }
709
710
            if (false === $result) {
711 6
                // 验证失败 返回错误信息
712
                if (!empty($msg[$i])) {
713
                    $message = $msg[$i];
714 6
                    if (is_string($message) && str_starts_with($message, '{%')) {
715 6
                        $message = $this->lang->get(substr($message, 2, -1));
716
                    }
717
                } else {
718
                    $message = $this->getRuleMsg($field, $title, $type, $rule);
719
                }
720
721
                return $message;
722
            } elseif (true !== $result) {
723
                // 返回自定义错误信息
724
                return $this->parseUserErrorMessage($result, $title, $rule);
725
            }
726
            $i++;
727 6
        }
728
729
        return $result ?? true;
730 6
    }
731
732
    protected function parseUserErrorMessage($message, $title, $rule)
733
    {
734
        if (is_string($message) && str_contains($message, ':')) {
735
            $message = str_replace(':attribute', $title, $message);
736
737
            if (str_contains($message, ':rule') && is_scalar($rule)) {
738
                $message = str_replace(':rule', (string) $rule, $message);
739
            }
740 6
        }
741
742
        return $message;
743 6
    }
744 6
745
    /**
746 6
     * 获取当前验证类型及规则
747 6
     * @access public
748
     * @param mixed $key
749 6
     * @param mixed $rule
750 6
     * @return array
751
     */
752
    protected function getValidateType($key, $rule): array
753
    {
754 6
        // 判断验证类型
755
        $hasParam = true;
756 6
        if (!is_numeric($key)) {
757
            $type = $key;
758
        } elseif (str_contains($rule, ':')) {
759
            [$type, $rule] = explode(':', $rule, 2);
760 6
        } else {
761 6
            $type     = $rule;
762 6
            $hasParam = false;
763 6
        }
764
765 6
        // 验证类型别名
766
        $type = $this->alias[$type] ?? $type;
767
768
        if (isset($this->type[$type])) {
769 6
            // 自定义验证
770
            $call = $this->type[$type];
771
        } else {
772
            $method = Str::camel($type);
773
            if (method_exists($this, $method)) {
774
                $call = [$this, $method];
775
                $rule = $hasParam ? $rule : '';
776
            } else {
777
                $call = [$this, 'is'];
778
            }
779
        }
780
781
        return [$type, $rule, $call];
782
    }
783
784
    /**
785
     * 验证是否和某个字段的值一致
786
     * @access public
787
     * @param mixed  $value 字段值
788
     * @param mixed  $rule  验证规则
789
     * @param array  $data  数据
790
     * @param string $field 字段名
791
     * @return bool
792
     */
793
    public function confirm($value, $rule, array $data = [], string $field = ''): bool
794
    {
795
        if ('' == $rule) {
796
            if (str_contains($field, '_confirm')) {
797
                $rule = strstr($field, '_confirm', true);
798
            } else {
799
                $rule = $field . '_confirm';
800
            }
801
        }
802
803
        return $this->getDataValue($data, $rule) === $value;
804
    }
805
806
    /**
807
     * 验证是否和某个字段的值是否不同
808
     * @access public
809
     * @param mixed $value 字段值
810
     * @param mixed $rule  验证规则
811
     * @param array $data  数据
812
     * @return bool
813
     */
814
    public function different($value, $rule, array $data = []): bool
815 3
    {
816
        return $this->getDataValue($data, $rule) != $value;
817 3
    }
818
819
    /**
820
     * 验证是否大于等于某个值
821
     * @access public
822
     * @param mixed $value 字段值
823
     * @param mixed $rule  验证规则
824
     * @param array $data  数据
825
     * @return bool
826
     */
827
    public function egt($value, $rule, array $data = []): bool
828 3
    {
829
        return $value >= $this->getDataValue($data, $rule);
830 3
    }
831
832
    /**
833
     * 验证是否大于某个值
834
     * @access public
835
     * @param mixed $value 字段值
836
     * @param mixed $rule  验证规则
837
     * @param array $data  数据
838
     * @return bool
839
     */
840
    public function gt($value, $rule, array $data = []): bool
841 3
    {
842
        return $value > $this->getDataValue($data, $rule);
843 3
    }
844
845
    /**
846
     * 验证是否小于等于某个值
847
     * @access public
848
     * @param mixed $value 字段值
849
     * @param mixed $rule  验证规则
850
     * @param array $data  数据
851
     * @return bool
852
     */
853
    public function elt($value, $rule, array $data = []): bool
854 3
    {
855
        return $value <= $this->getDataValue($data, $rule);
856 3
    }
857
858
    /**
859
     * 验证是否小于某个值
860
     * @access public
861
     * @param mixed $value 字段值
862
     * @param mixed $rule  验证规则
863
     * @param array $data  数据
864
     * @return bool
865
     */
866 3
    public function lt($value, $rule, array $data = []): bool
867
    {
868 3
        return $value < $this->getDataValue($data, $rule);
869
    }
870
871
    /**
872
     * 验证是否等于某个值
873
     * @access public
874
     * @param mixed $value 字段值
875
     * @param mixed $rule  验证规则
876
     * @return bool
877
     */
878 3
    public function eq($value, $rule): bool
879
    {
880 3
        return $value == $rule;
881
    }
882
883
    /**
884
     * 验证是否不等于某个值
885
     * @access public
886
     * @param mixed $value 字段值
887
     * @param mixed $rule  验证规则
888
     * @return bool
889
     */
890
    public function neq($value, $rule): bool
891
    {
892
        return $value != $rule;
893
    }
894
895
    /**
896
     * 必须验证
897
     * @access public
898
     * @param mixed $value 字段值
899
     * @param mixed $rule  验证规则
900
     * @return bool
901
     */
902
    public function must($value, $rule = null): bool
903 12
    {
904
        return !empty($value) || '0' == $value;
905 12
    }
906
907
    /**
908
     * 验证字段值是否为有效格式
909
     * @access public
910
     * @param mixed  $value 字段值
911
     * @param string $rule  验证规则
912
     * @param array  $data  数据
913
     * @return bool
914
     */
915
    public function is($value, string $rule, array $data = []): bool
916
    {
917
        $call = function ($value, $rule) {
918 12
            if (function_exists('ctype_' . $rule)) {
919
                // ctype验证规则
920 12
                $ctypeFun = 'ctype_' . $rule;
921 12
                $result   = $ctypeFun((string) $value);
922 12
            } elseif (isset($this->filter[$rule])) {
923 12
                // Filter_var验证规则
924 12
                $result = $this->filter($value, $this->filter[$rule]);
925 12
            } else {
926 12
                // 正则验证
927 12
                $result = $this->regex($value, $rule);
928 12
            }
929 12
            return $result;
930 12
        };
931 12
932 12
        return match (Str::camel($rule)) {
933 12
            'require' => !empty($value) || '0' == $value, // 必须
934 12
            'accepted' => in_array($value, ['1', 'on', 'yes', 'true', 1, true], true), // 接受
935 12
            'declined' => in_array($value, ['0', 'off', 'no', 'false', 0, false], true), // 不接受
936
            'date' => false !== strtotime($value), // 是否是一个有效日期
937
            'activeUrl' => checkdnsrr($value), // 是否为有效的网址
938
            'boolean', 'bool' => in_array($value, [true, false, 0, 1, '0', '1'], true), // 是否为布尔值
939
            'number' => ctype_digit((string) $value),
940
            'alphaNum' => ctype_alnum($value),
941
            'array'    => is_array($value), // 是否为数组
942
            'string'   => is_string($value),
943
            'file'     => $value instanceof File,
944
            'image'    => $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]),
945
            'token'    => $this->token($value, '__token__', $data),
946
            default    => $call($value, $rule),
947
        };
948
    }
949
950
    // 判断图像类型
951
    protected function getImageType($image)
952
    {
953
        if (function_exists('exif_imagetype')) {
954
            return exif_imagetype($image);
955
        }
956
957
        try {
958
            $info = getimagesize($image);
959
            return $info ? $info[2] : false;
960
        } catch (\Exception $e) {
961
            return false;
962
        }
963
    }
964
965
    /**
966
     * 验证表单令牌
967
     * @access public
968
     * @param mixed $value 字段值
969
     * @param mixed $rule  验证规则
970
     * @param array $data  数据
971
     * @return bool
972
     */
973
    public function token($value, string $rule, array $data): bool
974
    {
975
        $rule = !empty($rule) ? $rule : '__token__';
976
        return $this->request->checkToken($rule, $data);
977
    }
978
979
    /**
980
     * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
981
     * @access public
982
     * @param mixed $value 字段值
983
     * @param mixed $rule  验证规则
984
     * @return bool
985
     */
986
    public function activeUrl(string $value, string $rule = 'MX'): bool
987
    {
988
        if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
989
            $rule = 'MX';
990
        }
991
992
        return checkdnsrr($value, $rule);
993
    }
994
995
    /**
996
     * 验证是否有效IP
997
     * @access public
998
     * @param mixed $value 字段值
999
     * @param mixed $rule  验证规则 ipv4 ipv6
1000
     * @return bool
1001
     */
1002
    public function ip($value, string $rule = 'ipv4'): bool
1003
    {
1004
        if (!in_array($rule, ['ipv4', 'ipv6'])) {
1005
            $rule = 'ipv4';
1006
        }
1007
1008
        return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
1009
    }
1010
1011
    /**
1012
     * 检测是否以某个字符串开头
1013
     * @access public
1014
     * @param mixed $value 字段值
1015
     * @param string $rule  验证规则
1016
     * @return bool
1017
     */
1018
    public function startWith($value, string $rule): bool
1019
    {
1020
        return is_string($value) && str_starts_with($value, $rule);
1021
    }
1022
1023
    /**
1024
     * 检测是否以某个字符串结尾
1025
     * @access public
1026
     * @param mixed $value 字段值
1027
     * @param string $rule  验证规则
1028
     * @return bool
1029
     */
1030
    public function endWith($value, string $rule): bool
1031
    {
1032
        return is_string($value) && str_ends_with($value, $rule);
1033
    }
1034
1035
    /**
1036
     * 检测是否以包含某个字符串
1037
     * @access public
1038
     * @param mixed $value 字段值
1039
     * @param string $rule  验证规则
1040
     * @return bool
1041
     */
1042
    public function contain($value, string $rule): bool
1043
    {
1044
        return is_string($value) && str_contains($value, $rule);
1045
    }
1046
1047
    /**
1048
     * 检测上传文件后缀
1049
     * @access public
1050
     * @param File         $file
1051
     * @param array|string $ext 允许后缀
1052
     * @return bool
1053
     */
1054
    protected function checkExt(File $file, $ext): bool
1055
    {
1056
        if (is_string($ext)) {
1057
            $ext = explode(',', $ext);
1058
        }
1059
1060
        return in_array(strtolower($file->extension()), $ext);
1061
    }
1062
1063
    /**
1064
     * 检测上传文件大小
1065
     * @access public
1066
     * @param File    $file
1067
     * @param integer $size 最大大小
1068
     * @return bool
1069
     */
1070
    protected function checkSize(File $file, $size): bool
1071
    {
1072
        return $file->getSize() <= (int) $size;
1073
    }
1074
1075
    /**
1076
     * 检测上传文件类型
1077
     * @access public
1078
     * @param File         $file
1079
     * @param array|string $mime 允许类型
1080
     * @return bool
1081
     */
1082
    protected function checkMime(File $file, $mime): bool
1083
    {
1084
        if (is_string($mime)) {
1085
            $mime = explode(',', $mime);
1086
        }
1087
1088
        return in_array(strtolower($file->getMime()), $mime);
1089
    }
1090
1091
    /**
1092
     * 验证上传文件后缀
1093
     * @access public
1094
     * @param mixed $file 上传文件
1095
     * @param mixed $rule 验证规则
1096
     * @return bool
1097
     */
1098
    public function fileExt($file, $rule): bool
1099
    {
1100
        if (is_array($file)) {
1101
            foreach ($file as $item) {
1102
                if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
1103
                    return false;
1104
                }
1105
            }
1106
            return true;
1107
        } elseif ($file instanceof File) {
1108
            return $this->checkExt($file, $rule);
1109
        }
1110
1111
        return false;
1112
    }
1113
1114
    /**
1115
     * 验证上传文件类型
1116
     * @access public
1117
     * @param mixed $file 上传文件
1118
     * @param mixed $rule 验证规则
1119
     * @return bool
1120
     */
1121
    public function fileMime($file, $rule): bool
1122
    {
1123
        if (is_array($file)) {
1124
            foreach ($file as $item) {
1125
                if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
1126
                    return false;
1127
                }
1128
            }
1129
            return true;
1130
        } elseif ($file instanceof File) {
1131
            return $this->checkMime($file, $rule);
1132
        }
1133
1134
        return false;
1135
    }
1136
1137
    /**
1138
     * 验证上传文件大小
1139
     * @access public
1140
     * @param mixed $file 上传文件
1141
     * @param mixed $rule 验证规则
1142
     * @return bool
1143
     */
1144
    public function fileSize($file, $rule): bool
1145
    {
1146
        if (is_array($file)) {
1147
            foreach ($file as $item) {
1148
                if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
1149
                    return false;
1150
                }
1151
            }
1152
            return true;
1153
        } elseif ($file instanceof File) {
1154
            return $this->checkSize($file, $rule);
1155
        }
1156
1157
        return false;
1158
    }
1159
1160
    /**
1161
     * 验证图片的宽高及类型
1162
     * @access public
1163
     * @param mixed $file 上传文件
1164
     * @param mixed $rule 验证规则
1165
     * @return bool
1166
     */
1167
    public function image($file, $rule): bool
1168
    {
1169
        if (!($file instanceof File)) {
1170
            return false;
1171
        }
1172
1173
        if ($rule) {
1174
            $rule = explode(',', $rule);
1175
1176
            [$width, $height, $type] = getimagesize($file->getRealPath());
1177
1178
            if (isset($rule[2])) {
1179
                $imageType = strtolower($rule[2]);
1180
1181
                if ('jpg' == $imageType) {
1182
                    $imageType = 'jpeg';
1183
                }
1184
1185
                if (image_type_to_extension($type, false) != $imageType) {
1186
                    return false;
1187
                }
1188
            }
1189
1190
            [$w, $h] = $rule;
1191
1192
            return $w == $width && $h == $height;
1193
        }
1194
1195
        return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
1196
    }
1197
1198
    /**
1199
     * 验证时间和日期是否符合指定格式
1200
     * @access public
1201
     * @param mixed $value 字段值
1202
     * @param mixed $rule  验证规则
1203
     * @return bool
1204
     */
1205
    public function dateFormat($value, $rule): bool
1206
    {
1207
        $info = date_parse_from_format($rule, $value);
1208
        return 0 == $info['warning_count'] && 0 == $info['error_count'];
1209
    }
1210
1211
    /**
1212
     * 验证是否唯一
1213
     * @access public
1214
     * @param mixed  $value 字段值
1215
     * @param mixed  $rule  验证规则 格式:数据表,字段名,排除ID,主键名
1216
     * @param array  $data  数据
1217
     * @param string $field 验证字段名
1218
     * @return bool
1219
     */
1220
    public function unique($value, $rule, array $data = [], string $field = ''): bool
1221
    {
1222
        if (is_string($rule)) {
1223
            $rule = explode(',', $rule);
1224
        }
1225
1226
        if (str_contains($rule[0], '\\')) {
1227
            // 指定模型类
1228
            $db = new $rule[0]();
1229
        } else {
1230
            $db = $this->db->name($rule[0]);
0 ignored issues
show
Bug introduced by
The method name() does not exist on think\Db. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1230
            /** @scrutinizer ignore-call */ 
1231
            $db = $this->db->name($rule[0]);
Loading history...
1231
        }
1232
1233
        $key = $rule[1] ?? $field;
1234
        $map = [];
1235
1236
        if (str_contains($key, '^')) {
1237
            // 支持多个字段验证
1238
            $fields = explode('^', $key);
1239
            foreach ($fields as $key) {
1240
                if (isset($data[$key])) {
1241
                    $map[] = [$key, '=', $data[$key]];
1242
                }
1243
            }
1244
        } elseif (strpos($key, '=')) {
1245
            // 支持复杂验证
1246
            parse_str($key, $array);
1247
            foreach ($array as $k => $val) {
1248
                $map[] = [$k, '=', $data[$k] ?? $val];
1249
            }
1250
        } elseif (isset($data[$field])) {
1251
            $map[] = [$key, '=', $data[$field]];
1252
        }
1253
1254
        $pk = !empty($rule[3]) ? $rule[3] : $db->getPk();
1255
1256
        if (is_string($pk)) {
1257
            if (isset($rule[2])) {
1258
                $map[] = [$pk, '<>', $rule[2]];
1259
            } elseif (isset($data[$pk])) {
1260
                $map[] = [$pk, '<>', $data[$pk]];
1261
            }
1262
        }
1263
1264
        if ($db->where($map)->field($pk)->find()) {
1265
            return false;
1266
        }
1267
1268
        return true;
1269
    }
1270
1271
    /**
1272
     * 使用filter_var方式验证
1273
     * @access public
1274
     * @param mixed $value 字段值
1275
     * @param mixed $rule  验证规则
1276
     * @return bool
1277
     */
1278
    public function filter($value, $rule): bool
1279
    {
1280
        if (is_string($rule) && str_contains($rule, ',')) {
1281
            [$rule, $param] = explode(',', $rule);
1282
        } elseif (is_array($rule)) {
1283
            $param = $rule[1] ?? 0;
1284
            $rule  = $rule[0];
1285
        } else {
1286
            $param = 0;
1287
        }
1288
1289
        return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
1290
    }
1291
1292
    /**
1293
     * 验证某个字段等于某个值的时候必须
1294
     * @access public
1295
     * @param mixed $value 字段值
1296
     * @param mixed $rule  验证规则
1297
     * @param array $data  数据
1298
     * @return bool
1299
     */
1300
    public function requireIf($value, $rule, array $data = []): bool
1301
    {
1302
        [$field, $val] = is_string($rule) ? explode(',', $rule) : $rule;
1303
1304
        if ($this->getDataValue($data, $field) == $val) {
1305
            return !empty($value) || '0' == $value;
1306
        }
1307
1308
        return true;
1309
    }
1310
1311
    /**
1312
     * 通过回调方法验证某个字段是否必须
1313
     * @access public
1314
     * @param mixed        $value 字段值
1315
     * @param string|array $rule  验证规则
1316
     * @param array        $data  数据
1317
     * @return bool
1318
     */
1319
    public function requireCallback($value, string | array $rule, array $data = []): bool
1320
    {
1321
        $callback = is_array($rule) ? $rule : [$this, $rule];
0 ignored issues
show
introduced by
The condition is_array($rule) is always true.
Loading history...
1322
        $result   = call_user_func_array($callback, [$value, $data]);
1323
1324
        if ($result) {
1325
            return !empty($value) || '0' == $value;
1326
        }
1327
1328
        return true;
1329
    }
1330
1331
    /**
1332
     * 验证某个字段有值的情况下必须
1333
     * @access public
1334
     * @param mixed $value 字段值
1335
     * @param mixed $rule  验证规则
1336
     * @param array $data  数据
1337
     * @return bool
1338
     */
1339
    public function requireWith($value, $rule, array $data = []): bool
1340
    {
1341
        $val = $this->getDataValue($data, $rule);
1342
1343
        if (!empty($val)) {
1344
            return !empty($value) || '0' == $value;
1345
        }
1346
1347
        return true;
1348
    }
1349
1350
    /**
1351
     * 验证某个字段没有值的情况下必须
1352
     * @access public
1353
     * @param mixed $value 字段值
1354
     * @param mixed $rule  验证规则
1355
     * @param array $data  数据
1356
     * @return bool
1357
     */
1358
    public function requireWithout($value, $rule, array $data = []): bool
1359
    {
1360
        $val = $this->getDataValue($data, $rule);
1361
1362
        if (empty($val)) {
1363
            return !empty($value) || '0' == $value;
1364
        }
1365
1366
        return true;
1367
    }
1368
1369
    /**
1370
     * 验证是否为数组,支持检查键名
1371
     * @access public
1372
     * @param mixed $value 字段值
1373
     * @param mixed $rule  验证规则
1374
     * @return bool
1375
     */
1376
    public function array($value, $rule): bool
1377
    {
1378
        if (!is_array($value)) {
1379
            return false;
1380
        }
1381
        if ($rule) {
1382
            $keys = is_string($rule) ? explode(',', $rule) : $rule;
1383
            return empty(array_diff($keys, array_keys($value)));
1384
        } else {
1385
            return true;
1386
        }
1387
    }
1388
1389
    /**
1390
     * 验证是否在范围内
1391
     * @access public
1392
     * @param mixed $value 字段值
1393
     * @param mixed $rule  验证规则
1394
     * @return bool
1395
     */
1396
    public function in($value, $rule): bool
1397
    {
1398
        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1399
    }
1400
1401
    /**
1402
     * 验证是否为枚举
1403
     * @access public
1404
     * @param mixed $value 字段值
1405
     * @param mixed $rule  验证规则
1406
     * @return bool
1407
     */
1408
    public function enum($value, $rule): bool
1409
    {
1410
        if (is_subclass_of($rule, BackedEnum::class)) {
1411
            $values = array_map(fn($case) => $case->value, $rule::cases());
1412
        } elseif (is_subclass_of($rule, UnitEnum::class)) {
1413
            $values = array_map(fn($case) => $case->name, $rule::cases());
1414
        } elseif (is_subclass_of($rule, Enumable::class)) {
1415
            $values = $rule::values();
1416
        } else {
1417
            $reflect = new \ReflectionClass($rule);
1418
            $values  = $reflect->getConstants();
1419
        }
1420
1421
        return in_array($value, $values ?? []);
1422
    }
1423
1424
    /**
1425
     * 验证是否不在某个范围
1426
     * @access public
1427
     * @param mixed $value 字段值
1428
     * @param mixed $rule  验证规则
1429
     * @return bool
1430
     */
1431
    public function notIn($value, $rule): bool
1432
    {
1433
        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1434
    }
1435
1436
    /**
1437
     * between验证数据
1438
     * @access public
1439
     * @param mixed $value 字段值
1440
     * @param mixed $rule  验证规则
1441
     * @return bool
1442
     */
1443
    public function between($value, $rule): bool
1444
    {
1445
        [$min, $max] = is_string($rule) ? explode(',', $rule) : $rule;
1446
1447
        return $value >= $min && $value <= $max;
1448
    }
1449
1450
    /**
1451
     * 使用notbetween验证数据
1452
     * @access public
1453
     * @param mixed $value 字段值
1454
     * @param mixed $rule  验证规则
1455
     * @return bool
1456
     */
1457
    public function notBetween($value, $rule): bool
1458
    {
1459
        [$min, $max] = is_string($rule) ? explode(',', $rule) : $rule;
1460
1461
        return $value < $min || $value > $max;
1462
    }
1463
1464
    /**
1465
     * 验证数据长度
1466
     * @access public
1467
     * @param mixed $value 字段值
1468
     * @param mixed $rule  验证规则
1469
     * @return bool
1470
     */
1471
    public function length($value, $rule): bool
1472
    {
1473
        if (is_array($value)) {
1474
            $length = count($value);
1475
        } elseif ($value instanceof File) {
1476
            $length = $value->getSize();
1477
        } else {
1478
            $length = mb_strlen((string) $value);
1479
        }
1480
1481
        if (is_string($rule) && str_contains($rule, ',')) {
1482
            // 长度区间
1483
            [$min, $max] = explode(',', $rule);
1484
            return $length >= $min && $length <= $max;
1485
        }
1486
1487
        // 指定长度
1488
        return $length == $rule;
1489
    }
1490
1491
    /**
1492
     * 验证数据最大长度
1493
     * @access public
1494
     * @param mixed $value 字段值
1495
     * @param mixed $rule  验证规则
1496
     * @return bool
1497
     */
1498
    public function max($value, $rule): bool
1499
    {
1500
        if (is_array($value)) {
1501
            $length = count($value);
1502
        } elseif ($value instanceof File) {
1503
            $length = $value->getSize();
1504
        } else {
1505
            $length = mb_strlen((string) $value);
1506
        }
1507
1508
        return $length <= $rule;
1509
    }
1510
1511
    /**
1512
     * 验证数据最小长度
1513
     * @access public
1514
     * @param mixed $value 字段值
1515
     * @param mixed $rule  验证规则
1516
     * @return bool
1517
     */
1518
    public function min($value, $rule): bool
1519
    {
1520
        if (is_array($value)) {
1521
            $length = count($value);
1522
        } elseif ($value instanceof File) {
1523
            $length = $value->getSize();
1524
        } else {
1525
            $length = mb_strlen((string) $value);
1526
        }
1527
1528
        return $length >= $rule;
1529
    }
1530
1531
    /**
1532
     * 验证日期
1533
     * @access public
1534
     * @param mixed $value 字段值
1535
     * @param mixed $rule  验证规则
1536
     * @param array $data  数据
1537
     * @return bool
1538
     */
1539
    public function after($value, $rule, array $data = []): bool
1540
    {
1541
        return strtotime($value) >= strtotime($rule);
1542
    }
1543
1544
    /**
1545
     * 验证日期
1546
     * @access public
1547
     * @param mixed $value 字段值
1548
     * @param mixed $rule  验证规则
1549
     * @param array $data  数据
1550
     * @return bool
1551
     */
1552
    public function before($value, $rule, array $data = []): bool
1553
    {
1554
        return strtotime($value) <= strtotime($rule);
1555
    }
1556
1557
    /**
1558
     * 验证日期
1559
     * @access public
1560
     * @param mixed $value 字段值
1561
     * @param mixed $rule  验证规则
1562
     * @param array $data  数据
1563
     * @return bool
1564
     */
1565
    public function afterWith($value, $rule, array $data = []): bool
1566
    {
1567
        $rule = $this->getDataValue($data, $rule);
1568
        return !is_null($rule) && strtotime($value) >= strtotime($rule);
1569
    }
1570
1571
    /**
1572
     * 验证日期
1573
     * @access public
1574
     * @param mixed $value 字段值
1575
     * @param mixed $rule  验证规则
1576
     * @param array $data  数据
1577
     * @return bool
1578
     */
1579
    public function beforeWith($value, $rule, array $data = []): bool
1580
    {
1581
        $rule = $this->getDataValue($data, $rule);
1582
        return !is_null($rule) && strtotime($value) <= strtotime($rule);
1583
    }
1584
1585
    /**
1586
     * 验证有效期
1587
     * @access public
1588
     * @param mixed $value 字段值
1589
     * @param mixed $rule  验证规则
1590
     * @return bool
1591
     */
1592
    public function expire($value, $rule): bool
1593
    {
1594
        [$start, $end] = is_string($rule) ? explode(',', $rule) : $rule;
1595
1596
        if (!is_numeric($start)) {
1597
            $start = strtotime($start);
1598
        }
1599
1600
        if (!is_numeric($end)) {
1601
            $end = strtotime($end);
1602
        }
1603
1604
        return time() >= $start && time() <= $end;
1605
    }
1606
1607
    /**
1608
     * 验证IP许可
1609
     * @access public
1610
     * @param mixed $value 字段值
1611
     * @param mixed $rule  验证规则
1612
     * @return bool
1613
     */
1614
    public function allowIp($value, $rule): bool
1615
    {
1616
        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1617
    }
1618
1619
    /**
1620
     * 验证IP禁用
1621
     * @access public
1622
     * @param mixed $value 字段值
1623
     * @param mixed $rule  验证规则
1624
     * @return bool
1625
     */
1626
    public function denyIp($value, $rule): bool
1627 3
    {
1628
        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1629 3
    }
1630
1631 3
    /**
1632 3
     * 验证某个字段等于指定的值,则验证中的字段必须为 yes、on、1 或 true
1633
     * @access public
1634
     * @param mixed $value 字段值
1635
     * @param mixed $rule 验证规则
1636
     * @param array $data 数据
1637
     * @return bool
1638
     */
1639
    public function acceptedIf($value, $rule, array $data = []): bool
1640
    {
1641
        [$field, $val] = is_string($rule) ? explode(',', $rule) : $rule;
1642
1643
        if ($this->getDataValue($data, $field) == $val) {
1644
            return in_array($value, ['1', 'on', 'yes', 'true', 1, true], true);
1645
        }
1646 3
1647
        return true;
1648 3
    }
1649
1650 3
    /**
1651 3
     * 验证某个字段等于指定的值,则验证中的字段必须为 no、off、0 或 false
1652
     * @access public
1653
     * @param mixed $value 字段值
1654
     * @param mixed $rule 验证规则
1655
     * @param array $data 数据
1656
     * @return bool
1657
     */
1658
    public function declinedIf($value, $rule, array $data = []): bool
1659
    {
1660
        [$field, $val] = is_string($rule) ? explode(',', $rule) : $rule;
1661
1662
        if ($this->getDataValue($data, $field) == $val) {
1663 3
            return in_array($value, ['0', 'off', 'no', 'false', 0, false], true);
1664
        }
1665 3
1666 3
        return true;
1667
    }
1668
1669 3
    /**
1670
     * 验证某个字段必须是指定值的倍数
1671
     * @param mixed $value 字段值
1672
     * * @param mixed $rule 验证规则
1673
     * @return bool
1674
     */
1675
    public function multipleOf($value, $rule): bool
1676
    {
1677
        if ('0' == $rule || $value < $rule) {
1678
            return false;
1679
        }
1680
1681
        return $value % $rule === 0;
1682
    }
1683
1684
    /**
1685
     * 使用正则验证数据
1686
     * @access public
1687
     * @param mixed $value 字段值
1688
     * @param mixed $rule  验证规则 正则规则或者预定义正则名
1689
     * @return bool
1690
     */
1691
    public function regex($value, $rule): bool
1692
    {
1693
        $rule = $this->regex[$rule] ?? $this->getDefaultRegexRule($rule);
1694
1695
        if (is_string($rule) && !str_starts_with($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
1696
            // 不是正则表达式则两端补上/
1697
            $rule = '/^' . $rule . '$/';
1698
        }
1699
1700
        return is_scalar($value) && 1 === preg_match($rule, (string) $value);
1701
    }
1702
1703
    /**
1704
     * 获取内置正则验证规则
1705
     * @access public
1706
     * @param string $rule  验证规则 正则规则或者预定义正则名
1707
     * @return bool
1708
     */
1709
    protected function getDefaultRegexRule(string $rule): string
1710 6
    {
1711
        $name = Str::camel($rule);
1712 6
        if (isset($this->defaultRegex[$name])) {
1713
            $rule = $this->defaultRegex[$name];
1714
        }
1715
        return $rule;
1716
    }
1717
1718
    /**
1719
     * 获取错误信息
1720
     * @return array|string
1721
     */
1722 6
    public function getError()
1723
    {
1724 6
        return $this->error;
1725
    }
1726
1727
    /**
1728
     * 获取数据集合
1729
     * @access protected
1730
     * @param array  $data 数据
1731
     * @param string $key  数据标识 支持二维
1732
     * @return array
1733
     */
1734
    protected function getDataSet(array $data, $key): array
1735
    {
1736 6
        if (is_string($key) && str_contains($key, '*')) {
1737
            if (str_ends_with($key, '*')) {
1738
                // user.id.*
1739
                [$key] = explode('.*', $key);
1740
                $value = $this->getRecursiveData($data, $key);
1741
                return is_array($value) ? $value : [$value];
1742
            }
1743
            // user.*.id
1744
            [$key, $column] = explode('.*.', $key);
1745
            return array_column($this->getRecursiveData($data, $key) ?: [], $column);
1746 9
        }
1747
1748 9
        return [$this->getDataValue($data, $key)];
1749 3
    }
1750 6
1751
    /**
1752
     * 获取数据值
1753
     * @access protected
1754 6
     * @param array  $data 数据
1755
     * @param string $key  数据标识 支持二维
1756
     * @return mixed
1757 9
     */
1758
    protected function getDataValue(array $data, $key)
1759
    {
1760
        if (is_numeric($key)) {
1761
            $value = $key;
1762
        } elseif (is_string($key) && str_contains($key, '.')) {
1763
            // 支持多维数组验证
1764
            $value = $this->getRecursiveData($data, $key);
1765
        } else {
1766
            $value = $data[$key] ?? null;
1767
        }
1768
1769
        return $value;
1770
    }
1771
1772
    /**
1773
     * 获取数据值
1774
     * @access protected
1775
     * @param array  $data 数据
1776
     * @param string $key  数据标识 支持二维
1777
     * @return mixed
1778
     */
1779
1780
    protected function getRecursiveData(array $data, string $key)
1781
    {
1782
        $keys = explode('.', $key);
1783
        foreach ($keys as $key) {
1784
            if (!isset($data[$key])) {
1785
                $value = null;
1786
                break;
1787
            }
1788
            $value = $data = $data[$key];
1789
        }
1790 6
        return $value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $value seems to be defined by a foreach iteration on line 1783. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1791
    }
1792 6
1793
    /**
1794 6
     * 获取验证规则的错误提示信息
1795
     * @access protected
1796 6
     * @param string $attribute 字段英文名
1797
     * @param string $title     字段描述名
1798 6
     * @param string $type      验证规则名称
1799 6
     * @param mixed  $rule      验证规则数据
1800
     * @return string|array
1801
     */
1802
    protected function getRuleMsg(string $attribute, string $title, string $type, $rule)
1803
    {
1804
        if (isset($this->message[$attribute . '.' . $type])) {
1805
            $msg = $this->message[$attribute . '.' . $type];
1806 6
        } elseif (isset($this->message[$attribute][$type])) {
1807
            $msg = $this->message[$attribute][$type];
1808
        } elseif (isset($this->message[$attribute])) {
1809
            $msg = $this->message[$attribute];
1810 6
        } elseif (isset($this->typeMsg[$type])) {
1811
            $msg = $this->typeMsg[$type];
1812
        } elseif (str_starts_with($type, 'require')) {
1813
            $msg = $this->typeMsg['require'];
1814
        } else {
1815
            $msg = $title . $this->lang->get('not conform to the rules');
1816
        }
1817
1818
        if (is_array($msg)) {
1819
            return $this->errorMsgIsArray($msg, $rule, $title);
1820
        }
1821 6
1822
        return $this->parseErrorMsg($msg, $rule, $title);
1823 6
    }
1824
1825 6
    /**
1826
     * 获取验证规则的错误提示信息
1827
     * @access protected
1828
     * @param string $msg   错误信息
1829 6
     * @param mixed  $rule  验证规则数据
1830
     * @param string $title 字段描述名
1831
     * @return string|array
1832
     */
1833
    protected function parseErrorMsg(string $msg, $rule, string $title)
1834 6
    {
1835
        if (str_starts_with($msg, '{%')) {
1836
            $msg = $this->lang->get(substr($msg, 2, -1));
1837
        } elseif ($this->lang->has($msg)) {
1838 6
            $msg = $this->lang->get($msg);
1839
        }
1840 6
1841 6
        if (is_array($msg)) {
1842
            return $this->errorMsgIsArray($msg, $rule, $title);
1843
        }
1844
1845
        // rule若是数组则转为字符串
1846 6
        if (is_array($rule)) {
1847 6
            $rule = implode(',', $rule);
1848 6
        }
1849 6
1850 6
        if (is_scalar($rule) && str_contains($msg, ':')) {
1851
            // 变量替换
1852 6
            if (is_string($rule) && str_contains($rule, ',')) {
1853
                $array = array_pad(explode(',', $rule), 3, '');
1854
            } else {
1855
                $array = array_pad([], 3, '');
1856
            }
1857 6
1858
            $msg = str_replace(
1859
                [':attribute', ':1', ':2', ':3'],
1860
                [$title, $array[0], $array[1], $array[2]],
1861
                $msg,
1862
            );
1863
1864
            if (str_contains($msg, ':rule')) {
1865
                $msg = str_replace(':rule', (string) $rule, $msg);
1866
            }
1867
        }
1868
1869
        return $msg;
1870
    }
1871
1872
    /**
1873
     * 错误信息数组处理
1874
     * @access protected
1875
     * @param array $msg   错误信息
1876
     * @param mixed  $rule  验证规则数据
1877
     * @param string $title 字段描述名
1878
     * @return array
1879
     */
1880
    protected function errorMsgIsArray(array $msg, $rule, string $title)
1881
    {
1882
        foreach ($msg as $key => $val) {
1883
            if (is_string($val)) {
1884
                $msg[$key] = $this->parseErrorMsg($val, $rule, $title);
1885
            }
1886
        }
1887
        return $msg;
1888
    }
1889
1890
    /**
1891
     * 获取数据验证的场景
1892
     * @access protected
1893
     * @param string $scene 验证场景
1894
     * @return void
1895
     */
1896
    protected function getScene(string $scene): void
1897
    {
1898
        if (method_exists($this, 'scene' . $scene)) {
1899
            call_user_func([$this, 'scene' . $scene]);
1900
        } elseif (isset($this->scene[$scene])) {
1901 6
            // 如果设置了验证适用场景
1902
            $this->only = $this->scene[$scene];
1903 6
        }
1904 6
    }
1905
1906
    /**
1907 6
     * 动态方法 直接调用is方法进行验证
1908
     * @access public
1909 6
     * @param string $method 方法名
1910
     * @param array  $args   调用参数
1911
     * @return bool
1912
     */
1913
    public function __call($method, $args)
1914
    {
1915
        if ('is' == strtolower(substr($method, 0, 2))) {
1916
            $method = substr($method, 2);
1917
        }
1918
1919
        array_push($args, lcfirst($method));
1920
1921
        return call_user_func_array([$this, 'is'], $args);
1922
    }
1923
}
1924