Passed
Pull Request — 8.0 (#3079)
by wj
02:17
created

Validate::parseErrorMsg()   B

Complexity

Conditions 10
Paths 33

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 12.332

Importance

Changes 0
Metric Value
cc 10
eloc 20
c 0
b 0
f 0
nc 33
nop 3
dl 0
loc 37
ccs 15
cts 21
cp 0.7143
crap 12.332
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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