Passed
Push — 8.0 ( 7d1def...ddda1a )
by liu
08:47 queued 06:20
created

Validate::getDataValue()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.3731

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
nc 3
nop 2
dl 0
loc 12
ccs 5
cts 7
cp 0.7143
crap 4.3731
rs 10
c 2
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, ?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 6
            if (empty($values)) {
535
                if (is_string($rule)) {
536
                    $items = explode('|', $rule);
537
                } elseif (is_array($rule)) {
538
                    $items = $rule;
539
                }
540
541
                if (isset($items) && false !== array_search('require', $items)) {
542
                    $message = $this->getRuleMsg($key, $title, 'require', $rule);
543
                    throw new ValidateException($message, $key);
544
                }
545
            }
546
547
            // 字段数据因子验证
548 6
            foreach ($values as $value) {
549 6
                $result = $this->checkItem($key, $value, $rule, $data, $title);
550
551 6
                if (true !== $result) {
552
                    // 没有返回true 则表示验证失败
553 6
                    if (!empty($this->batch)) {
554
                        // 批量验证
555
                        $this->error[$key] = $result;
556 6
                    } elseif ($this->failException) {
557
                        throw new ValidateException($result, $key);
558
                    } else {
559 6
                        $this->error = $result;
560 6
                        return false;
561
                    }
562
                }
563
            }
564
        }
565
566
        if (!empty($this->error)) {
567
            if ($this->failException) {
568
                throw new ValidateException($this->error);
569
            }
570
            return false;
571
        }
572
573
        return true;
574
    }
575
576
    /**
577
     * 根据验证规则验证数据
578
     * @access public
579
     * @param mixed $value 字段值
580
     * @param mixed $rules 验证规则
581
     * @return bool
582
     */
583
    public function checkRule($value, $rules): bool
584
    {
585
        if ($rules instanceof Closure) {
586
            return call_user_func_array($rules, [$value]);
587
        } elseif ($rules instanceof ValidateRule) {
588
            $rules = $rules->getRule();
589
        } elseif (is_string($rules)) {
590
            $rules = explode('|', $rules);
591
        }
592
593
        foreach ($rules as $key => $rule) {
594
            if ($rule instanceof Closure) {
595
                $result = call_user_func_array($rule, [$value]);
596
            } else {
597
                // 判断验证类型
598
                [$type, $rule] = $this->getValidateType($key, $rule);
599
600
                $result = call_user_func_array($this->type[$type] ?? [$this, $type], [$value, $rule]);
601
            }
602
603
            if (true !== $result) {
604
                if ($this->failException) {
605
                    throw new ValidateException($result, $key);
606
                }
607
608
                return $result;
609
            }
610
        }
611
612
        return true;
613
    }
614
615
    /**
616
     * 验证单个字段规则
617
     * @access protected
618
     * @param string $field 字段名
619
     * @param mixed  $value 字段值
620
     * @param mixed  $rules 验证规则
621
     * @param array  $data  数据
622
     * @param string $title 字段描述
623
     * @param array  $msg   提示信息
624
     * @return mixed
625
     */
626 6
    protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = []): mixed
627
    {
628 6
        if ($rules instanceof Closure) {
629
            return call_user_func_array($rules, [$value, $data]);
630
        }
631
632 6
        if ($rules instanceof ValidateRule) {
633
            $title = $rules->getTitle() ?: $title;
634
            $msg   = $rules->getMsg();
635
            $rules = $rules->getRule();
636
        }
637
638 6
        if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
639
            // 字段已经移除 无需验证
640
            return true;
641
        }
642
643 6
        if (isset($this->replace[$field])) {
644
            $rules = $this->replace[$field];
645 6
        } elseif (isset($this->only[$field])) {
646
            $rules = $this->only[$field];
647
        }
648
649
        // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
650 6
        if (is_string($rules)) {
651 6
            $rules = explode('|', $rules);
652
        }
653
654 6
        if (isset($this->append[$field])) {
655
            // 追加额外的验证规则
656
            $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
657
            unset($this->append[$field]);
658
        }
659
660 6
        if (empty($rules)) {
661
            return true;
662
        }
663
664 6
        $i = 0;
665 6
        foreach ($rules as $key => $rule) {
666 6
            if ($rule instanceof Closure) {
667
                $result = call_user_func_array($rule, [$value, $data]);
668
                $info   = is_numeric($key) ? '' : $key;
669
            } else {
670
                // 判断验证类型
671 6
                [$type, $rule, $info] = $this->getValidateType($key, $rule);
672
673 6
                if (isset($this->append[$field]) && in_array($info, $this->append[$field])) {
674 6
                } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) {
675
                    // 规则已经移除
676
                    $i++;
677
                    continue;
678
                }
679
680 6
                if (isset($this->type[$type])) {
681
                    $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]);
