Passed
Pull Request — 8.0 (#3079)
by wj
02:22
created

Validate::parseErrorMsg()   B

Complexity

Conditions 10
Paths 33

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 12.332

Importance

Changes 0
Metric Value
cc 10
eloc 20
c 0
b 0
f 0
nc 33
nop 3
dl 0
loc 37
ccs 15
cts 21
cp 0.7143
crap 12.332
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
                    // 没有返回true 则表示验证失败
566 6
                    if (!empty($this->batch)) {
567
                        // 批量验证
568
                        $this->error[$key] = $result;
569 6
                    } elseif ($this->failException) {
570
                        throw new ValidateException($result, $key);
571
                    } else {
572 6
                        $this->error = $result;
573 6
                        return false;
574
                    }
575
                }
576
            }
577
        }
578
579
        if (!empty($this->error)) {
580
            if ($this->failException) {
581
                throw new ValidateException($this->error);
582
            }
583
            return false;
584
        }
585
586
        return true;
587
    }
588
589
    /**
590
     * 根据验证规则验证数据
591
     * @access public
592
     * @param mixed $value 字段值
593
     * @param mixed $rules 验证规则
594
     * @return bool
595
     */
596
    public function checkRule($value, $rules): bool
597
    {
598
        if ($rules instanceof Closure) {
599
            return call_user_func_array($rules, [$value]);
600
        } elseif ($rules instanceof ValidateRule) {
601
            $rules = $rules->getRule();
602
        } elseif (is_string($rules)) {
603
            $rules = explode('|', $rules);
604
        }
605
606
        foreach ($rules as $key => $rule) {
607
            if ($rule instanceof Closure) {
608
                $result = call_user_func_array($rule, [$value]);
609
            } else {
610
                // 判断验证类型
611
                [$type, $rule, $callback] = $this->getValidateType($key, $rule);
612
613
                $result = call_user_func_array($callback, [$value, $rule]);
614
            }
615
616
            if (true !== $result) {
617
                if ($this->failException) {
618
                    throw new ValidateException($result, $key);
619
                }
620
621
                return $result;
622
            }
623
        }
624
625
        return true;
626
    }
627
628
    /**
629
     * 验证单个字段规则
630
     * @access protected
631
     * @param string $field 字段名
632
     * @param mixed  $value 字段值
633
     * @param mixed  $rules 验证规则
634
     * @param array  $data  数据
635
     * @param string $title 字段描述
636
     * @param array  $msg   提示信息
637
     * @return mixed
638
     */
639 6
    protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = []): mixed
640
    {
641 6
        if ($rules instanceof Closure) {
642
            return call_user_func_array($rules, [$value, $data]);
643
        }
644
645 6
        if ($rules instanceof ValidateRule) {
646
            $title = $rules->getTitle() ?: $title;
647
            $msg   = $rules->getMsg();
648
            $rules = $rules->getRule();
649
        }
650
651 6
        if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
652
            // 字段已经移除 无需验证
653
            return true;
654
        }
655
656 6
        if (isset($this->replace[$field])) {
657
            $rules = $this->replace[$field];
658 6
        } elseif (isset($this->only[$field])) {
659
            $rules = $this->only[$field];
660
        }
661
662
        // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
663 6
        if (is_string($rules)) {
664 6
            $rules = explode('|', $rules);
665
        }
666
667 6
        if (isset($this->append[$field])) {
668
            // 追加额外的验证规则
669
            $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
670
            unset($this->append[$field]);
671
        }
672
673 6
        if (empty($rules)) {
674
            return true;
675
        }
676
677 6
        $i = 0;
