Passed
Push — 8.0 ( 7d4476...7a8970 )
by liu
02:25
created

Validate::checkItem()   F

Complexity

Conditions 32
Paths 1035

Size

Total Lines 84
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 123.3354

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 32
eloc 49
c 5
b 0
f 0
nc 1035
nop 6
dl 0
loc 84
ccs 26
cts 47
cp 0.5532
crap 123.3354
rs 0

How to fix   Long Method    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 2
                $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|array $name 场景名
377
     * @return $this
378
     */
379
    public function scene(string | array $name)
380
    {
381
        if (is_array($name)) {
0 ignored issues
show
introduced by
The condition is_array($name) is always true.
Loading history...
382
            $this->only = $name;
383
        } else {
384
            // 设置当前场景
385
            $this->currentScene = $name;
386
        }
387
388
        return $this;
389
    }
390
391
    /**
392
     * 判断是否存在某个验证场景
393
     * @access public
394
     * @param string $name 场景名
395
     * @return bool
396
     */
397
    public function hasScene(string $name): bool
398
    {
399
        return isset($this->scene[$name]) || method_exists($this, 'scene' . $name);
400
    }
401
402
    /**
403
     * 设置批量验证
404
     * @access public
405
     * @param bool $batch 是否批量验证
406
     * @return $this
407
     */
408
    public function batch(bool $batch = true)
409
    {
410
        $this->batch = $batch;
411
412
        return $this;
413
    }
414
415
    /**
416
     * 设置验证失败后是否抛出异常
417
     * @access protected
418
     * @param bool $fail 是否抛出异常
419
     * @return $this
420
     */
421
    public function failException(bool $fail = true)
422
    {
423
        $this->failException = $fail;
424
425
        return $this;
426
    }
427
428
    /**
429
     * 指定需要验证的字段列表
430
     * @access public
431
     * @param array $fields 字段名
432
     * @return $this
433
     */
434
    public function only(array $fields)
435
    {
436
        $this->only = $fields;
437
438
        return $this;
439
    }
440
441
    /**
442
     * 指定需要覆盖的字段验证规则
443
     * @access public
444
     * @param string $field 字段名
445
     * @param mixed  $rules 验证规则
446
     * @return $this
447
     */
448
    public function replace(string $field, $rules)
449
    {
450
        $this->replace[$field] = $rules;
451
452
        return $this;
453
    }
454
455
    /**
456
     * 移除某个字段的验证规则
457
     * @access public
458
     * @param string|array $field 字段名
459
     * @param mixed        $rule  验证规则 true 移除所有规则
460
     * @return $this
461
     */
462
    public function remove(string | array $field, $rule = null)
463
    {
464
        if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
465
            foreach ($field as $key => $rule) {
466
                if (is_int($key)) {
467
                    $this->remove($rule);
468
                } else {
469
                    $this->remove($key, $rule);
470
                }
471
            }
472
        } else {
473
            if (is_string($rule)) {
474
                $rule = explode('|', $rule);
475
            }
476
477
            $this->remove[$field] = $rule;
478
        }
479
480
        return $this;
481
    }
482
483
    /**
484
     * 追加某个字段的验证规则
485
     * @access public
486
     * @param string|array $field 字段名
487
     * @param mixed        $rule  验证规则
488
     * @return $this
489
     */
490
    public function append(string | array $field, $rule = null)
491
    {
492
        if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
493
            foreach ($field as $key => $rule) {
494
                $this->append($key, $rule);
495
            }
496
        } else {
497
            if (is_string($rule)) {
498
                $rule = explode('|', $rule);
499
            }
500
501
            $this->append[$field] = $rule;
502
        }
503
504
        return $this;
505
    }
506
507
    /**
508
     * 数据自动验证
509
     * @access public
510
     * @param array $data  数据
511
     * @param array $rules 验证规则
512
     * @return bool
513
     */
514 6
    public function check(array $data, array $rules = []): bool