682 6
                } elseif ('must' == $info || str_starts_with($info, 'require') || (!is_null($value) && '' !== $value)) {
683 6
                    $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]);
684 6
                } elseif (str_starts_with($info, 'accepted') || str_starts_with($info, 'declined')) {
685 6
                    $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]);
686
                } else {
687
                    $result = true;
688
                }
689
            }
690
691 6
            if (false === $result) {
692
                // 验证失败 返回错误信息
693 6
                if (!empty($msg[$i])) {
694
                    $message = $msg[$i];
695
                    if (is_string($message) && str_starts_with($message, '{%')) {
696
                        $message = $this->lang->get(substr($message, 2, -1));
697
                    }
698
                } else {
699 6
                    $message = $this->getRuleMsg($field, $title, $info, $rule);
700
                }
701
702 6
                return $message;
703 6
            } elseif (true !== $result) {
704
                // 返回自定义错误信息
705
                if (is_string($result) && str_contains($result, ':')) {
706
                    $result = str_replace(':attribute', $title, $result);
707
708
                    if (str_contains($result, ':rule') && is_scalar($rule)) {
709
                        $result = str_replace(':rule', (string) $rule, $result);
710
                    }
711
                }
712
713
                return $result;
714
            }
715 6
            $i++;
716
        }
717
718 6
        return $result ?? true;
719
    }
720
721
    /**
722
     * 获取当前验证类型及规则
723
     * @access public
724
     * @param mixed $key
725
     * @param mixed $rule
726
     * @return array
727
     */
728 6
    protected function getValidateType($key, $rule): array
729
    {
730
        // 判断验证类型
731 6
        if (!is_numeric($key)) {
732
            if (isset($this->alias[$key])) {
733
                // 判断别名
734
                $key = $this->alias[$key];
735
            }
736
            return [$key, $rule, $key];
737
        }
738
739 6
        if (str_contains($rule, ':')) {
740 6
            [$type, $rule] = explode(':', $rule, 2);
741 6
            if (isset($this->alias[$type])) {
742
                // 判断别名
743
                $type = $this->alias[$type];
744
            }
745 6
            $info = $type;
746 6
        } elseif (method_exists($this, $rule)) {
747
            $type = $rule;
748
            $info = $rule;
749
            $rule = '';
750
        } else {
751 6
            $type = 'is';
752 6
            $info = $rule;
753
        }
754
755 6
        return [$type, $rule, $info];
756
    }
757
758
    /**
759
     * 验证是否和某个字段的值一致
760
     * @access public
761
     * @param mixed  $value 字段值
762
     * @param mixed  $rule  验证规则
763
     * @param array  $data  数据
764
     * @param string $field 字段名
765
     * @return bool
766
     */
767
    public function confirm($value, $rule, array $data = [], string $field = ''): bool
