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

Validate::parseErrorMsg()   B

Complexity

Conditions 10
Paths 33

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 12.332

Importance

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

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

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