Passed
Push — 8.0 ( 8b2282...f74648 )
by liu
03:04
created

Validate::array()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

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