768
    {
769
        if ('' == $rule) {
770
            if (str_contains($field, '_confirm')) {
771
                $rule = strstr($field, '_confirm', true);
772
            } else {
773
                $rule = $field . '_confirm';
774
            }
775
        }
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 different($value, $rule, array $data = []): bool
789
    {
790
        return $this->getDataValue($data, $rule) != $value;
791
    }
792
793
    /**
794
     * 验证是否大于等于某个值
795
     * @access public
796
     * @param mixed $value 字段值
797
     * @param mixed $rule  验证规则
798
     * @param array $data  数据
799
     * @return bool
800
     */
801
    public function egt($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 gt($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 elt($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
     * @param array $data  数据
838
     * @return bool
839
     */
840
    public function lt($value, $rule, array $data = []): bool
841
    {
842
        return $value < $this->getDataValue($data, $rule);
843
    }
844
845
    /**
846
     * 验证是否等于某个值
847
     * @access public
848
     * @param mixed $value 字段值
849
     * @param mixed $rule  验证规则
850
     * @return bool
851
     */
852
    public function eq($value, $rule): bool
853
    {
854
        return $value == $rule;
855
    }
856
857
    /**
858
     * 必须验证
859
     * @access public
860
     * @param mixed $value 字段值
861
     * @param mixed $rule  验证规则
862
     * @return bool
863
     */
864
    public function must($value, $rule = null): bool
865
    {
866
        return !empty($value) || '0' == $value;
867
    }
868
869
    /**
870
     * 验证字段值是否为有效格式
871
     * @access public
872
     * @param mixed  $value 字段值
873
     * @param string $rule  验证规则
874
     * @param array  $data  数据
875
     * @return bool
876
     */
877 12
    public function is($value, string $rule, array $data = []): bool
878
    {
879 12
        $call = function ($value, $rule) {
880
            if (isset($this->type[$rule])) {
881
                // 注册的验证规则
882
                $result = call_user_func_array($this->type[$rule], [$value]);
883
            } elseif (function_exists('ctype_' . $rule)) {
884
                // ctype验证规则
885
                $ctypeFun = 'ctype_' . $rule;
886
                $result   = $ctypeFun((string) $value);
887
            } elseif (isset($this->filter[$rule])) {
888
                // Filter_var验证规则
889
                $result = $this->filter($value, $this->filter[$rule]);
890
            } else {
891
                // 正则验证
892
                $result = $this->regex($value, $rule);
893
            }
894
            return $result;
895 12
        };
896
897 12
        return match (Str::camel($rule)) {
898 12
            'require' => !empty($value) || '0' == $value, // 必须
899 12
            'accepted' => in_array($value, ['1', 'on', 'yes', 'true', 1, true], true), // 接受
900 12
            'declined' => in_array($value, ['0', 'off', 'no', 'false', 0, false], true), // 不接受
901 12
            'date' => false !== strtotime($value), // 是否是一个有效日期
902 12
            'activeUrl' => checkdnsrr($value), // 是否为有效的网址
903 12
            'boolean', 'bool' => in_array($value, [true, false, 0, 1, '0', '1'], true), // 是否为布尔值
904 12
            'number' => ctype_digit((string) $value),
905 12
            'alphaNum' => ctype_alnum($value),
906 12
            'array'    => is_array($value), // 是否为数组
907 12
            'string'   => is_string($value),
908 12
            'file'     => $value instanceof File,
909 12
            'image'    => $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]),
910 12
            'token'    => $this->token($value, '__token__', $data),
911 12
            default    => $call($value, $rule),
912 12
        };
913
    }
914
915
    // 判断图像类型
916
    protected function getImageType($image)
917
    {
918
        if (function_exists('exif_imagetype')) {
919
            return exif_imagetype($image);
920
        }
921
922
        try {
923
            $info = getimagesize($image);
924
            return $info ? $info[2] : false;
925
        } catch (\Exception $e) {
926
            return false;
927
        }
928
    }
929
930
    /**
931
     * 验证表单令牌
932
     * @access public
933
     * @param mixed $value 字段值
934
     * @param mixed $rule  验证规则
935
     * @param array $data  数据
936
     * @return bool
937
     */
938
    public function token($value, string $rule, array $data): bool
939
    {
940
        $rule = !empty($rule) ? $rule : '__token__';
941
        return $this->request->checkToken($rule, $data);
942
    }
943
944
    /**
945
     * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
946
     * @access public
947
     * @param mixed $value 字段值
948
     * @param mixed $rule  验证规则
949
     * @return bool
950
     */
951
    public function activeUrl(string $value, string $rule = 'MX'): bool
952
    {
953
        if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
954
            $rule = 'MX';
955
        }
956
957
        return checkdnsrr($value, $rule);
958
    }
959
960
    /**
961
     * 验证是否有效IP
962
     * @access public
963
     * @param mixed $value 字段值
964
     * @param mixed $rule  验证规则 ipv4 ipv6
965
     * @return bool
966
     */
967
    public function ip($value, string $rule = 'ipv4'): bool
968
    {
969
        if (!in_array($rule, ['ipv4', 'ipv6'])) {
970
            $rule = 'ipv4';
971
        }
972
973
        return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
974
    }
975
976
    /**
977
     * 检测是否以某个字符串开头
978
     * @access public
979
     * @param mixed $value 字段值
980
     * @param string $rule  验证规则
981
     * @return bool
982
     */
983
    public function startWith($value, string $rule): bool
984
    {
985
        return is_string($value) && str_starts_with($value, $rule);
986
    }
987
988
    /**
989
     * 检测是否以某个字符串结尾
990
     * @access public
991
     * @param mixed $value 字段值
992
     * @param string $rule  验证规则
993
     * @return bool
994
     */
995
    public function endWith($value, string $rule): bool
996
    {
997
        return is_string($value) && str_ends_with($value, $rule);
998
    }
999
1000
    /**
1001
     * 检测是否以包含某个字符串
1002
     * @access public
1003
     * @param mixed $value 字段值
1004
     * @param string $rule  验证规则
1005
     * @return bool
1006
     */
1007
    public function contain($value, string $rule): bool
1008
    {
1009
        return is_string($value) && str_contains($value, $rule);
1010
    }
1011
1012
    /**
1013
     * 检测上传文件后缀
1014
     * @access public
1015
     * @param File         $file
1016
     * @param array|string $ext 允许后缀
1017
     * @return bool
1018
     */
1019
    protected function checkExt(File $file, $ext): bool
1020
    {
1021
        if (is_string($ext)) {
1022
            $ext = explode(',', $ext);
1023
        }
1024
1025
        return in_array(strtolower($file->extension()), $ext);
1026
    }
1027
1028
    /**
1029
     * 检测上传文件大小
1030
     * @access public
1031
     * @param File    $file
1032
     * @param integer $size 最大大小
1033
     * @return bool
1034
     */
1035
    protected function checkSize(File $file, $size): bool
1036
    {
1037
        return $file->getSize() <= (int) $size;
1038
    }
1039
1040
    /**
1041
     * 检测上传文件类型
1042
     * @access public
1043
     * @param File         $file
1044
     * @param array|string $mime 允许类型
1045
     * @return bool
1046
     */
1047
    protected function checkMime(File $file, $mime): bool
1048
    {
1049
        if (is_string($mime)) {
1050
            $mime = explode(',', $mime);
1051
        }
1052
1053
        return in_array(strtolower($file->getMime()), $mime);
1054
    }
1055
1056
    /**
1057
     * 验证上传文件后缀
1058
     * @access public
1059
     * @param mixed $file 上传文件
1060
     * @param mixed $rule 验证规则
1061
     * @return bool
1062
     */
1063
    public function fileExt($file, $rule): bool
1064
    {
1065
        if (is_array($file)) {
1066
            foreach ($file as $item) {
1067
                if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
1068
                    return false;
1069
                }
1070
            }
1071
            return true;
1072
        } elseif ($file instanceof File) {
1073
            return $this->checkExt($file, $rule);
1074
        }
1075
1076
        return false;
1077
    }
