Passed
Pull Request — 6.0 (#2625)
by
unknown
13:13
created

Validate::requireCallback()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

1144
            /** @scrutinizer ignore-call */ 
1145
            $db = $this->db->name($rule[0]);
Loading history...
1145
        }
1146
1147
        $key = $rule[1] ?? $field;
1148
        $map = [];
1149
1150
        if (strpos($key, '^')) {
1151
            // 支持多个字段验证
1152
            $fields = explode('^', $key);
1153
            foreach ($fields as $key) {
1154
                if (isset($data[$key])) {
1155
                    $map[] = [$key, '=', $data[$key]];
1156
                }
1157
            }
1158
        } elseif (isset($data[$field])) {
1159
            $map[] = [$key, '=', $data[$field]];
1160
        }
1161
1162
        $pk = !empty($rule[3]) ? $rule[3] : $db->getPk();
1163
1164
        if (is_string($pk)) {
1165
            if (isset($rule[2])) {
1166
                $map[] = [$pk, '<>', $rule[2]];
1167
            } elseif (isset($data[$pk])) {
1168
                $map[] = [$pk, '<>', $data[$pk]];
1169
            }
1170
        }
1171
1172
        // 软删除数据条件
1173
        if (isset($rule[4]) && false != $rule[4]) {
1174
            if (is_array($rule[4])) {
1175
                // 数组则直接设置为查询条件
1176
                $map[] = $rule[4];
1177
            } else {
1178
                // 其它则按照默认规则设置软删除条件
1179
                $map[] = ['delete_time', 'null', null];
1180
            }
1181
        }
1182
1183
        if ($db->where($map)->field($pk)->find()) {
1184
            return false;
1185
        }
1186
1187
        return true;
1188
    }
1189
1190
    /**
1191
     * 使用filter_var方式验证
1192
     * @access public
1193
     * @param mixed $value 字段值
1194
     * @param mixed $rule  验证规则
1195
     * @return bool
1196
     */
1197
    public function filter($value, $rule): bool
1198
    {
1199
        if (is_string($rule) && strpos($rule, ',')) {
1200
            [$rule, $param] = explode(',', $rule);
1201
        } elseif (is_array($rule)) {
1202
            $param = $rule[1] ?? 0;
1203
            $rule  = $rule[0];
1204
        } else {
1205
            $param = 0;
1206
        }
1207
1208
        return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
1209
    }
1210
1211
    /**
1212
     * 验证某个字段等于某个值的时候必须
1213
     * @access public
1214
     * @param mixed $value 字段值
1215
     * @param mixed $rule  验证规则
1216
     * @param array $data  数据
1217
     * @return bool
1218
     */
1219
    public function requireIf($value, $rule, array $data = []): bool
1220
    {
1221
        [$field, $val] = explode(',', $rule);
1222
1223
        if ($this->getDataValue($data, $field) == $val) {
1224
            return !empty($value) || '0' == $value;
1225
        }
1226
1227
        return true;
1228
    }
1229
1230
    /**
1231
     * 通过回调方法验证某个字段是否必须
1232
     * @access public
1233
     * @param mixed $value 字段值
1234
     * @param mixed $rule  验证规则
1235
     * @param array $data  数据
1236
     * @return bool
1237
     */
1238
    public function requireCallback($value, $rule, array $data = []): bool
1239
    {
1240
        $result = call_user_func_array([$this, $rule], [$value, $data]);
1241
1242
        if ($result) {
1243
            return !empty($value) || '0' == $value;
1244
        }
1245
1246
        return true;
1247
    }
1248
1249
    /**
1250
     * 验证某个字段有值的情况下必须
1251
     * @access public
1252
     * @param mixed $value 字段值
1253
     * @param mixed $rule  验证规则
1254
     * @param array $data  数据
1255
     * @return bool
1256
     */
1257
    public function requireWith($value, $rule, array $data = []): bool
1258
    {
1259
        $val = $this->getDataValue($data, $rule);
1260
1261
        if (!empty($val)) {
1262
            return !empty($value) || '0' == $value;
1263
        }
1264
1265
        return true;
1266
    }
1267
1268
    /**
1269
     * 验证某个字段没有值的情况下必须
1270
     * @access public
1271
     * @param mixed $value 字段值
1272
     * @param mixed $rule  验证规则
1273
     * @param array $data  数据
1274
     * @return bool
1275
     */
1276
    public function requireWithout($value, $rule, array $data = []): bool