678 6
        foreach ($rules as $key => $rule) {
679 6
            if ($rule instanceof Closure) {
680
                $result = call_user_func_array($rule, [$value, $data]);
681
                $type   = is_numeric($key) ? '' : $key;
682 6
            } elseif (is_subclass_of($rule, UnitEnum::class) || is_subclass_of($rule, Enumable::class)) {
683
                $result = $this->enum($value, $rule);
684
                $type   = is_numeric($key) ? '' : $key;
685
            } else {
686
                // 判断验证类型
687 6
                [$type, $rule, $callback] = $this->getValidateType($key, $rule);
688
689 6
                if (isset($this->append[$field]) && in_array($type, $this->append[$field])) {
690 6
                } elseif (isset($this->remove[$field]) && in_array($type, $this->remove[$field])) {
691
                    // 规则已经移除
692
                    $i++;
693
                    continue;
694
                }
695
696 6
                if ('must' == $type || str_starts_with($type, 'require') || in_array($type, $this->must) || (!is_null($value) && '' !== $value)) {
697 6
                    $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]);
698
                } else {
699
                    $result = true;
700
                }
701
            }
702
703 6
            if (false === $result) {
704
                // 验证失败 返回错误信息
705 6
                if (!empty($msg[$i])) {
706
                    $message = $msg[$i];
707
                    if (is_string($message) && str_starts_with($message, '{%')) {
708
                        $message = $this->lang->get(substr($message, 2, -1));
709
                    }
710
                } else {
711 6
                    $message = $this->getRuleMsg($field, $title, $type, $rule);
712
                }
713
714 6
                return $message;
715 6
            } elseif (true !== $result) {
716
                // 返回自定义错误信息
717
                if (is_string($result) && str_contains($result, ':')) {
718
                    $result = str_replace(':attribute', $title, $result);
719
720
                    if (str_contains($result, ':rule') && is_scalar($rule)) {
721
                        $result = str_replace(':rule', (string) $rule, $result);
722
                    }
723
                }
724
725
                return $result;
726
            }
727 6
            $i++;
728
        }
729
730 6
        return $result ?? true;
731
    }
732
733
    /**
734
     * 获取当前验证类型及规则
735
     * @access public
736
     * @param mixed $key
737
     * @param mixed $rule
738
     * @return array
739
     */
740 6
    protected function getValidateType($key, $rule): array
741
    {
742
        // 判断验证类型
743 6
        $hasParam = true;
744 6
        if (!is_numeric($key)) {
745
            $type = $key;
746 6
        } elseif (str_contains($rule, ':')) {
747 6
            [$type, $rule] = explode(':', $rule, 2);
748
        } else {
749 6
            $type     = $rule;
750 6
            $hasParam = false;
751
        }
752
753
        // 验证类型别名
754 6
        $type = $this->alias[$type] ?? $type;
755
756 6
        if (isset($this->type[$type])) {
757
            // 自定义验证
758
            $call = $this->type[$type];
759
        } else {
760 6
            $method = Str::camel($type);
761 6
            if (method_exists($this, $method)) {
762 6
                $call = [$this, $method];
763 6
                $rule = $hasParam ? $rule : '';
764
            } else {
765 6
                $call = [$this, 'is'];
766
            }
767
        }
768
769 6
        return [$type, $rule, $call];
770
    }
771
772
    /**
773
     * 验证是否和某个字段的值一致
774
     * @access public
775
     * @param mixed  $value 字段值
776
     * @param mixed  $rule  验证规则
777
     * @param array  $data  数据
778
     * @param string $field 字段名
779
     * @return bool
780
     */
781
    public function confirm($value, $rule, array $data = [], string $field = ''): bool
782
    {
783
        if ('' == $rule) {
784
            if (str_contains($field, '_confirm')) {
785
                $rule = strstr($field, '_confirm', true);
786
            } else {
787
                $rule = $field . '_confirm';
788
            }
789
        }
790
791
        return $this->getDataValue($data, $rule) === $value;
792
    }
793
794
    /**
795
     * 验证是否和某个字段的值是否不同
796
     * @access public
797
     * @param mixed $value 字段值
798
     * @param mixed $rule  验证规则
799
     * @param array $data  数据
800
     * @return bool
801
     */
802
    public function different($value, $rule, array $data = []): bool
803
    {
804
        return $this->getDataValue($data, $rule) != $value;
805
    }
