.. index::
single: Formulários
Formulários
===========
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.
.. index::
single: Formulários; Criando um formulário simples
Criando um formulário simples
-----------------------------
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:
.. code-block:: php
// 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):
.. code-block:: bash
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.
.. index::
single: Formulários; Criando um formulário no controlador
Construindo o Formulário
~~~~~~~~~~~~~~~~~~~~~~~~
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 ":ref:`book-form-creating-form-classes`", 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 :ref:`book-forms-type-reference`).
.. index::
single: Forms; Renderização básica do template
Renderizando o Formulário
~~~~~~~~~~~~~~~~~~~~~~~~~
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:
.. configuration-block::
.. code-block:: html+jinja
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
.. code-block:: html+php
.. image:: /images/book/form-simple.png
:align: center
.. 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 ":ref:`form-rendering-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()``).
.. index::
single: Forms; Handling form submission
Manipulando o envio de formulários
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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'));
}
}
// ...
}
.. versionadded:: 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:
#. Inicialmente quando se carrega a página em um navegador, o método de solicitação (request) é
``GET`` e o formulário é simplesmente criado e renderizado;
#. Quando o usuário envia o formulário (Ex., o método é ``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;
#. Quando o usuário envia o formulário com dados válidos, o formulário é vinculado (bound) e
você tem a oportunidade de executar algumas ações usando o objeto ``$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.
.. index::
single: Formulários; Validação
Validação 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.
.. configuration-block::
.. code-block:: yaml
# Acme/TaskBundle/Resources/config/validation.yml
Acme\TaskBundle\Entity\Task:
properties:
task:
- NotBlank: ~
dueDate:
- NotBlank: ~
- Type: \DateTime
.. code-block:: php-annotations
// 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;
}
.. code-block:: xml
\DateTime
.. code-block:: php
// 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.
.. _book-forms-html5-validation-disable:
.. sidebar:: Validação HTML5
Com o HTML5, muitos navegadores podem, nativamente, impor certas *constraints* de validação
no lado do cliente. A validação mais comum é ativada renderizando
um atributo ``required`` em campos que são obrigatórios. Para navegadores que
suportam HTML5, isso irá resultar em uma mensagem nativa do navegador sendo exibida
se o usuário tentar enviar o formulário com o campo em branco.
Os formulários gerados podem aproveitar ao máximo esta nova funcionalidade, adicionando
atributos HTML que disparam a validação. A validação ao lado do cliente,
entretanto, pode ser desativada ao adicionar o atributo ``novalidate`` na
tag ``form`` ou ``formnovalidate`` na tag submit. Isto é especialmente
útil quando você quiser testar suas *constraints* de validação ao lado do servidor,
mas estão sendo impedidas pelo seu navegador, por exemplo, ao enviar
campos em branco.
A validação é um recurso muito poderoso do Symfony2 e tem seu próprio
:doc:`capítulo dedicado`.
.. index::
single: Formulários; Grupos de Validação
.. _book-forms-validation-groups:
Grupos de Validação
~~~~~~~~~~~~~~~~~~~
.. tip::
Se você não estiver usando :ref:`grupos de validação`,
então, você pode pular esta seção.
Se o seu objeto aproveita a :ref:`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 :ref:`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.
Grupos com base nos dados submetidos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 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');
}
},
));
}
.. index::
single: Formulários; Tipos de campos integrados
.. _book-forms-type-reference:
Tipos de campos integrados (Built-in)
-------------------------------------
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:
.. include:: /reference/forms/types/map.rst.inc
Você também pode criar os seus próprios tipos de campo personalizados. Este tópico é abordado
no artigo ":doc:`/cookbook/form/create_custom_field_type`" do cookbook.
.. index::
single: Formulários; Opções dos tipos de campos
Opções dos tipos de campos
~~~~~~~~~~~~~~~~~~~~~~~~~~
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 :doc:`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'))
.. image:: /images/book/form-simple2.png
:align: center
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.
.. sidebar:: A opção ``required``
A opção mais comum é a opção ``required``, que pode ser aplicada à
qualquer campo. Por padrão, a opção ``required`` é definida como ``true``, o que significa
que os navegadores prontos para o HTML5 aplicarão a validação ao lado do cliente se o campo
for deixado em branco. Se você não deseja esse comportamento, defina a opção ``required``
em seu campo para ``false`` ou :ref:`desabilite a validação HTML5`.
Além disso, note que a configuração da opção ``required`` para ``true`` **não**
resultará em validação aplicada ao lado do servidor. Em outras palavras, se um
usuário enviar um valor em branco para o campo (ou usar um navegador antigo
ou web service, por exemplo), ela será aceita como um valor válido, a menos
que você utilize a constraint de validação do Symfony ``NotBlank`` ou ``NotNull``.
Em outras palavras, a opção ``required`` é "agradável", mas a validação verdadeira
ao lado do servidor *sempre* deverá ser usada.
.. index::
single: Formulários; Adivinhando o tipo do campo
.. _book-forms-field-guessing:
Adivinhando o tipo do campo
---------------------------
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).
.. index::
single: Formulários; Adivinhando o tipo do campo
Adivinhando as opções dos tipos de campos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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))
.. index::
single: Formulários; Renderizando em um Template
.. _form-rendering-template:
Renderizando um formulário em um Template
-----------------------------------------
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:
.. configuration-block::
.. code-block:: html+jinja
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
.. code-block:: html+php
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 :ref:`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 :ref:`form-theming`, 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``:
.. configuration-block::
.. code-block:: jinja
{{ form.vars.value.task }}
.. code-block:: html+php
get('value')->getTask() ?>
.. index::
single: Formulários; Renderizando cada campo manualmente
Renderizando cada campo manualmente
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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``:
.. configuration-block::
.. code-block:: html+jinja
{{ form_errors(form) }}
{{ form_label(form.task) }}
{{ form_errors(form.task) }}
{{ form_widget(form.task) }}
{{ form_label(form.dueDate) }}
{{ form_errors(form.dueDate) }}
{{ form_widget(form.dueDate) }}
{{ form_rest(form) }}
.. code-block:: html+php
errors($form) ?>
label($form['task']) ?>
errors($form['task']) ?>
widget($form['task']) ?>
label($form['dueDate']) ?>
errors($form['dueDate']) ?>
widget($form['dueDate']) ?>
rest($form) ?>
Se a label auto-gerada para um campo não estiver correta, você pode especificá-la
explicitamente:
.. configuration-block::
.. code-block:: html+jinja
{{ form_label(form.task, 'Task Description') }}
.. code-block:: html+php
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:
.. configuration-block::
.. code-block:: html+jinja
{{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}
.. code-block:: html+php
widget($form['task'], array(
'attr' => array('class' => 'task_field'),
)) ?>
Referência de funções dos templates Twig
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Se você está usando o Twig, uma referência completa das funções de renderização do formulário
está disponível no :doc:`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.
.. index::
single: Formulários; Criando classes de formulário
.. _book-form-creating-form-classes:
Criando classes de formulário
-----------------------------
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:
.. code-block:: php
// 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:
.. code-block:: php
// 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ê.
.. _book-forms-data-class:
.. sidebar:: Setando o ``data_class``
Todo formulário precisa saber o nome da classe que contém os dados implícitos
(Ex. ``Acme\TaskBundle\Entity\Task``). Normalmente, ele é apenas adivinhado
com base no objeto passado no segundo argumento para o ``createForm``
(Ex. ``$task``). Mais tarde, quando você iniciar nos formulários embutidos, isto
não será suficiente. Então, embora nem sempre necessário, é geralmente uma
boa idéia especificar explicitamente a opção ``data_class`` adicionando
o seguinte à sua classe type de formulário::
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
));
}
.. 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();
.. index::
pair: Formulários; Doctrine
Formulários e o Doctrine
------------------------
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
:ref:`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 :doc:`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).
.. index::
single: Formulários; Formulários embutidos
Formulários embutidos
---------------------
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.
Embutindo um único objeto
~~~~~~~~~~~~~~~~~~~~~~~~~
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``:
.. code-block:: php
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``:
.. configuration-block::
.. code-block:: html+jinja
{# ... #}
Category
{{ form_row(form.category.name) }}
{{ form_rest(form) }}
{# ... #}
.. code-block:: html+php
Category
row($form['category']['name']) ?>
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.
Embutindo uma coleção de formulários
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 ":doc:`/cookbook/form/form_collections`"
e na :ref:`collection` referência dos tipos de campo.
.. index::
single: Formulários; Tematizando
single: Formulários; Personalizando os campos
.. _form-theming:
Tematizando os formulários
--------------------------
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:
.. configuration-block::
.. code-block:: html+jinja
{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
{% block field_row %}
{% spaceless %}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock field_row %}
.. code-block:: html+php
label($form, $label) ?>
errors($form) ?>
widget($form, $parameters) ?>
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:
.. configuration-block:: php
.. code-block:: html+jinja
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}