515
    {
516 6
        $this->error = [];
517
518 6
        if ($this->currentScene) {
519
            $this->getScene($this->currentScene);
520
        }
521
522 6
        if (empty($rules)) {
523
            // 读取验证规则
524 6
            $rules = $this->rule;
525
        }
526
527 6
        foreach ($this->append as $key => $rule) {
528
            if (!isset($rules[$key])) {
529
                $rules[$key] = $rule;
530
                unset($this->append[$key]);
531
            }
532
        }
533
534 6
        foreach ($rules as $key => $rule) {
535
            // field => 'rule1|rule2...' field => ['rule1','rule2',...]
536 6
            if (str_contains($key, '|')) {
537
                // 字段|描述 用于指定属性名称
538
                [$key, $title] = explode('|', $key);
539
            } else {
540 6
                $title = $this->field[$key] ?? $key;
541
            }
542
543
            // 场景检测
544 6
            if (!empty($this->only) && (!in_array($key, $this->only) && !array_key_exists($key, $this->only))) {
545
                continue;
546
            }
547
548
            // 获取数据 支持二维数组
549 6
            $values = $this->getDataSet($data, $key);
550
551 6
            if (empty($values)) {
552
                if (is_string($rule)) {
553
                    $items = explode('|', $rule);
554
                } elseif (is_array($rule)) {
555
                    $items = $rule;
556
                }
557
558
                if (isset($items) && false !== array_search('require', $items)) {
559
                    $message = $this->getRuleMsg($key, $title, 'require', $rule);
560
                    throw new ValidateException($message, $key);
561
                }
562
            }
563
564
            // 字段数据因子验证
565 6
            foreach ($values as $value) {
566 6
                $result = $this->checkItem($key, $value, $rule, $data, $title);
567
568 6
                if (true !== $result) {
569
                    // 验证失败 记录错误信息
570 6
                    if (false === $result) {
571
                        $result = $this->getRuleMsg($key, $title, '', $rule);
572
                    }
573
574 6
                    $this->error[$key] = $result;
575
576 6
                    if (!empty($this->batch)) {
577
                        // 批量验证
578 6
                    } elseif ($this->failException) {
579
                        throw new ValidateException($result, $key);
580
                    } else {
581 6
                        return false;
582
                    }
583
                }
584
            }
585
        }
586
587
        if (!empty($this->error)) {
588
            if ($this->failException) {
589
                throw new ValidateException($this->error);
590
            }
591
            return false;
592
        }
593
594
        return true;
595
    }
596
597
    /**
598
     * 根据验证规则验证数据
599
     * @access public
600
     * @param mixed $value 字段值
601
     * @param mixed $rules 验证规则
602
     * @return bool
603
     */
604
    public function checkRule($value, $rules): bool
605
    {
606
        if ($rules instanceof Closure) {
607
            return call_user_func_array($rules, [$value]);
608
        } elseif ($rules instanceof ValidateRule) {
609
            $rules = $rules->getRule();
610
        } elseif (is_string($rules)) {
611
            $rules = explode('|', $rules);
612
        }
613
614
        foreach ($rules as $key => $rule) {
615
            if ($rule instanceof Closure) {
616
                $result = call_user_func_array($rule, [$value]);
617
            } elseif (is_subclass_of($rule, UnitEnum::class) || is_subclass_of($rule, Enumable::class)) {
618
                $result = $this->enum($value, $rule);
619
            } else {
620
                // 判断验证类型
621
                [$type, $rule, $callback] = $this->getValidateType($key, $rule);
622
623
                $result = call_user_func_array($callback, [$value, $rule]);
624
            }
625
626
            if (true !== $result) {
627
                if ($this->failException) {
628
                    if (false === $result) {
629
                        $result = $this->getRuleMsg('', '', $type, $rule);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type seems to be defined later in this foreach loop on line 621. Are you sure it is defined here?
Loading history...
630
                    }
631
                    throw new ValidateException($result, $type);
632
                }
633
634
                return $result;
635
            }
636
        }
637
638
        return true;
639
    }
640
641
    /**
642
     * 验证单个字段规则
643
     * @access protected
644
     * @param string $field 字段名
645
     * @param mixed  $value 字段值
646
     * @param mixed  $rules 验证规则
647
     * @param array  $data  数据
648
     * @param string $title 字段描述
649
     * @param array  $msg   提示信息
650
     * @return mixed
651
     */
652 6
    protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = []): mixed
653
    {
654 6
        if ($rules instanceof Closure) {
655
            return call_user_func_array($rules, [$value, $data]);
656
        }
657
658 6
        if ($rules instanceof ValidateRule) {
659
            $title = $rules->getTitle() ?: $title;
660
            $msg   = $rules->getMsg();
661
            $rules = $rules->getRule();
662
        }
663
664 6
        if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
665
            // 字段已经移除 无需验证
666
            return true;
667
        }
