simker

Life is too short, just make it.


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 音乐

  • 搜索

使用symfony构建restful应用 - 验证器

发表于 2019-09-20 更新于 2021-01-14 分类于 PHP 阅读次数: Disqus:
本文字数: 7.9k 阅读时长 ≈ 7 分钟

目标:

上篇文章我们成功构建了异常处理,下面我们来构建自己的验证器

在官方文档: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
// src/controll/Word
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;

/**
* @Route("/hello")
*/
class Word
{
/**
* @Route("/word",methods={"GET"})
*/
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
* @package App\Validator
*/
class Base
{
/**
* @var Collection
*/
protected $collection;

/**
* @var ValidatorInterface
*/
protected static $validator;

/**
* Base constructor.
*/
public function __construct () {
if (empty(self::getValidator() instanceof ValidatorInterface)) {
self::setValidator();
}
static::setCollection();
}

/**
* @param array $input
* @throws \Exception
*/
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]);
}
// ...getter or setter
}

上面的代码中,我们构建了自己的验证器基类。
只要验证不通过,就会抛出参数异常的错误(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
// src/controll/Word
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Validator\Example;
use App\Exception\Success;

/**
* @Route("/hello")
*/
class Word
{
/**
* @Route("/word",methods={"GET"})
*/
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;

/**
* @Annotation
*/
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)
{
/* @var $constraint \App\Validator\Mobile */

$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
// src/controll/Word
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Validator\Example;
use App\Exception\Success;

/**
* @Route("/hello")
*/
class Word
{
/**
* @Route("/word",methods={"GET"})
*/
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
* @package App\Make
*/
class Rule extends AbstractMaker
{
/**
* @return string
*/
public static function getCommandName (): string
{
return 'make:rule';
}

/**
* @param Command $command
* @param InputConfiguration $inputConfig
*/
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'));
}

/**
* @param InputInterface $input
* @param ConsoleStyle $io
* @param Generator $generator
* @throws \Exception
*/
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</>',
]);
}

/**
* @param DependencyBuilder $dependencies
*/
public function configureDependencies (DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(
Validation::class,
'validator'
);
}
}

这样,我们就可以通过自己的命令创建规则了。

1
php bin/console make:rule [rulename]

以后创建的规则都定义在App\Rule下,不用担心验证器和规则重名的问题。

由此,我们构建了自己的数据校验层,它十分简洁,分工明确,维护简单。

enjoy and happy coding!

Cai xian 微信支付

微信支付

Cai xian 支付宝

支付宝

# restful # symfony
使用symfony构建restful应用 - restful异常处理
git重写你的提交日志
  • 文章目录
  • 站点概览
Cai xian

Cai xian

A super nice guy!
24 日志
12 分类
15 标签
  1. 1. 目标:
  2. 2. 验证器的简单使用
  3. 3. 自定义验证器
  4. 4. 自定义验证器的使用
  5. 5. 定义规则
  6. 6. 使用定义规则
  7. 7. 扩展 - 验证器与规则分层
© 2019 – 2021 Cai xian | 70k | 1:04
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.3.0
|