Passed
Push — 8.0 ( 75c977...e8c215 )
by liu
12:56
created

Validate::parseUserErrorMessage()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 5

Importance

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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