1277
    {
1278
        $val = $this->getDataValue($data, $rule);
1279
1280
        if (empty($val)) {
1281
            return !empty($value) || '0' == $value;
1282
        }
1283
1284
        return true;
1285
    }
1286
1287
    /**
1288
     * 验证是否在范围内
1289
     * @access public
1290
     * @param mixed $value 字段值
1291
     * @param mixed $rule  验证规则
1292
     * @return bool
1293
     */
1294
    public function in($value, $rule): bool
1295
    {
1296
        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1297
    }
1298
1299
    /**
1300
     * 验证是否不在某个范围
1301
     * @access public
1302
     * @param mixed $value 字段值
1303
     * @param mixed $rule  验证规则
1304
     * @return bool
1305
     */
1306
    public function notIn($value, $rule): bool
1307
    {
1308
        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1309
    }
1310
1311
    /**
1312
     * between验证数据
1313
     * @access public
1314
     * @param mixed $value 字段值
1315
     * @param mixed $rule  验证规则
1316
     * @return bool
1317
     */
1318
    public function between($value, $rule): bool
1319
    {
1320
        if (is_string($rule)) {
1321
            $rule = explode(',', $rule);
1322
        }
1323
        [$min, $max] = $rule;
1324
1325
        return $value >= $min && $value <= $max;
1326
    }
1327
1328
    /**
1329
     * 使用notbetween验证数据
1330
     * @access public
1331
     * @param mixed $value 字段值
1332
     * @param mixed $rule  验证规则
1333
     * @return bool
1334
     */
1335
    public function notBetween($value, $rule): bool
1336
    {
1337
        if (is_string($rule)) {
1338
            $rule = explode(',', $rule);
1339
        }
1340
        [$min, $max] = $rule;
1341
1342
        return $value < $min || $value > $max;
1343
    }
1344
1345
    /**
1346
     * 验证数据长度
1347
     * @access public
1348
     * @param mixed $value 字段值
1349
     * @param mixed $rule  验证规则
1350
     * @return bool
1351
     */
1352
    public function length($value, $rule): bool
1353
    {
1354
        if (is_array($value)) {
1355
            $length = count($value);
1356
        } elseif ($value instanceof File) {
1357
            $length = $value->getSize();
1358
        } else {
1359
            $length = mb_strlen((string) $value);
1360
        }
1361
1362
        if (is_string($rule) && strpos($rule, ',')) {
1363
            // 长度区间
1364
            [$min, $max] = explode(',', $rule);
1365
            return $length >= $min && $length <= $max;
1366
        }
1367
1368
        // 指定长度
1369
        return $length == $rule;
1370
    }
1371
1372
    /**
1373
     * 验证数据最大长度
1374
     * @access public
1375
     * @param mixed $value 字段值
1376
     * @param mixed $rule  验证规则
1377
     * @return bool
1378
     */
1379
    public function max($value, $rule): bool
1380
    {
1381
        if (is_array($value)) {
1382
            $length = count($value);
1383
        } elseif ($value instanceof File) {
1384
            $length = $value->getSize();
1385
        } else {
1386
            $length = mb_strlen((string) $value);
1387
        }
1388
1389
        return $length <= $rule;
1390
    }
1391
1392
    /**
1393
     * 验证数据最小长度
1394
     * @access public
1395
     * @param mixed $value 字段值
1396
     * @param mixed $rule  验证规则
1397
     * @return bool
1398
     */
1399
    public function min($value, $rule): bool
1400
    {
1401
        if (is_array($value)) {
1402
            $length = count($value);
1403
        } elseif ($value instanceof File) {
1404
            $length = $value->getSize();
1405
        } else {
1406
            $length = mb_strlen((string) $value);
1407
        }
1408
1409
        return $length >= $rule;
1410
    }
1411
1412
    /**
1413
     * 验证日期
1414
     * @access public
1415
     * @param mixed $value 字段值
1416
     * @param mixed $rule  验证规则
1417
     * @param array $data  数据
1418
     * @return bool
1419
     */
1420
    public function after($value, $rule, array $data = []): bool
1421
    {
1422
        return strtotime($value) >= strtotime($rule);
1423
    }
1424
1425
    /**
1426
     * 验证日期
1427
     * @access public
1428
     * @param mixed $value 字段值
1429
     * @param mixed $rule  验证规则
1430
     * @param array $data  数据
1431
     * @return bool
1432
     */
1433
    public function before($value, $rule, array $data = []): bool
1434
    {
1435
        return strtotime($value) <= strtotime($rule);
1436
    }