668
669 6
        if (isset($this->replace[$field])) {
670
            $rules = $this->replace[$field];
671 6
        } elseif (isset($this->only[$field])) {
672
            $rules = $this->only[$field];
673
        }
674
675
        // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
676 6
        if (is_string($rules)) {
677 6
            $rules = explode('|', $rules);
678
        }
679
680 6
        if (isset($this->append[$field])) {
681
            // 追加额外的验证规则
682
            $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
683
            unset($this->append[$field]);
684
        }
685
686 6
        if (empty($rules)) {
687
            return true;
688
        }
689
690 6
        $i = 0;
691 6
        foreach ($rules as $key => $rule) {
692 6
            if ($rule instanceof Closure) {
693
                $result = call_user_func_array($rule, [$value, $data]);
694
                $type   = is_numeric($key) ? '' : $key;
695 6
            } elseif (is_subclass_of($rule, UnitEnum::class) || is_subclass_of($rule, Enumable::class)) {
696
                $result = $this->enum($value, $rule);
697
                $type   = is_numeric($key) ? '' : $key;
698
            } else {
699
                // 判断验证类型
700 6
                [$type, $rule, $callback] = $this->getValidateType($key, $rule);
701
702 6
                if (isset($this->append[$field]) && in_array($type, $this->append[$field])) {
703 6
                } elseif (isset($this->remove[$field]) && in_array($type, $this->remove[$field])) {
704
                    // 规则已经移除
705
                    $i++;
706
                    continue;
707
                }
708
709 6
                if ('must' == $type || str_starts_with($type, 'require') || in_array($type, $this->must) || (!is_null($value) && '' !== $value)) {
710 6
                    $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]);
711
                } else {
712
                    $result = true;
713
                }
714
            }
715
716 6
            if (false === $result) {
717
                // 验证失败 返回错误信息
718 6
                if (!empty($msg[$i])) {
719
                    $message = $msg[$i];
720
                    if (is_string($message) && str_starts_with($message, '{%')) {
721
                        $message = $this->lang->get(substr($message, 2, -1));
722
                    }
723
                } else {
724 6
                    $message = $this->getRuleMsg($field, $title, $type, $rule);
725
                }
726
727 6
                return $message;
728 6
            } elseif (true !== $result) {
729
                // 返回自定义错误信息
730
                return $this->parseUserErrorMessage($result, $title, $rule);
731
            }
732 6
            $i++;
733
        }
734
735 6
        return $result ?? true;
736
    }
737
738
    protected function parseUserErrorMessage($message, $title, $rule)
739
    {
740
        if (is_string($message) && str_contains($message, ':')) {
741
            $message = str_replace(':attribute', $title, $message);
742
743
            if (str_contains($message, ':rule') && is_scalar($rule)) {
744
                $message = str_replace(':rule', (string) $rule, $message);
745
            }
746
        }
747
748
        return $message;
749
    }
750
751
    /**
752
     * 获取当前验证类型及规则
753
     * @access public
754
     * @param mixed $key
755
     * @param mixed $rule
756
     * @return array
757
     */
758 6
    protected function getValidateType($key, $rule): array
759
    {
760
        // 判断验证类型
761 6
        $hasParam = true;
762 6
        if (!is_numeric($key)) {
763
            $type = $key;
764 6
        } elseif (str_contains($rule, ':')) {
765 6
            [$type, $rule] = explode(':', $rule, 2);
766
        } else {
767 6
            $type     = $rule;
768 6
            $hasParam = false;
769
        }
770
771
        // 验证类型别名
772 6
        $type = $this->alias[$type] ?? $type;
773
774 6
        if (isset($this->type[$type])) {
775
            // 自定义验证
776
            $call = $this->type[$type];
777
        } else {
778 6
            $method = Str::camel($type);
779 6
            if (method_exists($this, $method)) {
780 6
                $call = [$this, $method];
781 6
                $rule = $hasParam ? $rule : '';
782
            } else {
783 6
                $call = [$this, 'is'];
784
            }
785
        }
786
787 6
        return [$type, $rule, $call];
788
    }
789
790
    /**
791
     * 验证是否和某个字段的值一致
792
     * @access public
793
     * @param mixed  $value 字段值
794
     * @param mixed  $rule  验证规则
795
     * @param array  $data  数据
796
     * @param string $field 字段名
797
     * @return bool
798
     */
799
    public function confirm($value, $rule, array $data = [], string $field = ''): bool
