如何创建一个自定义的验证约束

3.4 版本
维护中的版本

你可以继承基础的约束类(Constraint)来创建一个自定义的约束。例如,你要去创建一个简单的验证器,检查一个字符串是否是只包含字母和数字的字符。

创建约束类 

首先,你需要创建一个约束类并继承Constraint

1
2
3
4
5
6
7
8
9
10
11
12
// src/AppBundle/Validator/Constraints/ContainsAlphanumeric.php
namespace AppBundle\Validator\Constraints;
 
use Symfony\Component\Validator\Constraint;
 
/**
 * @Annotation
 */
class ContainsAlphanumeric extends Constraint
{
    public $message = 'The string "%string%" contains an illegal character: it can only contain letters or numbers.';
}

为了让这个新的约束,在类中能够以“注释”的方式来使用,你要加入@Annotation注释。你约束的配置选项,将以公有属性的方式,在约束类中呈现。

创建验证器本身 

正如您看到的一样,一个验证类是十分简洁的。他真正的验证是被其他“constraint validator(约束验证器)”类执行。这个约束验证器类被约束的validatedBy()方法所指定,他包含一些简单的默认逻辑:

1
2
3
4
5
// in the base Symfony\Component\Validator\Constraint class
public function validatedBy()
{
    return get_class($this).'Validator';
}

换句话说,如果你创建了一个自定义Constraint(例如 MyConstraint),当实际执行验证时,symfony将自动寻找MyConstraintValidator 类:

这个验证类也很简单,并且只有一个validate()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/AppBundle/Validator/Constraints/ContainsAlphanumericValidator.php
namespace AppBundle\Validator\Constraints;
 
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
 
class ContainsAlphanumericValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
        if (!preg_match('/^[a-zA-Z0-9]+$/', $value, $matches)) {
            $this->context->buildViolation($constraint->message)
                ->setParameter('%string%', $value)
                ->addViolation();
        }
    }
}

validate里,我们不需要去返回一个值。相反,如果他的内容是非法的,你需要添加非法操作到验证器的context属性,如果他的内容是合法的$value值将被认为是有效的。buildViolation 方法会把错误信息作为参数并返回一个ConstraintViolationBuilderInterface实例。 addViolation 方法最后会把不合法的内容添加到context。

使用这个新的验证器 

使用自定义验证器是很容易的,就和使用 Symfony 本身提供方式一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/AppBundle/Entity/AcmeEntity.php
use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Validator\Constraints as AcmeAssert;
 
class AcmeEntity
{
    // ...
 
    /**
     * @Assert\NotBlank
     * @AcmeAssert\ContainsAlphanumeric
     */
    protected $name;
 
    // ...
}
1
2
3
4
5
6
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\AcmeEntity:
    properties:
        name:
            - NotBlank: ~
            - AppBundle\Validator\Constraints\ContainsAlphanumeric: ~
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- src/AppBundle/Resources/config/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
 
    <class name="AppBundle\Entity\AcmeEntity">
        <property name="name">
            <constraint name="NotBlank" />
            <constraint name="AppBundle\Validator\Constraints\ContainsAlphanumeric" />
        </property>
    </class>
</constraint-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/AppBundle/Entity/AcmeEntity.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use AppBundle\Validator\Constraints\ContainsAlphanumeric;
 
class AcmeEntity
{
    public $name;
 
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('name', new NotBlank());
        $metadata->addPropertyConstraint('name', new ContainsAlphanumeric());
    }
}

如果在您定义的约束类中含有可选择的属性时,您应该在创建自定义约束类的时候就以公有的方式声明这些属性,那么这些可选择的属性就可以像使用 核心 Symfony 约束类中的属性一样使用它们。

带有依赖关系的约束验证器 

如果你的约束验证器有依赖关系,比如一个数据库连接,你要把他配置为一个依赖注入容器的服务。这个服务一定要包含validator.constraint_validator标签以便让验证系统知道它:

1
2
3
4
5
6
# app/config/services.yml
services:
    validator.contains_alphanumeric:
        class: AppBundle\Validator\Constraints\ContainsAlphanumericValidator
        tags:
            - { name: validator.constraint_validator }
1
2
3
4
5
<!-- app/config/services.xml -->
<service id="validator.contains_alphanumeric" class="AppBundle\Validator\Constraints\ContainsAlphanumericValidator">
    <argument type="service" id="doctrine.orm.default_entity_manager" />
    <tag name="validator.constraint_validator" />
</service>
1
2
3
4
// app/config/services.php
$container
    ->register('validator.contains_alphanumeric', 'AppBundle\Validator\Constraints\ContainsAlphanumericValidator')
    ->addTag('validator.constraint_validator');

现在,当symfony找到ContainsAlphanumericValidator验证器,就会从容器加载此服务。

在Symfony的早期版本中,标签(tag)必须有一个alias键(通常设置这个类的名称)。它仍然允许你的约束validateBy方法返回别名(而不是一个类名)。 如:

1
2
3
4
public function validatedBy()
{
    return 'alias_name';
}

类的约束验证 

除了验证一个类的属性,一个约束也能验证一个类作用域,是通过在他的Constraint类中提供一个目标

1
2
3
4
public function getTargets()
{
    return self::CLASS_CONSTRAINT;
}

验证类中的 validate() 方法把这个对象作为它的第一个参数:

1
2
3
4
5
6
7
8
9
10
11
class ProtocolClassValidator extends ConstraintValidator
{
    public function validate($protocol, Constraint $constraint)
    {
        if ($protocol->getFoo() != $protocol->getBar()) {
            $this->context->buildViolation($constraint->message)
                ->atPath('foo')
                ->addViolation();
        }
    }
}

注意,一个类约束验证器应用于类本身,而不是属性:

1
2
3
4
5
6
7
/**
 * @AcmeAssert\ContainsAlphanumeric
 */
class AcmeEntity
{
    // ...
}
1
2
3
4
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\AcmeEntity:
    constraints:
        - AppBundle\Validator\Constraints\ContainsAlphanumeric: ~
1
2
3
4
<!-- src/AppBundle/Resources/config/validation.xml -->
<class name="AppBundle\Entity\AcmeEntity">
    <constraint name="AppBundle\Validator\Constraints\ContainsAlphanumeric" />
</class>

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)