Passed
Push — 8.0 ( 3324e2...c8bf87 )
by liu
58:40 queued 10s
created

Validate::acceptedIf()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 4
nop 3
dl 0
loc 9
ccs 4
cts 5
cp 0.8
crap 3.072
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 BackedEnum;
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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