800
    {
801
        if ('' == $rule) {
802
            if (str_contains($field, '_confirm')) {
803
                $rule = strstr($field, '_confirm', true);
804
            } else {
805
                $rule = $field . '_confirm';
806
            }
807
        }
808
809
        return $this->getDataValue($data, $rule) === $value;
810
    }
811
812
    /**
813
     * 验证是否和某个字段的值是否不同
814
     * @access public
815
     * @param mixed $value 字段值
816
     * @param mixed $rule  验证规则
817
     * @param array $data  数据
818
     * @return bool
819
     */
820
    public function different($value, $rule, array $data = []): bool
821
    {
822
        return $this->getDataValue($data, $rule) != $value;
823
    }
824
825
    /**
826
     * 验证是否大于等于某个值
827
     * @access public
828
     * @param mixed $value 字段值
829
     * @param mixed $rule  验证规则
830
     * @param array $data  数据
831
     * @return bool
832
     */
833 3
    public function egt($value, $rule, array $data = []): bool
834
    {
835 3
        return $value >= $this->getDataValue($data, $rule);
836
    }
837
838
    /**
839
     * 验证是否大于某个值
840
     * @access public
841
     * @param mixed $value 字段值
842
     * @param mixed $rule  验证规则
843
     * @param array $data  数据
844
     * @return bool
845
     */
846 3
    public function gt($value, $rule, array $data = []): bool
847
    {
848 3
        return $value > $this->getDataValue($data, $rule);
849
    }
850
851
    /**
852
     * 验证是否小于等于某个值
853
     * @access public
854
     * @param mixed $value 字段值
855
     * @param mixed $rule  验证规则
856
     * @param array $data  数据
857
     * @return bool
858
     */
859 3
    public function elt($value, $rule, array $data = []): bool
860
    {
861 3
        return $value <= $this->getDataValue($data, $rule);
862
    }
863
864
    /**
865
     * 验证是否小于某个值
866
     * @access public
867
     * @param mixed $value 字段值
868
     * @param mixed $rule  验证规则
869
     * @param array $data  数据
870
     * @return bool
871
     */
872 3
    public function lt($value, $rule, array $data = []): bool
873
    {
874 3
        return $value < $this->getDataValue($data, $rule);
875
    }
876
877
    /**
878
     * 验证是否等于某个值
879
     * @access public
880
     * @param mixed $value 字段值
881
     * @param mixed $rule  验证规则
882
     * @return bool
883
     */
884 3
    public function eq($value, $rule): bool
885
    {
886 3
        return $value == $rule;
887
    }
888
889
    /**
890
     * 验证是否不等于某个值
891
     * @access public
892
     * @param mixed $value 字段值
893
     * @param mixed $rule  验证规则
894
     * @return bool
895
     */
896 3
    public function neq($value, $rule): bool
897
    {
898 3
        return $value != $rule;
899
    }
900
901
    /**
902
     * 必须验证
903
     * @access public
904
     * @param mixed $value 字段值
905
     * @param mixed $rule  验证规则
906
     * @return bool
907
     */
908
    public function must($value, $rule = null): bool
909
    {
910
        return !empty($value) || '0' == $value;
911
    }
912
913
    /**
914
     * 验证字段值是否为有效格式
915
     * @access public
916
     * @param mixed  $value 字段值
917
     * @param string $rule  验证规则
918
     * @param array  $data  数据
919
     * @return bool
920
     */
921 12
    public function is($value, string $rule, array $data = []): bool
922
    {
923 12
        $call = function ($value, $rule) {
924
            if (function_exists('ctype_' . $rule)) {
925
                // ctype验证规则
926
                $ctypeFun = 'ctype_' . $rule;
927
                $result   = $ctypeFun((string) $value);
928
            } elseif (isset($this->filter[$rule])) {
929
                // Filter_var验证规则
930
                $result = $this->filter($value, $this->filter[$rule]);
931
            } else {
932
                // 正则验证
933
                $result = $this->regex($value, $rule);
934
            }
935
            return $result;
936 12
        };
937
938 12
        return match (Str::camel($rule)) {
939 12
            'require' => !empty($value) || '0' == $value, // 必须
940 12
            'accepted' => in_array($value, ['1', 'on', 'yes', 'true', 1, true], true), // 接受
941 12
            'declined' => in_array($value, ['0', 'off', 'no', 'false', 0, false], true), // 不接受
942 12
            'date' => false !== strtotime($value), // 是否是一个有效日期
943 12
            'activeUrl' => checkdnsrr($value), // 是否为有效的网址
944 12
            'boolean', 'bool' => in_array($value, [true, false, 0, 1, '0', '1'], true), // 是否为布尔值
945 12
            'number' => ctype_digit((string) $value),
946 12
            'alphaNum' => ctype_alnum($value),
947 12
            'array'    => is_array($value), // 是否为数组
948 12
            'string'   => is_string($value),
949 12
            'file'     => $value instanceof File,
950 12
            'image'    => $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]),