1437
1438
    /**
1439
     * 验证日期
1440
     * @access public
1441
     * @param mixed $value 字段值
1442
     * @param mixed $rule  验证规则
1443
     * @param array $data  数据
1444
     * @return bool
1445
     */
1446
    public function afterWith($value, $rule, array $data = []): bool
1447
    {
1448
        $rule = $this->getDataValue($data, $rule);
1449
        return !is_null($rule) && strtotime($value) >= strtotime($rule);
1450
    }
1451
1452
    /**
1453
     * 验证日期
1454
     * @access public
1455
     * @param mixed $value 字段值
1456
     * @param mixed $rule  验证规则
1457
     * @param array $data  数据
1458
     * @return bool
1459
     */
1460
    public function beforeWith($value, $rule, array $data = []): bool
1461
    {
1462
        $rule = $this->getDataValue($data, $rule);
1463
        return !is_null($rule) && strtotime($value) <= strtotime($rule);
1464
    }
1465
1466
    /**
1467
     * 验证有效期
1468
     * @access public
1469
     * @param mixed $value 字段值
1470
     * @param mixed $rule  验证规则
1471
     * @return bool
1472
     */
1473
    public function expire($value, $rule): bool
1474
    {
1475
        if (is_string($rule)) {
1476
            $rule = explode(',', $rule);
1477
        }
1478
1479
        [$start, $end] = $rule;
1480
1481
        if (!is_numeric($start)) {
1482
            $start = strtotime($start);
1483
        }
1484
1485
        if (!is_numeric($end)) {
1486
            $end = strtotime($end);
1487
        }
1488
1489
        return time() >= $start && time() <= $end;
1490
    }
1491
1492
    /**
1493
     * 验证IP许可
1494
     * @access public
1495
     * @param mixed $value 字段值
1496
     * @param mixed $rule  验证规则
1497
     * @return bool
1498
     */
1499
    public function allowIp($value, $rule): bool
1500
    {
1501
        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1502
    }
1503
1504
    /**
1505
     * 验证IP禁用
1506
     * @access public
1507
     * @param mixed $value 字段值
1508
     * @param mixed $rule  验证规则
1509
     * @return bool
1510
     */
1511
    public function denyIp($value, $rule): bool
1512
    {
1513
        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
1514
    }
1515
1516
    /**
1517
     * 使用正则验证数据
1518
     * @access public
1519
     * @param mixed $value 字段值
1520
     * @param mixed $rule  验证规则 正则规则或者预定义正则名
1521
     * @return bool
1522
     */
1523
    public function regex($value, $rule): bool
1524
    {
1525
        if (isset($this->regex[$rule])) {
1526
            $rule = $this->regex[$rule];
1527
        } elseif (isset($this->defaultRegex[$rule])) {
1528
            $rule = $this->defaultRegex[$rule];
1529
        }
1530
1531
        if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
1532
            // 不是正则表达式则两端补上/
1533
            $rule = '/^' . $rule . '$/';
1534
        }
1535
1536
        return is_scalar($value) && 1 === preg_match($rule, (string) $value);
1537
    }
1538
1539
    /**
1540
     * 获取错误信息
1541
     * @return array|string
1542
     */
1543
    public function getError()
1544
    {
1545
        return $this->error;
1546
    }
1547
1548
    /**
1549
     * 获取数据值
1550
     * @access protected
1551
     * @param array  $data 数据
1552
     * @param string $key  数据标识 支持二维
1553
     * @return mixed
1554
     */
1555
    protected function getDataValue(array $data, $key)
1556
    {
1557
        if (is_numeric($key)) {
1558
            $value = $key;
1559
        } elseif (is_string($key) && strpos($key, '.')) {
1560
            // 支持多维数组验证
1561
            foreach (explode('.', $key) as $key) {
0 ignored issues
show
introduced by
$key is overwriting one of the parameters of this function.
Loading history...
1562
                if (!isset($data[$key])) {
1563
                    $value = null;
1564
                    break;
1565
                }
1566
                $value = $data = $data[$key];
1567
            }
1568
        } else {
1569
            $value = $data[$key] ?? null;
1570
        }
1571
1572
        return $value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.
Loading history...
1573
    }
1574
1575
    /**
1576
     * 获取验证规则的错误提示信息
1577
     * @access protected
1578
     * @param string $attribute 字段英文名
1579
     * @param string $title     字段描述名
1580
     * @param string $type      验证规则名称
1581
     * @param mixed  $rule      验证规则数据
1582
     * @return string|array
1583
     */
