Passed
Push — 8.0 ( f74648...4c7881 )
by liu
02:20
created

Validate::fileExt()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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

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