806
807
    /**
808
     * 验证是否大于等于某个值
809
     * @access public
810
     * @param mixed $value 字段值
811
     * @param mixed $rule  验证规则
812
     * @param array $data  数据
813
     * @return bool
814
     */
815 3
    public function egt($value, $rule, array $data = []): bool
816
    {
817 3
        return $value >= $this->getDataValue($data, $rule);
818
    }
819
820
    /**
821
     * 验证是否大于某个值
822
     * @access public
823
     * @param mixed $value 字段值
824
     * @param mixed $rule  验证规则
825
     * @param array $data  数据
826
     * @return bool
827
     */
828 3
    public function gt($value, $rule, array $data = []): bool
829
    {
830 3
        return $value > $this->getDataValue($data, $rule);
831
    }
832
833
    /**
834
     * 验证是否小于等于某个值
835
     * @access public
836
     * @param mixed $value 字段值
837
     * @param mixed $rule  验证规则
838
     * @param array $data  数据
839
     * @return bool
840
     */
841 3
    public function elt($value, $rule, array $data = []): bool
842
    {
843 3
        return $value <= $this->getDataValue($data, $rule);
844
    }
845
846
    /**
847
     * 验证是否小于某个值
848
     * @access public
849
     * @param mixed $value 字段值
850
     * @param mixed $rule  验证规则
851
     * @param array $data  数据
852
     * @return bool
853
     */
854 3
    public function lt($value, $rule, array $data = []): bool
855
    {
856 3
        return $value < $this->getDataValue($data, $rule);
857
    }
858
859
    /**
860
     * 验证是否等于某个值
861
     * @access public
862
     * @param mixed $value 字段值
863
     * @param mixed $rule  验证规则
864
     * @return bool
865
     */
866 3
    public function eq($value, $rule): bool
867
    {
868 3
        return $value == $rule;
869
    }
870
871
    /**
872
     * 验证是否不等于某个值
873
     * @access public
874
     * @param mixed $value 字段值
875
     * @param mixed $rule  验证规则
876
     * @return bool
877
     */
878 3
    public function neq($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 must($value, $rule = null): bool
891
    {
892
        return !empty($value) || '0' == $value;
893
    }
894
895
    /**
896
     * 验证字段值是否为有效格式
897
     * @access public
898
     * @param mixed  $value 字段值
899
     * @param string $rule  验证规则
900
     * @param array  $data  数据
901
     * @return bool
902
     */
903 12
    public function is($value, string $rule, array $data = []): bool
904
    {
905 12
        $call = function ($value, $rule) {
906
            if (function_exists('ctype_' . $rule)) {
907
                // ctype验证规则
908
                $ctypeFun = 'ctype_' . $rule;
909
                $result   = $ctypeFun((string) $value);
910
            } elseif (isset($this->filter[$rule])) {
911
                // Filter_var验证规则
912
                $result = $this->filter($value, $this->filter[$rule]);
913
            } else {
914
                // 正则验证
915
                $result = $this->regex($value, $rule);
916
            }
917
            return $result;
918 12
        };
919
920 12
        return match (Str::camel($rule)) {
921 12
            'require' => !empty($value) || '0' == $value, // 必须
922 12
            'accepted' => in_array($value, ['1', 'on', 'yes', 'true', 1, true], true), // 接受
923 12
            'declined' => in_array($value, ['0', 'off', 'no', 'false', 0, false], true), // 不接受
924 12
            'date' => false !== strtotime($value), // 是否是一个有效日期
925 12
            'activeUrl' => checkdnsrr($value), // 是否为有效的网址
926 12
            'boolean', 'bool' => in_array($value, [true, false, 0, 1, '0', '1'], true), // 是否为布尔值
927 12
            'number' => ctype_digit((string) $value),
928 12
            'alphaNum' => ctype_alnum($value),
929 12
            'array'    => is_array($value), // 是否为数组
930 12
            'string'   => is_string($value),
931 12
            'file'     => $value instanceof File,
932 12
            'image'    => $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]),
933 12
            'token'    => $this->token($value, '__token__', $data),
934 12
            default    => $call($value, $rule),
935 12
        };