1584
    protected function getRuleMsg(string $attribute, string $title, string $type, $rule)
1585
    {
1586
        if (isset($this->message[$attribute . '.' . $type])) {
1587
            $msg = $this->message[$attribute . '.' . $type];
1588
        } elseif (isset($this->message[$attribute][$type])) {
1589
            $msg = $this->message[$attribute][$type];
1590
        } elseif (isset($this->message[$attribute])) {
1591
            $msg = $this->message[$attribute];
1592
        } elseif (isset($this->typeMsg[$type])) {
1593
            $msg = $this->typeMsg[$type];
1594
        } elseif (0 === strpos($type, 'require')) {
1595
            $msg = $this->typeMsg['require'];
1596
        } else {
1597
            $msg = $title . $this->lang->get('not conform to the rules');
1598
        }
1599
1600
        if (is_array($msg)) {
1601
            return $this->errorMsgIsArray($msg, $rule, $title);
1602
        }
1603
1604
        return $this->parseErrorMsg($msg, $rule, $title);
1605
    }
1606
1607
    /**
1608
     * 获取验证规则的错误提示信息
1609
     * @access protected
1610
     * @param string $msg   错误信息
1611
     * @param mixed  $rule  验证规则数据
1612
     * @param string $title 字段描述名
1613
     * @return string|array
1614
     */
1615
    protected function parseErrorMsg(string $msg, $rule, string $title)
1616
    {
1617
        if (0 === strpos($msg, '{%')) {
1618
            $msg = $this->lang->get(substr($msg, 2, -1));
1619
        } elseif ($this->lang->has($msg)) {
1620
            $msg = $this->lang->get($msg);
1621
        }
1622
1623
        if (is_array($msg)) {
1624
            return $this->errorMsgIsArray($msg, $rule, $title);
1625
        }
1626
1627
        // rule若是数组则转为字符串
1628
        if (is_array($rule)) {
1629
            $rule = implode(',', $rule);
1630
        }
1631
1632
        if (is_scalar($rule) && false !== strpos($msg, ':')) {
1633
            // 变量替换
1634
            if (is_string($rule) && strpos($rule, ',')) {
1635
                $array = array_pad(explode(',', $rule), 3, '');
1636
            } else {
1637
                $array = array_pad([], 3, '');
1638
            }
1639
1640
            $msg = str_replace(
1641
                [':attribute', ':1', ':2', ':3'],
1642
                [$title, $array[0], $array[1], $array[2]],
1643
                $msg
1644
            );
1645
1646
            if (strpos($msg, ':rule')) {
1647
                $msg = str_replace(':rule', (string) $rule, $msg);
1648
            }
1649
        }
1650
1651
        return $msg;
1652
    }
1653
1654
    /**
1655
     * 错误信息数组处理
1656
     * @access protected
1657
     * @param array $msg   错误信息
1658
     * @param mixed  $rule  验证规则数据
1659
     * @param string $title 字段描述名
1660
     * @return array
1661
     */
1662
    protected function errorMsgIsArray(array $msg, $rule, string $title)
1663
    {
1664
        foreach ($msg as $key => $val) {
1665
            if (is_string($val)) {
1666
                $msg[$key] = $this->parseErrorMsg($val, $rule, $title);
1667
            }
1668
        }
1669
        return $msg;
1670
    }
1671
1672
    /**
1673
     * 获取数据验证的场景
1674
     * @access protected
1675
     * @param string $scene 验证场景
1676
     * @return void
1677
     */
1678
    protected function getScene(string $scene): void
1679
    {
1680
        $this->only = $this->append = $this->remove = [];
1681
1682
        if (method_exists($this, 'scene' . $scene)) {
1683
            call_user_func([$this, 'scene' . $scene]);
1684
        } elseif (isset($this->scene[$scene])) {
1685
            // 如果设置了验证适用场景
1686
            $this->only = $this->scene[$scene];
1687
        }
1688
    }
1689
1690
    /**
1691
     * 动态方法 直接调用is方法进行验证
1692
     * @access public
1693
     * @param string $method 方法名
1694
     * @param array  $args   调用参数
1695
     * @return bool
1696
     */
1697
    public function __call($method, $args)
1698
    {
1699
        if ('is' == strtolower(substr($method, 0, 2))) {
1700
            $method = substr($method, 2);
1701
        }
1702
1703
        array_push($args, lcfirst($method));
1704
1705
        return call_user_func_array([$this, 'is'], $args);
1706
    }
1707
}
1708