Para criar um validador personalizado, devemos criar as seguintes classes:
- Constraint, responsável por configurar o comando
Esta classe vai expor a configuração do comando, seja por Annotation, YAML, etc. - Validator, que executa todo o processo de validação
Como situação de teste, vamos verificar se o usuário está preenchendo o campo com o valor esfiha.
Para validar um campo, comecemos com a configuração do contraint:
<?php
// src/Acme/FoodBundle/Validator/NotEsfiha.php
namespace Acme\FoodBundle\Validator;
// importa a classe base
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class NotEsfiha extends Constraint
{
// a única configuração exposta neste validador será a mensagem
// para expor outras configuração, é só definir mais propriedades públicas como esta.
public $message = 'acme_food.test.esfiha';
public function validatedBy()
{
return get_class($this).'Validator';
}
}
Esta configuração irá despachar o processo de validação para a classe retornada pelo método validatedBy
.
Neste caso, a classe chamada será a NotEsfihaValidator.
<?php
// src/Acme/FoodBundle/Validator/NotEsfihaValidator.php
namespace Acme\FoodBundle\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class NotEsfihaValidator extends ConstraintValidator
{
public function isValid($value, Constraint $constraint)
{
// o valor do campo em que este validador for aplicado será passado pelo
// primeiro argumento ($value, neste caso).
if ('esfiha' == strtolower($value)) {
// caso a validação falhe, devemos configurar a mensagem de retorno.
// para isso, pegaremos a mensagem no configurador (Constraint),
// passado no segundo parâmetro ($constraint)
$this->setMessage($constraint->message);
return false;
}
return true;
}
}
Pronto!
O validador já está pronto para ser usado em um campo.
<?php
use Acme\FoodBundle\Validator\NotEsfiha;
class Dish
{
/**
* note que eu estou omitindo o resto das configurações da
* entidade para simplificar o exemplo
*
* @NotEsfiha(message="Esfiha? Hummmm… hoje não.")
*/
public $name;
}
Para validar mais de um campo ao mesmo tempo (comparar campos, por exemplo), é necessário apenas o "alvo" do Constraint (configurador) e a forma como a validação é feita no Validator, que agora receberá o objeto completo, ao invés de apenas uma propriedade.
Alterando o alvo (escopo) do Constraint de property para class:
<?php
// src/Acme/FoodBundle/Validator/NotEsfiha.php
class NotEsfiha extends Constraint
{
// ...
public function getTargets()
{
return Constraint::CLASS_CONSTRAINT;
}
}
Ajustando a Annotation:
<?php
use Acme\FoodBundle\Validator\NotEsfiha;
/**
* A anotação sai do campo e vem para a classe.
*
* @NotEsfiha(message="Esfiha azul? WTF!?!?")
*/
class Dish
{
public $name;
// colocando um novo campo para que a gente tenha o que comparar
public $color;
}
E agora é só validar os campos:
<?php
// src/Acme/FoodBundle/Validator/NotEsfihaValidator.php
class NotEsfihaValidator extends ConstraintValidator
{
// note que agora o validador recebe o valor sobre o qual ele foi "anotado";
// o que antes era um valor (propriedade), agora é um objeto (class)
public function isValid($entity, Constraint $constraint)
{
if (
'esfiha' == strtolower($entity->name)
&& 'blue' == strtolower($entity->color)
) {
$this->setMessage($constraint->message);
return false;
}
return true;
}
}