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

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