936
    }
937
938
    // 判断图像类型
939
    protected function getImageType($image)
940
    {
941
        if (function_exists('exif_imagetype')) {
942
            return exif_imagetype($image);
943
        }
944
945
        try {
946
            $info = getimagesize($image);
947
            return $info ? $info[2] : false;
948
        } catch (\Exception $e) {
949
            return false;
950
        }
951
    }
952
953
    /**
954
     * 验证表单令牌
955
     * @access public
956
     * @param mixed $value 字段值
957
     * @param mixed $rule  验证规则
958
     * @param array $data  数据
959
     * @return bool
960
     */
961
    public function token($value, string $rule, array $data): bool
962
    {
963
        $rule = !empty($rule) ? $rule : '__token__';
964
        return $this->request->checkToken($rule, $data);
965
    }
966
967
    /**
968
     * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
969
     * @access public
970
     * @param mixed $value 字段值
971
     * @param mixed $rule  验证规则
972
     * @return bool
973
     */
974
    public function activeUrl(string $value, string $rule = 'MX'): bool
975
    {
976
        if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
977
            $rule = 'MX';
978
        }
979
980
        return checkdnsrr($value, $rule);
981
    }
982
983
    /**
984
     * 验证是否有效IP
985
     * @access public
986
     * @param mixed $value 字段值
987
     * @param mixed $rule  验证规则 ipv4 ipv6
988
     * @return bool
989
     */
990
    public function ip($value, string $rule = 'ipv4'): bool
991
    {
992
        if (!in_array($rule, ['ipv4', 'ipv6'])) {
993
            $rule = 'ipv4';
994
        }
995
996
        return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
997
    }
998
999
    /**
1000
     * 检测是否以某个字符串开头
1001
     * @access public
1002
     * @param mixed $value 字段值
1003
     * @param string $rule  验证规则
1004
     * @return bool
1005
     */
1006
    public function startWith($value, string $rule): bool
1007
    {
1008
        return is_string($value) && str_starts_with($value, $rule);
1009
    }
1010
1011
    /**
1012
     * 检测是否以某个字符串结尾
1013
     * @access public
1014
     * @param mixed $value 字段值
1015
     * @param string $rule  验证规则
1016
     * @return bool
1017
     */
1018
    public function endWith($value, string $rule): bool
1019
    {
1020
        return is_string($value) && str_ends_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 contain($value, string $rule): bool
1031
    {
1032
        return is_string($value) && str_contains($value, $rule);
1033
    }
1034
1035
    /**
1036
     * 检测上传文件后缀
1037
     * @access public
1038
     * @param File         $file
1039
     * @param array|string $ext 允许后缀
1040
     * @return bool
1041
     */
1042
    protected function checkExt(File $file, $ext): bool
1043
    {
1044
        if (is_string($ext)) {
1045
            $ext = explode(',', $ext);
1046
        }
1047
1048
        return in_array(strtolower($file->extension()), $ext);
1049
    }
1050
1051
    /**
1052
     * 检测上传文件大小
1053
     * @access public
1054
     * @param File    $file
1055
     * @param integer $size 最大大小
1056
     * @return bool
1057
     */
1058
    protected function checkSize(File $file, $size): bool
1059
    {
1060
        return $file->getSize() <= (int) $size;
1061
    }
1062
1063
    /**
1064
     * 检测上传文件类型
1065
     * @access public
1066
     * @param File         $file
1067
     * @param array|string $mime 允许类型
1068
     * @return bool
1069
     */
1070
    protected function checkMime(File $file, $mime): bool
1071
    {
1072
        if (is_string($mime)) {
1073
            $mime = explode(',', $mime);
1074
        }
1075
1076
        return in_array(strtolower($file->getMime()), $mime);
1077
    }
1078
1079
    /**
1080
     * 验证上传文件后缀
1081
     * @access public
1082
     * @param mixed $file 上传文件
1083
     * @param mixed $rule 验证规则
1084
     * @return bool
1085
     */
1086
    public function fileExt($file, $rule): bool
1087
    {
1088
        if (is_array($file)) {
1089
            foreach ($file as $item) {
1090
                if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
1091
                    return false;
1092
                }
1093
            }
1094
            return true;
1095
        } elseif ($file instanceof File) {
1096
            return $this->checkExt($file, $rule);
1097
        }
1098
1099
        return false;
1100
    }
