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

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