Passed
Push — 8.0 ( ba123a...8bf22a )
by liu
02:15
created

Validate::multipleOf()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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

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