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

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