Documentação do Symfony - versão 3.1
Renderizada do repositório symfony-docs-pt-BR no Github
Lidar com formulários HTML é uma das mais comuns - e desafiadoras - tarefas para um desenvolvedor web. O Symfony2 integra um componente de formulário que torna fácil a tarefa de lidar com formulários. Neste capítulo, você vai construir um formulário complexo a partir do zero, aprendendo as características mais importantes da biblioteca de formulários ao longo do caminho.
Note
O componente de formulário do Symfony é uma biblioteca independente que pode ser utilizada fora de projetos Symfony2. Para mais informações, consulte o `Componente de Formulário do Symfony2`_ no Github.
Suponha que você está construindo uma aplicação simples de lista de tarefas que precisará
exibir “tarefas”. Devido aos seus usuários terem que editar e criar tarefas, você precisará
construir um formulário. Mas, antes de começar, primeiro vamos focar na classe genérica Task
que representa e armazena os dados de uma única tarefa:
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 | // src/Acme/TaskBundle/Entity/Task.php
namespace Acme\TaskBundle\Entity;
class Task
{
protected $task;
protected $dueDate;
public function getTask()
{
return $this->task;
}
public function setTask($task)
{
$this->task = $task;
}
public function getDueDate()
{
return $this->dueDate;
}
public function setDueDate(\DateTime $dueDate = null)
{
$this->dueDate = $dueDate;
}
}
|
Note
Se você está codificando junto com este exemplo, crie o AcmeTaskBundle
primeiro, executando o seguinte comando (e aceite todas as opções
padrão):
1 | php app/console generate:bundle --namespace=Acme/TaskBundle
|
Essa classe é um “antigo objeto PHP simples”, porque, até agora, não tem nada
a ver com Symfony ou qualquer outra biblioteca. É simplesmente um objeto PHP normal
que, diretamente resolve um problema no interior da sua aplicação (ou seja, a necessidade de
representar uma tarefa na sua aplicação). Claro, até o final deste capítulo,
você será capaz de enviar dados para uma instância Task
(através de um formulário HTML), validar
os seus dados, e persisti-los para o banco de dados.
Agora que você já criou a classe Task
, o próximo passo é criar e renderizar
o formulário HTML real. No Symfony2, isto é feito através da construção de um objeto
de formulário e, em seguida, renderizando em um template. Por ora, tudo isso pode
ser feito dentro de um controlador:
// src/Acme/TaskBundle/Controller/DefaultController.php
namespace Acme\TaskBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\TaskBundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
public function newAction(Request $request)
{
// create a task and give it some dummy data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Tip
Este exemplo mostra como construir o seu formulário diretamente no controlador. Mais tarde, na seção “Criando classes de formulário”, você aprenderá como construir o seu formulário em uma classe independente, que é o recomendado pois torna o seu formulário reutilizável.
A criação de um formulário requer relativamente pouco código porque os objetos de formulário do Symfony2 são construídos com um “construtor de formulários”. A finalidade do construtor de formulários é permitir que você escreva “receitas” simples de formulários, e ele fazer todo o trabalho pesado, de, realmente, construir o formulário.
Neste exemplo, você acrescentou dois campos ao seu formulário - task
e dueDate
-
que correspondem as propriedades task
e dueDate
da classe Task
.
Você também atribuiu a cada um deles um “type” (exemplo: text
, date
), que, entre
outras coisas, determina qual(ais) tag(s) HTML de formulário serão renderizadas para esse campo.
O Symfony2 vem com muitos tipos embutidos, que serão discutidos em breve (veja Tipos de campos integrados (Built-in)).
Agora que o formulário foi criado, o próximo passo é renderizá-lo. Isto é
feito passando um objeto “view” especial para o seu template (note o
$form->createView()
no controlador acima) e usando um conjunto de funções helper
para o formulário:
1 2 3 4 5 6 7 | {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
|
1 2 3 4 5 6 7 | <!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?> >
<?php echo $view['form']->widget($form) ?>
<input type="submit" />
</form>
|
Note
Este exemplo assume que você criou uma rota chamada task_new
que aponta para o controlador AcmeTaskBundle:Default:new
o qual
foi criado anteriormente.
É isso! Ao imprimir o form_widget(form)
, cada campo do formulário é
renderizado, juntamente com uma label e uma mensagem de erro (se houver). Fácil
assim, embora não muito flexível (ainda). Normalmente, você desejará renderizar cada
campo do formulário individualmente, pois poderá controlar como será a aparência do formulário.
Você aprenderá como fazer isso na seção “Renderizando um formulário em um Template”.
Antes de prosseguirmos, observe como o campo input task
renderizado tem o valor
da propriedade task
do objeto $task
(Ex. “Write a blog post”).
Este é o primeiro trabalho de um formulário: pegar os dados de um objeto e traduzi-lo
em um formato que seja adequado para ser renderizado em um formulário HTML.
Tip
O sistema de formulários é inteligente o suficiente para acessar o valor da propriedade protegida
task
através dos métodos getTask()
e setTask()
na classe Task
. A menos que a
propriedade seja pública, ela deve ter um método “getter” e “setter” para que o componente de
formulário possa obter e definir os dados na propriedade. Para uma propriedade Boolean, você pode
usar um método “isser” (por exemplo, isPublished()
) em vez de um getter
(Ex. getPublished()
).
O segundo trabalho de um formulário é traduzir os dados enviados pelo usuário de volta as propriedades de um objeto. Para que isso aconteça, os dados enviados pelo usuário devem ser vinculados (bound) ao formulário. Adicione as seguintes funcionalidades no seu controlador:
// ...
public function newAction(Request $request)
{
// just setup a fresh $task object (remove the dummy data)
$task = new Task();
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
// perform some action, such as saving the task to the database
return $this->redirect($this->generateUrl('task_success'));
}
}
// ...
}
New in version 2.1: O método bind
tornou-se mais flexível no Symfony 2.1. Ele aceita agora os
dados brutos do cliente (como antes) ou um objeto Request do Symfony. Ele é
preferido ao invés do método obsoleto bindRequest
.
Agora, quando enviar o formulário, o controlador vincula (bind) ao formulário os dados enviados,
que traduz os dados de volta as propriedades task
e dueDate
do objeto $task
. Isso tudo acontece através do método bind()
.
Note
Assim que o bind()
é chamado, os dados enviados são transferidos
imediatamente para o objeto implícito. Isso acontece independentemente dos
dados implícitos serem realmente válidos.
Este controlador segue um padrão comum para a manipulação de formulários, e possui três caminhos possíveis:
GET
e o formulário é simplesmente criado e renderizado;POST
) mas os dados não são válidos
(a validação será discutida na próxima seção), o formulário é vinculado (bound) e
então processado, desta vez exibindo todos os erros de validação;$task
(por exemplo, persisti-lo para o banco de dados) antes de redirecionar o usuário
para outra página (por exemplo, uma página de “obrigado” ou “sucesso”).Note
Redirecionar o usuário após o envio bem sucedido do formulário impede que ele, ao clicar em “atualizar”, reenvie os dados do formulário.
Na seção anterior, você aprendeu como um formulário pode ser enviado com dados
válidos ou inválidos. No Symfony2, a validação é aplicada ao objeto implícito
(Ex., Task
). Em outras palavras, a questão não é se o “formulário” é
válido, mas se o objeto $task
é válido após a aplicação dos dados enviados pelo
formulário. A chamada $form->isValid()
é um atalho
que pergunta ao objeto $task
se ele possui ou não dados válidos.
A validação é feita adicionando um conjunto de regras (chamadas constraints) à uma classe. Para
ver isso em ação, adicione constraints de validação para que o campo task
não deve
ser vazio e o campo dueDate
não deve ser vazio e deve ser um objeto DateTime
válido.
1 2 3 4 5 6 7 8 | # Acme/TaskBundle/Resources/config/validation.yml
Acme\TaskBundle\Entity\Task:
properties:
task:
- NotBlank: ~
dueDate:
- NotBlank: ~
- Type: \DateTime
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Acme/TaskBundle/Entity/Task.php
use Symfony\Component\Validator\Constraints as Assert;
class Task
{
/**
* @Assert\NotBlank()
*/
public $task;
/**
* @Assert\NotBlank()
* @Assert\Type("\DateTime")
*/
protected $dueDate;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- Acme/TaskBundle/Resources/config/validation.xml -->
<class name="Acme\TaskBundle\Entity\Task">
<property name="task">
<constraint name="NotBlank" />
</property>
<property name="dueDate">
<constraint name="NotBlank" />
<constraint name="Type">
<value>\DateTime</value>
</constraint>
</property>
</class>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Acme/TaskBundle/Entity/Task.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
class Task
{
// ...
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('task', new NotBlank());
$metadata->addPropertyConstraint('dueDate', new NotBlank());
$metadata->addPropertyConstraint('dueDate', new Type('\DateTime'));
}
}
|
É isso! Se você reenviar o formulário com dados inválidos, verá os erros correspondentes exibidos com o formulário.
A validação é um recurso muito poderoso do Symfony2 e tem seu próprio capítulo dedicado.
Tip
Se você não estiver usando grupos de validação, então, você pode pular esta seção.
Se o seu objeto aproveita a grupos de validação, você precisa especificar qual(ais) grupo(s) de validação seu formulário deve usar:
$form = $this->createFormBuilder($users, array(
'validation_groups' => array('registration'),
))->add(...)
;
Se você está criando classes de formulário (uma
boa prática), então você precisa adicionar o seguinte ao método
setDefaultOptions()
:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('registration')
));
}
Em ambos os casos, apenas o grupo de validação registration
será
usado para validar o objeto implícito.
New in version 2.1: A capacidade de especificar um callback ou Closure no validation_groups
é novo na versão 2.1
Se você precisar de alguma lógica avançada para determinar os grupos de validação (por exemplo,
com base nos dados submetidos), você pode definir a opção validation_groups
para um array callback
ou uma Closure
:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('Acme\\AcmeBundle\\Entity\\Client', 'determineValidationGroups'),
));
}
Isso irá chamar o método estático determineValidationGroups()
na
classe Client
após o formulário ser vinculado (bound), mas antes da validação ser executada.
O objeto do formulário é passado como um argumento para esse método (veja o exemplo seguinte).
Você também pode definir toda a lógica inline usando uma Closure:
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$data = $form->getData();
if (Entity\Client::TYPE_PERSON == $data->getType()) {
return array('person')
} else {
return array('company');
}
},
));
}
O Symfony vem, por padrão, com um grande grupo de tipos de campos que cobrem todos os os campos comuns de formulário e tipos de dados que você vai encontrar:
Você também pode criar os seus próprios tipos de campo personalizados. Este tópico é abordado no artigo “/cookbook/form/create_custom_field_type” do cookbook.
Cada tipo de campo possui um número de opções que podem ser usadas para configurá-lo.
Por exemplo, o campo dueDate
é atualmente processado como 3 select
boxes. No entanto, o campo date pode ser
configurado para ser renderizado como uma caixa de texto simples (onde o usuário deve
digitar a data como uma string na caixa):
->add('dueDate', 'date', array('widget' => 'single_text'))
Cada tipo de campo tem um número de opções diferentes que podem ser passadas à ele. Muitas delas são específicas para o tipo de campo e os detalhes podem ser encontrados na documentação de cada tipo.
Agora que você adicionou metadados de validação na classe Task
, o Symfony
já sabe um pouco sobre os seus campos. Se você permitir, o Symfony pode “adivinhar”
o tipo do seu campo e configurá-lo para você. Neste exemplo, o Symfony pode
adivinhar a partir das regras de validação que o campo task
é um campo texto
normal e o campo dueDate
é um campo data
:
public function newAction()
{
$task = new Task();
$form = $this->createFormBuilder($task)
->add('task')
->add('dueDate', null, array('widget' => 'single_text'))
->getForm();
}
A “adivinhação” é ativada quando você omitir o segundo argumento do método add()
(ou se você passar null
para ele). Se você passar um array de opções como o
terceiro argumento (feito para o dueDate
acima), estas opções são aplicadas ao
campo adivinhado.
Caution
Se o formulário usa um grupo de validação específico, o adivinhador do tipo de campo ainda vai considerar todas as constraints de validação quando estiver adivinhando os seus tipos de campos (incluindo as constraints que não fazem parte dos grupos de validação sendo utilizados).
Além de adivinhar o “tipo” para um campo, o Symfony também pode tentar adivinhar os valores corretos de uma série de opções do campo.
Tip
Quando essas opções são definidas, o campo será renderizado com atributos HTML especiais
que fornecem para a validação HTML5 ao lado do cliente. Entretanto, ele
não gera as constraints equivalentes ao lado do servidor (Ex. Assert\MaxLength
).
E, embora você precisará adicionar manualmente a validação ao lado do servidor, essas
opções de tipo de campo podem, então, ser adivinhadas a partir dessa informação.
required
: A opção required
pode ser adivinhada com base nas regras de
validação (ou seja, o campo é NotBlank
ou NotNull
) ou metadados do Doctrine
(ou seja, é o campo é nullable
). Isto é muito útil, pois a sua validação ao lado
do cliente irá corresponder automaticamente as suas regras de validação.min_length
: Se o campo é uma espécie de campo de texto, então, a opção min_length
pode ser adivinhada a partir das constraints de validação (se o MinLength
ou Min
é usado) ou a partir dos metadados do Doctrine (através do tamanho do campo).max_length
: Semelhante ao min_length
, o tamanho máximo também pode
ser adivinhado.Note
Estas opções de campo são adivinhadas apenas se você estiver usando o Symfony para adivinhar
o tipo de campo (ou seja, omitir ou passar null
como o segundo argumento para o add()
).
Se você desejar modificar um dos valores adivinhados, você pode sobrescrevê-lo passando a opção no array de opções do campo:
->add('task', null, array('min_length' => 4))
Até agora, você viu como um formulário inteiro pode ser renderizado com apenas uma linha de código. Claro, você geralmente precisará de muito mais flexibilidade quando estiver renderizando:
1 2 3 4 5 6 7 8 9 10 11 12 | {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_errors(form) }}
{{ form_row(form.task) }}
{{ form_row(form.dueDate) }}
{{ form_rest(form) }}
<input type="submit" />
</form>
|
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->
<form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?>>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->row($form['task']) ?>
<?php echo $view['form']->row($form['dueDate']) ?>
<?php echo $view['form']->rest($form) ?>
<input type="submit" />
</form>
|
Vamos dar uma olhada em cada parte:
form_enctype(form)
- Se pelo menos um campo for um campo para upload de arquivo, ele irá
renderizar o enctype="multipart/form-data"
obrigatório;form_errors(form)
- Renderiza quaisquer erros globais para todo o formulário
(erros específicos de campos são exibidos ao lado de cada campo);form_row(form.dueDate)
- Renderiza a label, qualquer erro, e o widget
HTML do formulário para o campo informado (Ex. dueDate
), por padrão, um
elemento div
;form_rest(form)
- Renderiza quaisquer campos que ainda não tenham sido renderizados.
Geralmente é uma boa idéia fazer uma chamada deste helper na parte inferior de
cada formulário (no caso de você ter esquecido algum campo ou não quer se preocupar
em renderizar manualmente os campos ocultos). Este helper também é útil para aproveitar
a Proteção CSRF automática.A maioria do trabalho é feito pelo helper form_row
, que renderiza
a label, os erros e widgets HTML do formulário para cada campo dentro de uma tag div
por padrão. Na seção Tematizando os formulários, você aprenderá como a saída do form_row
pode ser personalizada em muitos níveis diferentes.
Tip
Você pode acessar os dados atuais do seu formulário via form.vars.value
:
1 | {{ form.vars.value.task }}
|
1 | <?php echo $view['form']->get('value')->getTask() ?>
|
O helper form_row
é ótimo porque você pode renderizar rapidamente cada
campo de seu formulário (e também é possível personalizar a marcação utilizada para a “linha”
). Mas, como a vida nem sempre é tão simples, você também pode renderizar cada campo
inteiramente à mão. O produto final do que segue é o mesmo de quando você
usou o helper form_row
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | {{ form_errors(form) }}
<div>
{{ form_label(form.task) }}
{{ form_errors(form.task) }}
{{ form_widget(form.task) }}
</div>
<div>
{{ form_label(form.dueDate) }}
{{ form_errors(form.dueDate) }}
{{ form_widget(form.dueDate) }}
</div>
{{ form_rest(form) }}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php echo $view['form']->errors($form) ?>
<div>
<?php echo $view['form']->label($form['task']) ?>
<?php echo $view['form']->errors($form['task']) ?>
<?php echo $view['form']->widget($form['task']) ?>
</div>
<div>
<?php echo $view['form']->label($form['dueDate']) ?>
<?php echo $view['form']->errors($form['dueDate']) ?>
<?php echo $view['form']->widget($form['dueDate']) ?>
</div>
<?php echo $view['form']->rest($form) ?>
|
Se a label auto-gerada para um campo não estiver correta, você pode especificá-la explicitamente:
1 | {{ form_label(form.task, 'Task Description') }}
|
1 | <?php echo $view['form']->label($form['task'], 'Task Description') ?>
|
Finalmente, alguns tipos de campos tem opções de renderização adicionais que podem ser passadas
para o widget. Estas opções estão documentadas com cada tipo, mas uma opção em comum
é o attr
, que permite modificar atributos no elemento do formulário.
O seguinte código adiciona a classe task_field
para o campo texto de entrada
renderizado:
1 | {{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}
|
1 2 3 | <?php echo $view['form']->widget($form['task'], array(
'attr' => array('class' => 'task_field'),
)) ?>
|
Se você está usando o Twig, uma referência completa das funções de renderização do formulário está disponível no manual de referência. Leia ele para saber tudo sobre os helpers disponíveis e as opções que podem ser usadas com cada um.
Como você viu, um formulário pode ser criado e usado diretamente em um controlador. No entanto, uma prática melhor é construir o formulário separadamente, em uma classe PHP independente, que poderá, então, ser reutilizada em qualquer lugar na sua aplicação. Crie uma nova classe que vai abrigar a lógica da construção do formulário de tarefas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // src/Acme/TaskBundle/Form/Type/TaskType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task');
$builder->add('dueDate', null, array('widget' => 'single_text'));
}
public function getName()
{
return 'task';
}
}
|
Esta nova classe contém todas as orientações necessárias para criar o formulário de tarefas
(Note que o método getName()
deve retornar um identificador exclusivo para esse
“tipo” do formulário). Ele pode ser usado para construir rapidamente um objeto de formulário no controlador:
1 2 3 4 5 6 7 8 9 10 11 12 | // src/Acme/TaskBundle/Controller/DefaultController.php
// add this new use statement at the top of the class
use Acme\TaskBundle\Form\Type\TaskType;
public function newAction()
{
$task = // ...
$form = $this->createForm(new TaskType(), $task);
// ...
}
|
Colocando a lógica do formulário em sua própria classe significa que o formulário pode ser facilmente reutilizado em outros lugares no seu projeto. Esta é a melhor forma de criar formulários, mas, a decisão final depende de você.
Tip
Ao mapear formulários para objetos, todos os campos são mapeados. Qualquer campo do formulário que não existe no objeto mapeado irá fazer com que uma exceção seja gerada.
Nos casos em que você precisa de campos extras na formulário (por exemplo: um
checkbox “você concorda com os termos”) que não será mapeado para o objeto implícito,
você precisa definir a opção property_path como false
:
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task');
$builder->add('dueDate', null, array('property_path' => false));
}
Além disso, se houver quaiquer campos do formulário que não estão incluídos nos
dados submetidos, esses campos serão definidos explicitamente como null
.
Os dados do campo podem ser acessados em um controlador com:
$form->get('dueDate')->getData();
O objetivo de um formulário é traduzir os dados de um objeto (Ex. Task
) para um
formulário HTML e, em seguida, traduzir os dados enviados pelo usuário de volta ao objeto original.
Como tal, o tópico da persistência do objeto Task
no banco de dados é totalmente
não relacionado ao tópico de formulários. Mas, se você configurou a classe Task
para ser persistida através do Doctrine (ou seja, você adicionou
metadados de mapeamento à ele), então, a persistência
após a submissão do formulário pode ser feita quando o formulário é válido:
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
return $this->redirect($this->generateUrl('task_success'));
}
Se, por algum motivo, você não tem acesso ao seu objeto $task
original,
você pode buscá-lo a partir do formulário:
$task = $form->getData();
Para mais informações, consulte o capítulo Doctrine ORM.
A chave para entender é que, quando o formulário é vinculado (bound), os dados submetidos são transferidos imediatamente para o objeto implícito. Se você quiser persistir esses dados, basta persistir o objeto em si (que já contém os dados submetidos).
Muitas vezes, você desejará criar um formulário que vai incluir campos de vários objetos
diferentes. Por exemplo, um formulário de inscrição pode conter dados que pertencem a
um objeto User
, bem como, muitos objetos Address
. Felizmente, isto
é fácil e natural com o componente de formulário.
Suponha que cada Task
pertence a um simples objeto Category
. Inicie,
é claro, criando o objeto Category
:
// src/Acme/TaskBundle/Entity/Category.php
namespace Acme\TaskBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Category
{
/**
* @Assert\NotBlank()
*/
public $name;
}
Em seguida, adicione uma nova propriedade category
na classe Task
:
// ...
class Task
{
// ...
/**
* @Assert\Type(type="Acme\TaskBundle\Entity\Category")
*/
protected $category;
// ...
public function getCategory()
{
return $this->category;
}
public function setCategory(Category $category = null)
{
$this->category = $category;
}
}
Agora que a sua aplicação foi atualizada para refletir as novas exigências,
crie uma classe de formulário para que o objeto Category
possa ser modificado pelo usuário:
// src/Acme/TaskBundle/Form/Type/CategoryType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Category',
));
}
public function getName()
{
return 'category';
}
}
O objetivo final é permitir que a Category
de uma Task
possa ser modificada direitamente
dentro do próprio formulário da tarefa. Para fazer isso, adicione um campo category
ao objeto TaskType
cujo tipo é uma instância da nova classe
CategoryType
:
1 2 3 4 5 6 7 8 | use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->add('category', new CategoryType());
}
|
Os campos do CategoryType
podem agora ser renderizados juntamente com os campos da
classe TaskType
. Para ativar a validação no CategoryType, adicione
a opção cascade_validation
:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Category',
'cascade_validation' => true,
));
}
Renderize os campos Category
da mesma forma
que os campos originais da Task
:
1 2 3 4 5 6 7 8 9 | {# ... #}
<h3>Category</h3>
<div class="category">
{{ form_row(form.category.name) }}
</div>
{{ form_rest(form) }}
{# ... #}
|
1 2 3 4 5 6 7 8 9 | <!-- ... -->
<h3>Category</h3>
<div class="category">
<?php echo $view['form']->row($form['category']['name']) ?>
</div>
<?php echo $view['form']->rest($form) ?>
<!-- ... -->
|
Quando o usuário enviar o formulário, os dados submetidos para os campos Category
são usados para construir uma instância de Category
, que é então definida
no campo Category
da instância Task
.
A instância Category
é acessível naturalmente via $task->getCategory()
e pode ser persistida no banco de dados ou usada como você precisar.
Você também pode embutir uma coleção de formulários em um formulário (imagine um formulário
Category
com muitos sub-formulários Product
). Isto é feito usando o tipo de
campo collection
.
Para mais informações consulte no “/cookbook/form/form_collections” e na collection referência dos tipos de campo.
Cada parte de como um formulário é renderizado pode ser personalizada. Você está livre para mudar como cada “linha” do formulário é renderizada, alterar a marcação usada para renderizar os erros, ou até mesmo, personalizar como uma tag ``textarea` deve ser renderizada. Nada está fora dos limites, e é possível utilizar diferentes personalizações em diferentes lugares.
O Symfony utiliza templates para renderizar todas e cada uma das partes de um formulário, tais como
tags label
, tags input
, mensagens de erro e tudo mais.
No Twig, cada “fragmento” do formulário é representado por um bloco Twig. Para personalizar qualquer parte de como um formulário é renderizado, você só precisa substituir o bloco apropriado.
No PHP, cada “fragmento” do formulário é renderizado por um arquivo de template individual. Para personalizar qualquer parte de como um formulário é renderizado, você só precisa sobrescrever o template já existente, criando um novo.
Para entender como isso funciona, vamos personalizar o fragmento form_row
e
adicionar um atributo class para o elemento div
que envolve cada linha. Para
fazer isso, crie um novo arquivo template que irá armazenar a marcação nova:
1 2 3 4 5 6 7 8 9 10 11 | {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
{% block field_row %}
{% spaceless %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock field_row %}
|
1 2 3 4 5 6 7 | <!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php -->
<div class="form_row">
<?php echo $view['form']->label($form, $label) ?>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form, $parameters) ?>
</div>
|
O fragmento field_row
do formulário é utilizado para renderizar a maioria dos campos através da
da função form_row
. Para dizer ao componente de formulário para utilizar o seu novo fragmento
field_row
definido acima, adicione o seguinte no topo do template que
renderiza o formulário:
1 2 3 4 5 | {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}
<form ...>
|
1 2 3 4 5 | <!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form')) ?>
<form ...>
|
A tag form_theme
(no Twig) “importa” os fragmentos definidos no
template informado e utiliza-os quando renderiza o formulário. Em outras palavras,
quando a função form_row
é chamada mais tarde neste template, ela usará o bloco
field_row
de seu tema personalizado (ao invés do bloco padrão field_row
que vem com o Symfony).
Para personalizar qualquer parte de um formulário, você só precisa substituir o fragmento apropriado. Saber exatamente qual bloco ou arquivo deve-se substituir é o tema da próxima seção.
New in version 2.1: Foi introduzida uma sintaxe alternativa do Twig para form_theme
no 2.1. Ela aceita
qualquer expressão Twig válida (a diferença mais notável está no uso de um array quando
utilizar vários temas).
1 2 3 4 5 | {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form with 'AcmeTaskBundle:Form:fields.html.twig' %}
{% form_theme form with ['AcmeTaskBundle:Form:fields.html.twig', 'AcmeTaskBundle:Form:fields2.html.twig'] %}
|
Para uma discussão mais extensiva, consulte /cookbook/form/form_customization.
No Symfony, cada parte de um formulário que é renderizada - elementos de formulário HTML, erros, labels, etc - é definida em um tema base, que é uma coleção de blocos no Twig e uma coleção de arquivos de template no PHP.
No Twig, cada bloco necessário é definido em um único arquivo de template (`form_div_layout.html.twig`_) que encontra-se no interior do `Twig Bridge`_. Dentro desse arquivo, você pode ver todos os blocos necessários para renderizar um formulário e todo o tipo de campo padrão.
No PHP, os fragmentos são arquivos de template individuais. Por padrão, eles estão localizados no diretório Resources/views/Form do framework bundle (`veja no GitHub`_).
Cada nome de fragmento segue o mesmo padrão básico e é dividido em duas partes,
separadas por um único caractere de sublinhado (_
). Alguns exemplos são:
field_row
- usado pelo form_row
para renderizar a maioria dos campos;textarea_widget
- usado pelo form_widget
para renderizar um campo do tipo
textarea
;field_errors
- usado pelo form_errors
para renderizar os erros para um campo;Cada fragmento segue o mesmo padrão básico: type_part
. A porção type
corresponde ao tipo do campo sendo renderizado (Ex. textarea
, checkbox
,
date
, etc) enquanto a porção part
corresponde a o que está sendo
renderizado (Ex., label
, widget
, errors
, etc). Por padrão, existem
4 partes possíveis de um formulário que podem ser renderizadas:
label |
(Ex. field_label ) |
renderiza label do campo |
widget |
(Ex. field_widget ) |
renderiza a representação HTML do campo |
errors |
(Ex. field_errors ) |
renderiza os errors do campo |
row |
(Ex. field_row ) |
renderiza a linha inteira do campo (label, widget e erros) |
Note
Na verdade, existem outras três partes - rows
, rest
e enctype
-
mas você raramente ou nunca vai precisar se preocupar em sobrescrevê-las
Ao conhecer o tipo do campo (Ex. `` textarea``) e qual parte você deseja
personalizar (Ex. widget
), você pode construir o nome do fragmento que precisa
ser sobrescrito (Ex. textarea_widget
).
Em alguns casos, o fragmento que você deseja personalizar parecerá estar faltando.
Por exemplo, não existe um fragmento textarea_errors
nos temas padrão
fornecidos com o Symfony. Então, como são renderizados os erros de um campo textarea?
A resposta é: através do fragmento field_errors
. Quando o Symfony renderiza os erros
para um tipo textarea, ele procura primeiro por um fragmento textarea_errors
antes
de voltar para o fragmento field_errors
. Cada tipo de campo tem um tipo
pai (o tipo pai do textarea
é field
), e o Symfony usa
o fragmento para o tipo pai se o fragmento base não existir.
Então, para substituir os erros para apenas os campos textarea
, copie o
fragmento field_errors
, renomeie para textarea_errors
e personalize-o. Para
sobrescrever a renderização de erro padrão para todos os campos, copie e personalize
diretamente o fragmento field_errors
.
Tip
O tipo “pai” de cada tipo de campo está disponível na referência de tipos do formulário para cada tipo de campo.
No exemplo acima, você usou o helper form_theme
(no Twig) para “importar”
os fragmentos personalizados somente para este formulário. Você também pode dizer
ao Symfony para importar as personalizações do formulário para todo o seu projeto.
Para incluir automaticamente os blocos personalizados do template fields.html.twig
criado anteriormente em todos os templates, modifique o seu arquivo de configuração
da aplicação:
1 2 3 4 5 6 7 | # app/config/config.yml
twig:
form:
resources:
- 'AcmeTaskBundle:Form:fields.html.twig'
# ...
|
1 2 3 4 5 6 7 8 | <!-- app/config/config.xml -->
<twig:config ...>
<twig:form>
<resource>AcmeTaskBundle:Form:fields.html.twig</resource>
</twig:form>
<!-- ... -->
</twig:config>
|
1 2 3 4 5 6 7 8 | // app/config/config.php
$container->loadFromExtension('twig', array(
'form' => array('resources' => array(
'AcmeTaskBundle:Form:fields.html.twig',
))
// ...
));
|
Quaisquer blocos dentro do template fields.html.twig
agora são usados globalmente
para definir a saída do formulário.
Para incluir automaticamente os templates personalizados do diretório Acme/TaskBundle/Resources/views/Form
criado anteriormente em todos os templates, modifique o seu arquivo de configuração da
aplicação:
1 2 3 4 5 6 7 8 | # app/config/config.yml
framework:
templating:
form:
resources:
- 'AcmeTaskBundle:Form'
# ...
|
1 2 3 4 5 6 7 8 9 10 | <!-- app/config/config.xml -->
<framework:config ...>
<framework:templating>
<framework:form>
<resource>AcmeTaskBundle:Form</resource>
</framework:form>
</framework:templating>
<!-- ... -->
</framework:config>
|
1 2 3 4 5 6 7 8 9 | // app/config/config.php
$container->loadFromExtension('framework', array(
'templating' => array('form' =>
array('resources' => array(
'AcmeTaskBundle:Form',
)))
// ...
));
|
Qualquer fragmento dentro do diretório Acme/TaskBundle/Resources/views/Form
agora será usado globalmente para definir a saída do formulário.
CSRF - ou `Cross-site request forgery`_ - é um método pelo qual um usuário mal-intencionado tenta fazer com que os seus usuários legítimos, sem saber, enviem dados que eles não pretendem enviar. Felizmente, os ataques CSRF podem ser prevenidos usando um token CSRF dentro do seu formulário.
A boa notícia é que o Symfony, por padrão, incorpora e valida os tokens CSRF automaticamente para você. Isso significa que você pode aproveitar a proteção CSRF sem precisar fazer nada. Na verdade, todo formulário neste capítulo aproveitou a proteção CSRF!
A proteção CSRF funciona adicionando um campo oculto ao seu formulário - chamado _token
por padrão - que contém um valor que só você e seu usuário sabem. Isto
garante que o usuário - e não alguma outra entidade - está enviando os dados.
O Symfony automaticamente valida a presença e exatidão deste token.
O campo _token
é um campo oculto e será automaticamente renderizado
se você incluir a função form_rest()
em seu template, que garante
a saída de todos os campos não-renderizados.
O token CSRF pode ser personalizado formulário por formulário. Por exemplo:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
'csrf_protection' => true,
'csrf_field_name' => '_token',
// a unique key to help generate the secret token
'intention' => 'task_item',
));
}
// ...
}
Para desativar a proteção CSRF, defina a opção csrf_protection
para false.
As personalizações também podem ser feitas globalmente em seu projeto. Para mais informações
veja a seção referência de configuração do formulário
.
Note
A opção intention
é opcional, mas aumenta muito a segurança do
token gerado, tornando-o diferente para cada formulário.
Na maioria dos casos, um formulário é vinculado a um objeto, e os campos do formulário obtêm e armazenam seus dados nas propriedades desse objeto. Isto foi exatamente o que você viu até agora neste capítulo com a classe Task.
Mas, às vezes, você pode desejar apenas utilizar um formulário sem uma classe, e receber um array dos dados submetidos. Isso é realmente muito fácil:
// Certifique-se que você importou o namespace Request acima da classe
use Symfony\Component\HttpFoundation\Request
// ...
public function contactAction(Request $request)
{
$defaultData = array('message' => 'Type your message here');
$form = $this->createFormBuilder($defaultData)
->add('name', 'text')
->add('email', 'email')
->add('message', 'textarea')
->getForm();
if ($request->isMethod('POST')) {
$form->bind($request);
// data is an array with "name", "email", and "message" keys
$data = $form->getData();
}
// ... render the form
}
Por padrão, um formulário assume que você deseja trabalhar com arrays de dados, em vez de um objeto. Há exatamente duas maneiras em que você pode mudar esse comportamento e amarrar o formulário à um objeto:
createFormBuilder
ou o segundo argumento para createForm
);data_class
no seu formulário.Se você não fizer qualquer uma destas, então o formulário irá retornar os dados como
um array. Neste exemplo, uma vez que $defaultData
não é um objeto (e
não foi definida a opção data_class
), o $form->getData()
retorna
um array.
Tip
Você também pode acessar os valores POST (neste caso, “name”) diretamente através
do objeto do pedido (request
), desta forma:
1 | $this->get('request')->request->get('name');
|
Esteja ciente, no entanto, que, na maioria dos casos, usar o método getData() é uma melhor escolha, já que retorna os dados (geralmente um objeto), após ele ser transformado pelo framework de formulário.
A peça que falta é a validação. Normalmente, quando você chama $form->isValid()
,
o objeto é validado através da leitura das constraints que você aplicou à
classe. Mas, sem uma classe, como você pode adicionar constraints para os dados do seu
formulário?
A resposta é configurar as constraints você mesmo, e passá-las para o seu formulário. A abordagem global é explicada um pouco mais no validation chapter, mas aqui está um pequeno exemplo:
// import the namespaces above your controller class
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\Collection;
$collectionConstraint = new Collection(array(
'name' => new MinLength(5),
'email' => new Email(array('message' => 'Invalid email address')),
));
// create a form, no default values, pass in the constraint option
$form = $this->createFormBuilder(null, array(
'validation_constraint' => $collectionConstraint,
))->add('email', 'email')
// ...
;
Agora, quando você chamar $form->bind($request), a configuração de constraints aqui será executada
em relação aos dados do seu formulário. Se você estiver usando uma classe de formulário, sobrescreva
o método setDefaultOptions
para especificar a opção:
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\Collection;
class ContactType extends AbstractType
{
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$collectionConstraint = new Collection(array(
'name' => new MinLength(5),
'email' => new Email(array('message' => 'Invalid email address')),
));
$resolver->setDefaults(array(
'validation_constraint' => $collectionConstraint
));
}
}
Agora, você tem a flexibilidade de criar formulários - com validação - que retorna um array de dados, em vez de um objeto. Na maioria dos casos, é melhor - e certamente mais robusto - ligar (bind) o seu formulário à um objeto. Mas, para formulários simples, esta é uma excelente abordagem.
Você já conhece todos os blocos de construção necessários para construir formulários complexos e
funcionais para a sua aplicação. Ao construir formulários, tenha em mente que
a primeira meta de um formulário é traduzir os dados de um objeto (Task
) para um
formulário HTML, para que o usuário possa modificar os dados. O segundo objetivo de um formulário é
pegar os dados enviados pelo usuário e reaplicá-los ao objeto.
Ainda há muito mais para aprender sobre o mundo poderoso das formulários, tais como como lidar com uploads de arquivos com o Doctrine ou como criar um formulário onde um número dinâmico de sub-formulários podem ser adicionados (por exemplo, uma lista de tarefas onde você pode continuar a adicionar mais campos antes de enviar via Javascript). Veja estes tópicos no cookbook. Além disso, certifique-se de apoiar-se na documentação de referência de tipos de campo, que inclui exemplos de como usar cada tipo de campo e suas opções.
Virtual