目标:
上篇文章我们成功构建了异常处理,下面我们来构建自己的验证器
在官方文档:validator 中,验证器常常以注解的方式使用,我们构建的restful架构使用自定义验证器。
验证器的简单使用 安装:
1 composer require symfony/validator
我们可以在控制器中使用简单的验证器来验证数据,像这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php namespace App \Controller ;use Symfony \Component \HttpFoundation \Response ;use Symfony \Component \Routing \Annotation \Route ;use Symfony \Component \Validator \Validation ;use Symfony \Component \Validator \Constraints as Assert ;use App \Exception \Parameter ;use App \Exception \Success ;class Word { public function index ( ) { $validator = Validation ::createValidator (); $violations = $validator ->validate ([ 'num' => 12 ], new Assert\Collection ([ 'num' => new Assert\GreaterThan ([ 'value' => 10 , ]) ])) if (0 !== count ($violations )) { foreach ($res as $item ) { $prefix = empty ($message ) ? '' : ';' ; $message .= $prefix . $item ->getMessage (); } throw new Parameter (['message' => $message ]); } throw new Success (); } }
如上,
如果验证通过就会抛出Success,表示请求成功且数据验证通过。
如果验证失败就会抛出Parameter,提示用户数据验证失败。
自定义验证器 上面我们已经能够成功使用验证器了,但是距离我们的预期太远了。如果多几个数据,多几个约束,光是一个数据的校验就几十行代码,可读性极差,不能重用,日后维护也很困难
我们需要构建自己的验证器 。App\Validator\Base代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?php namespace App \Validator ;use App \Exception \Parameter ;use Symfony \Component \Validator \Constraints \Collection ;use Symfony \Component \Validator \Validation ;use Symfony \Component \Validator \Validator \ValidatorInterface ;class Base { protected $collection ; protected static $validator ; public function __construct ( ) { if (empty (self ::getValidator () instanceof ValidatorInterface)) { self ::setValidator (); } static ::setCollection (); } public function check (array $input ) { $violations = self ::getValidator ()->validate ($input , self ::getCollection ()); $message = '' ; foreach ($violations as $$violation ) { $prefix = empty ($message ) ? '' : ';' ; $message .= $prefix . $$violation ->getMessage (); } if ($message ) throw new Parameter (['message' => $message ]); } }
上面的代码中,我们构建了自己的验证器基类。 只要验证不通过,就会抛出参数异常的错误(Parameter); 子类只需要实现setCollection定义约束就可以使用了。
自定义验证器的使用 我们来构建一个子类App\Validator\Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php namespace App \Validator ;use Symfony \Component \Validator \Constraints as Assert ;class Example extends Base { public function setCollection ( ) { $this ->collection = new Assert\Collection ([ 'num' => new Assert\GreaterThan ([ 'value' => 10 , ]) ]); } }
让我们在控制器中使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php namespace App \Controller ;use Symfony \Component \HttpFoundation \Response ;use Symfony \Component \Routing \Annotation \Route ;use App \Validator \Example ;use App \Exception \Success ;class Word { public function index ( ) { $data = ['num' => 12 ]; (new Example ())->check ($data ); throw new Success (); } }
从数行代码变为一行代码,是不是简洁多了?
定义规则
上面我们虽然实现了自定义验证器。但是用的规则还是symfony/validation里面的,当需要验证特殊字段的时候(比如验证手机号码),我们还是需要自己定义的规则。
下面我们就来实现一下验证手机号码的规则。
1 php bin/console make:validator Mobile
上面的代码中,我们使用makebundle帮我们自动生成了App\Validator\Mobile和App\Validator\MobileValidator,如果你没有安装makebundle的话,执行以下命令:
1 composer require symfony/maker-bundle --dev
下面我们来实现App\Validator\Mobile和App\Validator\MobileValidator
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php namespace App \Validator ;use Symfony \Component \Validator \Constraint ;class Mobile extends Constraint { public $message = '电话号码错误' ; }
创建App\Rule\MobileValidator.php文件,定义我们的规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php namespace App \Validator ;use Symfony \Component \Validator \Constraint ;use Symfony \Component \Validator \ConstraintValidator ;class MobileValidator extends ConstraintValidator { public function validate ($value , Constraint $constraint ) { $rule = '^1(3|4|5|7|8)[0-9]\d{8}$^' ; $res = preg_match ($rule , $value ); if (empty ($res )) { $this ->context ->buildViolation ($constraint ->message) ->setParameter ('{{ value }}' , $value ) ->addViolation (); } } }
上面代码中,我们用了一个简单的正则表达式来验证电话号码,失败后丢入错误的上下文中。
使用定义规则 我们已经有了自己定义的规则,我们怎么来使用它呢?
更新我们的App\Validator\Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php namespace App \Validator ;use App \Validator \Mobile ;use Symfony \Component \Validator \Constraints as Assert ;class Example extends Base { public function setCollection ( ) { $this ->collection = new Assert\Collection ([ 'num' => new Assert\GreaterThan ([ 'value' => 10 , ]), 'mobile' => new Mobile () ]); } }
然后我们在控制器中使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php namespace App \Controller ;use Symfony \Component \HttpFoundation \Response ;use Symfony \Component \Routing \Annotation \Route ;use App \Validator \Example ;use App \Exception \Success ;class Word { public function index ( ) { $data = [ 'num' => 12 . 'mobile' => '12345678920' ]; (new Example ())->check ($data ); throw new Success (); } }
可以看到我们几乎没有改动控制器的代码,只是更新了验证的数据而已。
扩展 - 验证器与规则分层 到这里我们基本实现了自己的数据校验层,细心的同学可能发现了,我们的定义的规则和验证器都放在了App\Validator下,其实这也没什么,但是以后验证层多起来,找文件非常困难,更何况还有可能有重名文件存在。
我们希望验证器和我们定义的规则分开,规则都放在App\Rule下
上面我们通过makebundle创建了我们的规则。同理我们可以创建自己的make命令,不需要动其中的逻辑,只要简单的修改创建命令和创建规则的文件夹就行了。这里只是简单的实现,如果想深入的话,还请参阅源码
创建App\Make\Rule,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 <?php namespace App \Make ;use Symfony \Bundle \MakerBundle \ConsoleStyle ;use Symfony \Bundle \MakerBundle \DependencyBuilder ;use Symfony \Bundle \MakerBundle \Generator ;use Symfony \Bundle \MakerBundle \InputConfiguration ;use Symfony \Bundle \MakerBundle \Maker \AbstractMaker ;use Symfony \Bundle \MakerBundle \Str ;use Symfony \Component \Console \Command \Command ;use Symfony \Component \Console \Input \InputArgument ;use Symfony \Component \Console \Input \InputInterface ;use Symfony \Component \Validator \Validation ;class Rule extends AbstractMaker { public static function getCommandName ( ): string { return 'make:rule' ; } public function configureCommand (Command $command , InputConfiguration $inputConfig ) { $command ->setDescription ('Creates a new validator and constraint class' ) ->addArgument ('name' , InputArgument ::OPTIONAL , 'The name of the validator class (e.g. <fg=yellow>EnabledValidator</>)' ) ->setHelp (file_get_contents (__DIR__ .'/../../vendor/symfony/maker-bundle/src/Resources/help/MakeValidator.txt' )); } public function generate (InputInterface $input , ConsoleStyle $io , Generator $generator ) { $validatorClassNameDetails = $generator ->createClassNameDetails ( $input ->getArgument ('name' ), 'Rule\\' , 'Validator' ); $constraintFullClassName = Str ::removeSuffix ($validatorClassNameDetails ->getFullName (), 'Validator' ); $generator ->generateClass ( $validatorClassNameDetails ->getFullName (), 'validator/Validator.tpl.php' , [ 'constraint_class_name' => $constraintFullClassName , ] ); $generator ->generateClass ( $constraintFullClassName , 'validator/Constraint.tpl.php' , [] ); $generator ->writeChanges (); $this ->writeSuccessMessage ($io ); $io ->text ([ 'Next: Open your new constraint & validators and add your logic.' , 'Find the documentation at <fg=yellow>http://symfony.com/doc/current/validation/custom_constraint.html</>' , ]); } public function configureDependencies (DependencyBuilder $dependencies ) { $dependencies ->addClassDependency ( Validation ::class , 'validator' ); } }
这样,我们就可以通过自己的命令创建规则了。
1 php bin/console make:rule [rulename]
以后创建的规则都定义在App\Rule下,不用担心验证器和规则重名的问题。
由此,我们构建了自己的数据校验层,它十分简洁,分工明确,维护简单。
enjoy and happy coding!