951 12
            'token'    => $this->token($value, '__token__', $data),
952 12
            default    => $call($value, $rule),
953 12
        };
954
    }
955
956
    // 判断图像类型
957
    protected function getImageType($image)
958
    {
959
        if (function_exists('exif_imagetype')) {
960
            return exif_imagetype($image);
961
        }
962
963
        try {
964
            $info = getimagesize($image);
965
            return $info ? $info[2] : false;
966
        } catch (\Exception $e) {
967
            return false;
968
        }
969
    }
970
971
    /**
972
     * 验证表单令牌
973
     * @access public
974
     * @param mixed $value 字段值
975
     * @param mixed $rule  验证规则
976
     * @param array $data  数据
977
     * @return bool
978
     */
979
    public function token($value, string $rule, array $data): bool
980
    {
981
        $rule = !empty($rule) ? $rule : '__token__';
982
        return $this->request->checkToken($rule, $data);
983
    }
984
985
    /**
986
     * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
987
     * @access public
988
     * @param mixed $value 字段值
989
     * @param mixed $rule  验证规则
990
     * @return bool
991
     */
992
    public function activeUrl(string $value, string $rule = 'MX'): bool
993
    {
994
        if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
995
            $rule = 'MX';
996
        }
997
998
        return checkdnsrr($value, $rule);
999
    }
1000
1001
    /**
1002
     * 验证是否有效IP
1003
     * @access public
1004
     * @param mixed $value 字段值
1005
     * @param mixed $rule  验证规则 ipv4 ipv6
1006
     * @return bool
1007
     */
1008
    public function ip($value, string $rule = 'ipv4'): bool
1009
    {
1010
        if (!in_array($rule, ['ipv4', 'ipv6'])) {
1011
            $rule = 'ipv4';
1012
        }
1013
1014
        return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
1015
    }
1016
1017
    /**
1018
     * 检测是否以某个字符串开头
1019
     * @access public
1020
     * @param mixed $value 字段值
1021
     * @param string $rule  验证规则
1022
     * @return bool
1023
     */
1024
    public function startWith($value, string $rule): bool
1025
    {
1026
        return is_string($value) && str_starts_with($value, $rule);
1027
    }
1028
1029
    /**
1030
     * 检测是否以某个字符串结尾
1031
     * @access public
1032
     * @param mixed $value 字段值
1033
     * @param string $rule  验证规则
1034
     * @return bool
1035
     */
1036
    public function endWith($value, string $rule): bool
1037
    {
1038
        return is_string($value) && str_ends_with($value, $rule);
1039
    }
1040
1041
    /**
1042
     * 检测是否以包含某个字符串
1043
     * @access public
1044
     * @param mixed $value 字段值
1045
     * @param string $rule  验证规则
1046
     * @return bool
1047
     */
1048
    public function contain($value, string $rule): bool
1049
    {
1050
        return is_string($value) && str_contains($value, $rule);
1051
    }
1052
1053
    /**
1054
     * 检测上传文件后缀
1055
     * @access public
1056
     * @param File         $file
1057
     * @param array|string $ext 允许后缀
1058
     * @return bool
1059
     */
1060
    protected function checkExt(File $file, $ext): bool
1061
    {
1062
        if (is_string($ext)) {
1063
            $ext = explode(',', $ext);
1064
        }
1065
1066
        return in_array(strtolower($file->extension()), $ext);
1067
    }
1068
1069
    /**
1070
     * 检测上传文件大小
1071
     * @access public
1072
     * @param File    $file
1073
     * @param integer $size 最大大小
1074
     * @return bool
1075
     */
1076
    protected function checkSize(File $file, $size): bool
1077
    {
1078
        return $file->getSize() <= (int) $size;
1079
    }
