Passed
Push — 8.0 ( e229cb...c71a61 )
by liu
12:51
created

Validate::is()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 32
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 10.3999

Importance

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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