Passed
Push — 8.0 ( 73f4bb...3324e2 )
by liu
07:47
created

Validate::enum()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

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