1101
1102
    /**
1103
     * 验证上传文件类型
1104
     * @access public
1105
     * @param mixed $file 上传文件
1106
     * @param mixed $rule 验证规则
1107
     * @return bool
1108
     */
1109
    public function fileMime($file, $rule): bool
1110
    {
1111
        if (is_array($file)) {
1112
            foreach ($file as $item) {
1113
                if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
1114
                    return false;
1115
                }
1116
            }
1117
            return true;
1118
        } elseif ($file instanceof File) {
1119
            return $this->checkMime($file, $rule);
1120
        }
1121
1122
        return false;
1123
    }
1124
1125
    /**
1126
     * 验证上传文件大小
1127
     * @access public
1128
     * @param mixed $file 上传文件
1129
     * @param mixed $rule 验证规则
1130
     * @return bool
1131
     */
1132
    public function fileSize($file, $rule): bool
1133
    {
1134
        if (is_array($file)) {
1135
            foreach ($file as $item) {
1136
                if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
1137
                    return false;
1138
                }
1139
            }
1140
            return true;
1141
        } elseif ($file instanceof File) {
1142
            return $this->checkSize($file, $rule);
1143
        }
1144
1145
        return false;
1146
    }
1147
1148
    /**
1149
     * 验证图片的宽高及类型
1150
     * @access public
1151
     * @param mixed $file 上传文件
1152
     * @param mixed $rule 验证规则
1153
     * @return bool
1154
     */
1155
    public function image($file, $rule): bool
1156
    {
1157
        if (!($file instanceof File)) {
1158
            return false;
1159
        }
1160
1161
        if ($rule) {
1162
            $rule = explode(',', $rule);
1163
1164
            [$width, $height, $type] = getimagesize($file->getRealPath());
1165
1166
            if (isset($rule[2])) {
1167
                $imageType = strtolower($rule[2]);
1168
1169
                if ('jpg' == $imageType) {
1170
                    $imageType = 'jpeg';
1171
                }
1172
1173
                if (image_type_to_extension($type, false) != $imageType) {
1174
                    return false;
1175
                }
1176
            }
1177
1178
            [$w, $h] = $rule;
1179
1180
            return $w == $width && $h == $height;
1181
        }
1182
1183
        return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
1184
    }
1185
1186
    /**
1187
     * 验证时间和日期是否符合指定格式
1188
     * @access public
1189
     * @param mixed $value 字段值
1190
     * @param mixed $rule  验证规则
1191
     * @return bool
1192
     */
1193
    public function dateFormat($value, $rule): bool
1194
    {
1195
        $info = date_parse_from_format($rule, $value);
1196
        return 0 == $info['warning_count'] && 0 == $info['error_count'];
1197
    }
1198
1199
    /**
1200
     * 验证是否唯一
1201
     * @access public
1202
     * @param mixed  $value 字段值
1203
     * @param mixed  $rule  验证规则 格式:数据表,字段名,排除ID,主键名
1204
     * @param array  $data  数据
1205
     * @param string $field 验证字段名
1206
     * @return bool
1207
     */
1208
    public function unique($value, $rule, array $data = [], string $field = ''): bool
1209
    {
1210
        if (is_string($rule)) {
1211
            $rule = explode(',', $rule);
1212
        }
1213
1214
        if (str_contains($rule[0], '\\')) {
1215
            // 指定模型类
1216
            $db = new $rule[0]();
1217
        } else {
1218
            $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

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