Validação¶
Validação é uma tarefa muito comum em aplicações web. Dado inserido em formulário precisa ser validado. Dado também precisa ser revalidado antes de ser escrito num banco de dados ou passado a um serviço web.
Symfony2 vem acompanhado com um componente Validator que torna essa tarefa fácil e transparente. Esse componente é baseado na especificação `JSR303 Bean Validation`_. O quê ? Uma especificação Java no PHP? Você ouviu corretamente, mas não é tão ruim quanto parece. Vamos olhar como isso pode ser usado no PHP.
As bases da validação¶
A melhor forma de entender validação é vê-la em ação. Para começar, suponha que você criou um bom e velho objeto PHP que você precisa usar em algum lugar da sua aplicação:
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
class Author
{
public $name;
}
Até agora, essa é somente uma classe comum que serve para alguns propósitos dentro de sua aplicação. O objetivo da validação é avisar você se um dado de um objeto é válido ou não. Para esse trabalho, você irá configura uma lista de regras (chamada constraints) em que o objeto deve seguir em ordem para ser validado. Essas regras podem ser especificadas por um número de diferentes formatos (YAML, XML, annotations, ou PHP).
Por exemplo, para garantir que a propriedade $name
não é vazia, adicione o
seguinte:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: name: - NotBlank: ~
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() */ public $name; }
- XML
<!-- src/Acme/BlogBundle/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="Acme\BlogBundle\Entity\Author"> <property name="name"> <constraint name="NotBlank" /> </property> </class> </constraint-mapping>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $name; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new NotBlank()); } }
Dica
Propriedades protected e private podem também ser validadas, bem como os métodos
“getter” (veja validator-constraint-targets
Usando o serviço validator
¶
Próximo passo, para realmente validar um objeto``Author``, use o método validate
no serviço validator
(classe Validator
).
A tarefa do validator
é fácil: ler as restrições (i.e. regras)
de uma classe e verificar se o dado no objeto satisfaz ou não aquelas restrições.
Se a validação falhar, retorna um array de erros. Observe esse
simples exemplo de dentro do controller:
use Symfony\Component\HttpFoundation\Response;
use Acme\BlogBundle\Entity\Author;
// ...
public function indexAction()
{
$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
if (count($errors) > 0) {
return new Response(print_r($errors, true));
} else {
return new Response('The author is valid! Yes!');
}
}
Se a propriedade $name
é vazia, você verá a seguinte mensagem de
erro:
Acme\BlogBundle\Author.name:
This value should not be blank
Se você inserir um valor na propriedade name
, aparecerá a feliz mensagem
de sucesso.
Dica
A maior parte do tempo, você não irá interagir diretamente com o serviço
validator
ou precisará se preocupar sobre imprimir os erros. A maior parte do tempo,
você irá usar a validação indiretamente quando lidar com dados enviados do formulário.
Para mais informações, veja: ref:book-validation-forms.
Você também poderia passar o conjunto de erros em um template.
if (count($errors) > 0) {
return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
'errors' => $errors,
));
} else {
// ...
}
Dentro do template, você pode gerar a lista de erros exatamente necessária:
- Twig
{# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #} <h3>The author has the following errors</h3> <ul> {% for error in errors %} <li>{{ error.message }}</li> {% endfor %} </ul>
- PHP
<!-- src/Acme/BlogBundle/Resources/views/Author/validate.html.php --> <h3>The author has the following errors</h3> <ul> <?php foreach ($errors as $error): ?> <li><?php echo $error->getMessage() ?></li> <?php endforeach; ?> </ul>
Nota
Cada erro de validação (chamado de “constraint violation”), é representado por
um objeto ConstraintViolation
.
Validação e formulários¶
O serviço validator
pode ser usado a qualquer momento para validar qualquer objeto.
Na realidade, entretanto, você irá trabalhar frequentemente com o validator
indiretamente
enquanto trabalhar com formulário. A biblioteca Symfony’s form usa o serviço validator
internamente para validar o objeto oculto após os valores terem sido enviados
e fixados. As violações de restrição no objeto são convertidas em objetos FieldError
que podem ser facilmente exibidos com seu formulário. O tipico fluxo de envio do formulário
parece o seguinte dentro do controller:
use Acme\BlogBundle\Entity\Author;
use Acme\BlogBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;
// ...
public function updateAction(Request $request)
{
$author = new Acme\BlogBundle\Entity\Author();
$form = $this->createForm(new AuthorType(), $author);
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
// the validation passed, do something with the $author object
$this->redirect($this->generateUrl('...'));
}
}
return $this->render('BlogBundle:Author:form.html.twig', array(
'form' => $form->createView(),
));
}
Nota
Esse exemplo usa uma classe de formulários AuthorType
, que não é mostrada aqui.
Para mais informações, veja: doc:Forms</book/forms> chapter.
Configuração¶
O validador do Symfony2 é abilitado por padrão, mas você deve abilitar explicitamente anotações se você usar o método de anotação para especificar suas restrições:
- YAML
# app/config/config.yml framework: validation: { enable_annotations: true }
- XML
<!-- app/config/config.xml --> <framework:config> <framework:validation enable_annotations="true" /> </framework:config>
- PHP
// app/config/config.php $container->loadFromExtension('framework', array('validation' => array( 'enable_annotations' => true, )));
Restrições¶
O validator
é designado para validar objtos perante restrições (i.e.
regras). Em ordem para validar um objeto, simplesmente mapeie uma ou mais restrições
para aquela classe e então passe para o serviço validator
.
Por trás dos bastidores, uma restrição é simplesmente um objeto PHP que faz uma sentença afirmativa. Na vida real, uma restrição poderia ser: “O bolo não deve queimar”. No Symfony2, restrições são similares: elas são afirmações que uma condição é verdadeira. Dado um valor, a restriçao irá indicar a você se o valor adere ou não às regras da restrição.
Restições Suportadas¶
Symfony2 engloba um grande número de restrições mais frequentemente usadas:
Você também pode criar sua própria restrição personalizada. Esse tópico é coberto no artigo do cookbook “Como criar uma Constraint de Validação Personalizada” .
Configuração de restrições¶
Algumas restrições, como NotBlank,
são simples como as outras, como a restrição Choice
, tem várias opções de configuração disponíveis. Suponha que a classe``Author``
tenha outra propriedade, gender
que possa ser configurado como
“male” ou “female”:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: { choices: [male, female], message: Choose a valid gender. }
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice( * choices = { "male", "female" }, * message = "Choose a valid gender." * ) */ public $gender; }
- XML
<!-- src/Acme/BlogBundle/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="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <option name="choices"> <value>male</value> <value>female</value> </option> <option name="message">Choose a valid gender.</option> </constraint> </property> </class> </constraint-mapping>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Choice(array( 'choices' => array('male', 'female'), 'message' => 'Choose a valid gender.', ))); } }
A opção de uma restrição pode sempre ser passada como um array. Algumas restrições,
entretanto, também permitem a você passar o valor de uma opção “default” no lugar
do array. No cado da restrição Choice
, as opções choices
podem ser espeficadas dessa forma.
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: [male, female]
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice({"male", "female"}) */ protected $gender; }
- XML
<!-- src/Acme/BlogBundle/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="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <value>male</value> <value>female</value> </constraint> </property> </class> </constraint-mapping>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Choice; class Author { protected $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Choice(array('male', 'female'))); } }
Isso significa simplesmente fazer a configuração da opção mais comum de uma restrição mais curta e rápida.
Se você está incerto de como especificar uma opção, ou verifique a documentação da API para a restrição ou faça de forma segura sempre passando um array de opções (o primeiro método mostrado acima).
Escopos da restrição¶
Restrições podem ser aplicadas a uma propriedade de classe (e.g. name
) ou
um método getter público (e.g. getFullName
). O primeiro é mais comum e fácil
de usar, mas o segundo permite você especificar regras de validação mais complexas.
Propriedades¶
Validar as propriedades de uma classe é a técnica de validação mais básica.Symfony2
permite a você validar propriedades private, protected ou public. A próxima listagem
mostra a você como configurar a propriedade $firstName
da classe Author
para ter ao menos 3 caracteres.
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: firstName: - NotBlank: ~ - MinLength: 3
- Annotations
// Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() * @Assert\MinLength(3) */ private $firstName; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <property name="firstName"> <constraint name="NotBlank" /> <constraint name="MinLength">3</constraint> </property> </class>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class Author { private $firstName; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new NotBlank()); $metadata->addPropertyConstraint('firstName', new MinLength(3)); } }
Getters¶
Restrições podem também ser aplicadas no método de retorno de um valor.Symfony2 permite a você adicionar uma restrição para qualquer método public cujo nome comec com “get” ou “is”. Nesse guia, ambos os tipos de métodos são referidos como “getters”.
O benefício dessa técnica é que permite a você validar seu objeto
dinamicamente. Por exemplo, suponha que você queira ter certeza que um campo de senha
não coincida com o primeiro nome do usuário (por motivos de segurança). Você pode
fazer isso criando um método isPasswordLegal
, e então afirmando que
esse método deva retornar ‘’true’‘:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: getters: passwordLegal: - "True": { message: "The password cannot match your first name" }
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\True(message = "The password cannot match your first name") */ public function isPasswordLegal() { // return true or false } }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <getter property="passwordLegal"> <constraint name="True"> <option name="message">The password cannot match your first name</option> </constraint> </getter> </class>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\True; class Author { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint('passwordLegal', new True(array( 'message' => 'The password cannot match your first name', ))); } }
Agora, crie o método isPasswordLegal()
, e inclua a lógica que você precisa:
public function isPasswordLegal()
{
return ($this->firstName != $this->password);
}
Nota
Com uma visão apurada, você irá perceber que o prefixo do getter (“get” ou “is”) é omitido no mapeamento. Isso permite você mover a restrição para uma propriedade com o mesmo nome mais tarde (ou vice-versa) sem mudar sua lógica de validação.
Classes¶
Algumas restrições aplicam para a classe inteira ser validada. Por exemplo, a restrição Callback é uma restrição genérica que é aplicada para a própria classe. Quando a classe é validada, métodos especificados por aquela restrição são simplesmente executadas então cada um pode prover uma validação mais personalizada.
Grupos de validação¶
Até agora, você foi capaz de adicionar restrições a uma classe e perguntar se aquela classe passa ou não por todas as restrições definidas. Em alguns casos, entretanto, você precisará validar um objeto a somente algumas das restrições naqula classe. Para fazer isso, você pode organizar cada restrição dentro de um ou mais “grupos de validação”, e então aplicar validação a apenas um grupo de restrições.
Por exemplo, suponha que você tenha uma classe User
, que é usada tanto quando um
usuário registra e quando um usuário atualiza sua informações de contato posteriormente:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\User: properties: email: - Email: { groups: [registration] } password: - NotBlank: { groups: [registration] } - MinLength: { limit: 7, groups: [registration] } city: - MinLength: 2
- Annotations
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; class User implements UserInterface { /** * @Assert\Email(groups={"registration"}) */ private $email; /** * @Assert\NotBlank(groups={"registration"}) * @Assert\MinLength(limit=7, groups={"registration"}) */ private $password; /** * @Assert\MinLength(2) */ private $city; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\User"> <property name="email"> <constraint name="Email"> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="password"> <constraint name="NotBlank"> <option name="groups"> <value>registration</value> </option> </constraint> <constraint name="MinLength"> <option name="limit">7</option> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="city"> <constraint name="MinLength">7</constraint> </property> </class>
- PHP
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class User { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('email', new Email(array( 'groups' => array('registration') ))); $metadata->addPropertyConstraint('password', new NotBlank(array( 'groups' => array('registration') ))); $metadata->addPropertyConstraint('password', new MinLength(array( 'limit' => 7, 'groups' => array('registration') ))); $metadata->addPropertyConstraint('city', new MinLength(3)); } }
Com essa configuração, existem dois grupos de validação:
Default
- contém as restrições não atribuidas a qualquer outro grupo;registration
- Contém as restrições somente nos camposemail
epassword
.
Para avisar o validador a usar um grupo específico, passe um ou mais nomes de trupos como um segundo argumento para o método``validate()``
$errors = $validator->validate($author, array(‘registration’));
Claro, você irá frequentemente trabalhar com validação indiretamnte por meio da biblioteca do formulário. Para informações em como usar grupos de validação dentro de formulários, veja Grupos de Validação.
Validando Valores e Arrays¶
Até agora, você viu como pode validar objetos inteiros. Mas às vezes, você somente quer validar um valor simples - como verificar se uma string é um endereço de e-mail válido. Isso é realmente muito fácil de fazer. De dentro do controller, parece com isso:
// add this to the top of your class use SymfonyComponentValidatorConstraintsEmail;
public function addEmailAction($email) {
$emailConstraint = new Email(); // all constraint “options” can be set this way $emailConstraint->message = ‘Invalid email address’;
// use the validator to validate the value $errorList = $this->get(‘validator’)->validateValue($email, $emailConstraint);
- if (count($errorList) == 0) {
- // this IS a valid email address, do something
- } else {
// this is not a valid email address $errorMessage = $errorList[0]->getMessage()
// do somethign with the error
}
// ...
}
Ao chamar validateValue
no validador, você pode passar um valor bruto e
o objeto de restrição que você com o qual você quer validar aquele valor. Uma lista
completa de restrições disponíveis - bem como o nome inteiro da classe para cada
restrição - está disponível em constraints reference
section .
O método validateValule
retorna um objeto ConstraintViolationList
, que age como um array de erros. Cada erro na coleção é um objeto :
class:Symfony\Component\Validator\ConstraintViolation ,
que contém a mensagem de erro no método getMessage dele.
Considerações Finais¶
O Symfony2 validator
é uma ferramenta poderosa que pode ser multiplicada para
garantir que o dado de qualquer objeto seja “válido”. O poder por trás da validação
rside em “restrições”, que seão regras que você pode aplicar a propriedades ou métodos
getter de seus objetos. E enquanto você irá usar mais frequentemente usar a validação
do framework indiretamente quando usar formulários, lembre que isso pode ser usado
em qualquer lugar para validar qualquer objeto.