1078
1079
    /**
1080
     * 验证上传文件类型
1081
     * @access public
1082
     * @param mixed $file 上传文件
1083
     * @param mixed $rule 验证规则
1084
     * @return bool
1085
     */
1086
    public function fileMime($file, $rule): bool
1087
    {
1088
        if (is_array($file)) {
1089
            foreach ($file as $item) {
1090
                if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
1091
                    return false;
1092
                }
1093
            }
1094
            return true;
1095
        } elseif ($file instanceof File) {
1096
            return $this->checkMime($file, $rule);
1097
        }
1098
1099
        return false;
1100
    }
1101
1102
    /**
1103
     * 验证上传文件大小
1104
     * @access public
1105
     * @param mixed $file 上传文件
1106
     * @param mixed $rule 验证规则
1107
     * @return bool
1108
     */
1109
    public function fileSize($file, $rule): bool
1110
    {
1111
        if (is_array($file)) {
1112
            foreach ($file as $item) {
1113
                if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
1114
                    return false;
1115
                }
1116
            }
1117
            return true;
1118
        } elseif ($file instanceof File) {
1119
            return $this->checkSize($file, $rule);
1120
        }
1121
1122
        return false;
1123
    }
1124
1125
    /**
1126
     * 验证图片的宽高及类型
1127
     * @access public
1128
     * @param mixed $file 上传文件
1129
     * @param mixed $rule 验证规则
1130
     * @return bool
1131
     */
1132
    public function image($file, $rule): bool
1133
    {
1134
        if (!($file instanceof File)) {
1135
            return false;
1136
        }
1137
1138
        if ($rule) {
1139
            $rule = explode(',', $rule);
1140
1141
            [$width, $height, $type] = getimagesize($file->getRealPath());
1142
1143
            if (isset($rule[2])) {
1144
                $imageType = strtolower($rule[2]);
1145
1146
                if ('jpg' == $imageType) {
1147
                    $imageType = 'jpeg';
1148
                }
1149
1150
                if (image_type_to_extension($type, false) != $imageType) {
1151
                    return false;
1152
                }
1153
            }
1154
1155
            [$w, $h] = $rule;
1156
1157
            return $w == $width && $h == $height;
1158
        }
1159
1160
        return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
1161
    }
1162
1163
    /**
1164
     * 验证时间和日期是否符合指定格式
1165
     * @access public
1166
     * @param mixed $value 字段值
1167
     * @param mixed $rule  验证规则
1168
     * @return bool
1169
     */
1170
    public function dateFormat($value, $rule): bool
1171
    {
1172
        $info = date_parse_from_format($rule, $value);
1173
        return 0 == $info['warning_count'] && 0 == $info['error_count'];
1174
    }
1175
1176
    /**
1177
     * 验证是否唯一
1178
     * @access public
1179
     * @param mixed  $value 字段值
1180
     * @param mixed  $rule  验证规则 格式:数据表,字段名,排除ID,主键名
1181
     * @param array  $data  数据
1182
     * @param string $field 验证字段名
1183
     * @return bool
1184
     */
1185
    public function unique($value, $rule, array $data = [], string $field = ''): bool
1186
    {
1187
        if (is_string($rule)) {
1188
            $rule = explode(',', $rule);
1189
        }
1190
1191
        if (str_contains($rule[0], '\\')) {
1192
            // 指定模型类
1193
            $db = new $rule[0];
1194
        } else {
1195
            $db = $this->db->name($rule[0]);
0 ignored issues
show
Bug introduced by
The method name() does not exist on think\Db. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

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