1080
1081
    /**
1082
     * 检测上传文件类型
1083
     * @access public
1084
     * @param File         $file
1085
     * @param array|string $mime 允许类型
1086
     * @return bool
1087
     */
1088
    protected function checkMime(File $file, $mime): bool
1089
    {
1090
        if (is_string($mime)) {
1091
            $mime = explode(',', $mime);
1092
        }
1093
1094
        return in_array(strtolower($file->getMime()), $mime);
1095
    }
1096
1097
    /**
1098
     * 验证上传文件后缀
1099
     * @access public
1100
     * @param mixed $file 上传文件
1101
     * @param mixed $rule 验证规则
1102
     * @return bool
1103
     */
1104
    public function fileExt($file, $rule): bool
1105
    {
1106
        if (is_array($file)) {
1107
            foreach ($file as $item) {
1108
                if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
1109
                    return false;
1110
                }
1111
            }
1112
            return true;
1113
        } elseif ($file instanceof File) {
1114
            return $this->checkExt($file, $rule);
1115
        }
1116
1117
        return false;
1118
    }
1119
1120
    /**
1121
     * 验证上传文件类型
1122
     * @access public
1123
     * @param mixed $file 上传文件
1124
     * @param mixed $rule 验证规则
1125
     * @return bool
1126
     */
1127
    public function fileMime($file, $rule): bool
1128
    {
1129
        if (is_array($file)) {
1130
            foreach ($file as $item) {
1131
                if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
1132
                    return false;
1133
                }
1134
            }
1135
            return true;
1136
        } elseif ($file instanceof File) {
1137
            return $this->checkMime($file, $rule);
1138
        }
1139
1140
        return false;
1141
    }
1142
1143
    /**
1144
     * 验证上传文件大小
1145
     * @access public
1146
     * @param mixed $file 上传文件
1147
     * @param mixed $rule 验证规则
1148
     * @return bool
1149
     */
1150
    public function fileSize($file, $rule): bool
1151
    {
1152
        if (is_array($file)) {
1153
            foreach ($file as $item) {
1154
                if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
1155
                    return false;
1156
                }
1157
            }
1158
            return true;
1159
        } elseif ($file instanceof File) {
1160
            return $this->checkSize($file, $rule);
1161
        }
1162
1163
        return false;
1164
    }
1165
1166
    /**
1167
     * 验证图片的宽高及类型
1168
     * @access public
1169
     * @param mixed $file 上传文件
1170
     * @param mixed $rule 验证规则
1171
     * @return bool
1172
     */
1173
    public function image($file, $rule): bool
1174
    {
1175
        if (!($file instanceof File)) {
1176
            return false;
1177
        }
1178
1179
        if ($rule) {
1180
            $rule = explode(',', $rule);
1181
1182
            [$width, $height, $type] = getimagesize($file->getRealPath());
1183
1184
            if (isset($rule[2])) {
1185
                $imageType = strtolower($rule[2]);
1186
1187
                if ('jpg' == $imageType) {
1188
                    $imageType = 'jpeg';
1189
                }
1190
1191
                if (image_type_to_extension($type, false) != $imageType) {
1192
                    return false;
1193
                }
1194
            }
1195
1196
            [$w, $h] = $rule;
1197
1198
            return $w == $width && $h == $height;
1199
        }
1200
1201
        return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
1202
    }
1203
1204
    /**
1205
     * 验证时间和日期是否符合指定格式
1206
     * @access public
1207
     * @param mixed $value 字段值
1208
     * @param mixed $rule  验证规则
1209
     * @return bool
1210
     */
1211
    public function dateFormat($value, $rule): bool
1212
    {
1213
        $info = date_parse_from_format($rule, $value);
1214
        return 0 == $info['warning_count'] && 0 == $info['error_count'];
1215
    }
1216
1217
    /**
1218
     * 验证是否唯一
1219
     * @access public
1220
     * @param mixed  $value 字段值
1221
     * @param mixed  $rule  验证规则 格式:数据表,字段名,排除ID,主键名
1222
     * @param array  $data  数据
1223
     * @param string $field 验证字段名
1224
     * @return bool
1225
     */
1226
    public function unique($value, $rule, array $data = [], string $field = ''): bool
1227
    {
1228
        if (is_string($rule)) {
1229
            $rule = explode(',', $rule);
1230
        }
1231
1232
        if (str_contains($rule[0], '\\')) {
1233
            // 指定模型类
1234
            $db = new $rule[0]();
1235
        } else {
1236
            $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

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