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

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