Capítulo 1 - Criação do Form
Um form é feitos de campos como inputs hidden, inputs text, combos e checkboxes. Esse capítulo ensina a você a criação e manipulação de campos de formulário usando o framework de forms symfony.
É necessário o Symfony 1.1 para seguir os passos desse capítulo do livro. Você também ira precisar criar um projeto e um aplicativo chamado frontend
para continuar. Por favor, olhe a introdução para mais informações sobre a criação de um projeto symfony.
Antes de Iniciar
Vamos começar adicionando um formulário de contato em uma aplicação symfony
Figura 1-1 Mostra o formulário de contato como visto pelos usuário que querem enviar uma mensagem.
Figura 1-1 - Formulário de contato
Nós vamos criar 3 campos para este formulário: o nome do usuário, o email do usuário e a mensagem que o usuário quer enviar. Vamos simplesmente mostrar a informação submetida no formulário para o propósito deste exercício como mostrado na figura 1-2.
Figura 1-2 - Página de Agradecimento
Figura 1-3 - Interação entre a aplicação e o usuário
Widgets
Classes sfForm
e sfWidget
O usuário digita informação nos campos que compõe os formulários. No symfony um form é uma herança de objeto da classe sfForm
. No nosso exemplo, nós vamos criar uma classe ContactForm
que herda a classe sfForm
.
Nota
sfForm
é a classe base de todos os formulários e torna facil gerenciar a configuração e vida dos seus formulários.
Você pode iniciar a configuração de seu formulário adicionando widgets com o método configura()
Um widget representa um campo do formulário. Para nosso formulário de exemplo, nós precisamos adicionar 3 widgets que representam nossos três campos: name
, email
, e message
. Listagem 1-1 mostra a primeira implementação da classe ContactForm
Listagem 1-1 - Classe ContactForm
com três campos
// lib/form/ContactForm.class.php class ContactForm extends sfForm { public function configura() { $this->setWidgets(array( 'name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'message' => new sfWidgetFormTextarea(), )); } }
Os widgets são definidos no método configura()
. Esse método é automaticamente chamado pelo construtor da classe sfForm
O método setWidgets()
é usado para definir os widgets usados no form. O método setWidgets()
aceita um array associativo onde as chaves são os nomes do campos e os valores são classes widget. Cada widget é um objeto que herda a classe sfWidget
. Para esse exemplo nós usamos dois tipos de widgets:
sfWidgetFormInput
: Esse widget representa um campoinput
.sfWidgetFormTextarea
: Esse widget representa um campotextarea
.
Nota Como convenção, nós guardamos as classes de form no diretório
lib/form
. Você pode guardar elas em qualquer diretório gerenciado pelo mecanismo de autoloading do symfony, mas como vamos ver mais tarde, o symfony usa o diretóriolib/form
para gerar formulários baseados em objetos de modelo(ORM)
Mostrando o Formulário
Nosso formulário agora esta pronto para ser usado. Nós agora podemos criar um módulo symfony para mostrar o formulário:
$ cd ~/CAMINHO/PARA/O/PROJETO $ php symfony generate:module frontend contact
No módulo contact
, vamos modificar a action index
para passar uma instancia do formulário para o template, conforme a Listagem 1-2.
Listagem 1-2 - Classe Actions do módulo contact
// apps/frontend/modules/contact/actions/actions.class.php class contactActions extends sfActions { public function executeIndex() { $this->form = new ContactForm(); } }
Quando criando um formulário, o método configura()
, definido anteriormente, vai ser chamado automaticamente.
Agora nós só precisamos criar um template para mostrar o formulário, como mostrado na Listagem 1-3.
Listagem 1-3 - Template mostrando o formulário
// apps/frontend/modules/contact/templates/indexSuccess.php <form action="<?php echo url_for('contact/submit') ?>" method="POST"> <table> <?php echo $form ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
Um formulário do symfony somente gerencia os widgets mostrando informações ao usuário. No template indexSuccess
, a linha <?php echo $form ?>
mostra apenas três campos. Os outros elementos, como a tag form
e o botão de submit precisam ser adicionados pelo desenvolvedor. Isso não parece óbvio no inicio, mas como vamos depois como é fácil injetar formulários.
Usar essa construção <?php echo $form ?>
é muito bom para criação de protótipos e definindo os formulários. Isso permite que os desenvolvedores se concentrem nas regras de negócio sem se preocupar com aspectos visuais. O capitulo três vai explicar como personalizaro template e o layout do formulário.
Nota Ao mostrar um objeto usando
<?php echo $form ?>
, a engine PHP vai realmente mostrar a representação textual do objeto$form
. Para converter um objeto em uma string, o PHP tenta executar o metódo mágico__toString()
. Cada widget implementa este método mágico para converter o objeto em código HTML. Chamando<?php echo $form ?>
é o mesmo que chamar<?php echo $form->__toString() ?>
.
Agora nós podemos ver o formulário em um navegador (Figura 1-4) e verificar o resultado digitando o endereço da ação contact/index
(/frontend_dev.php/contact
).
Figura 1-4 - Formulário de Contato(Contact) Gerado
Listagem 1-4 Mostra o código gerado no template.
<form action="/frontend_dev.php/contact/submit" method="POST"> <table> <!-- Beginning of generated code by <?php echo $form ?> --> <tr> <th><label for="name">Name</label></th> <td><input type="text" name="name" id="name" /></td> </tr> <tr> <th><label for="email">Email</label></th> <td><input type="text" name="email" id="email" /></td> </tr> <tr> <th><label for="message">Message</label></th> <td><textarea rows="4" cols="30" name="message" id="message"></textarea></td> </tr> <!-- End of generated code by <?php echo $form ?> --> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
Podemos ver que o formulário é exibido com três linhas <tr>
de uma tabela HTML. Esse é o motivo de termos que fechar a tag <table>
. Cada linha inclui uma tag <label>
e uma tag de formulário (<input>
ou <textarea>
).
Rótulos(Labels)
Os rótulos de cada campo são gerados automaticamente. Por padrão, rótulos são a transformação do nome do campo seguindo as seguintes regras: Capitalizar a primeira letra e traço é trocado para espaço.
Exemplo:
$this->setWidgets(array( 'first_name' => new sfWidgetFormInput(), // rótulo gerado: "First name" 'last_name' => new sfWidgetFormInput(), // rótulo gerado: "Last name" ));
Mesmo se a geração automática de rótulos é útil, o framework permite que você defina rótulos personalizados com o método setLabels()
:
Como definir:
$this->widgetSchema->setLabels(array( 'name' => 'Your name', 'email' => 'Your email address', 'message' => 'Your message', ));
Você pode também modificar somente um único rótulo com o método setLabel()
;
$this->widgetSchema->setLabel('email', 'Your email address');
Finalmente, iremos ver no capítulo três que você pode extender os rótulos no template para personalizar o form.
sidebar
Widget Schema
Quando usamos o método setWidgets()
, o symfony cria um objeto sfWidgetFormSchema
. Esse objeto é um widget que permite que você represente um grupo de widgets. Em nosso formulário de contato, nós chamamos o método setWidgets()
. Isso é equivalente ao código a seguir:
$this->setWidgetSchema(new sfWidgetFormSchema(array( 'name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'message' => new sfWidgetFormTextarea(), ))); // também equivalente há: $this->widgetSchema = new sfWidgetFormSchema(array( 'name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'message' => new sfWidgetFormTextarea(), ));
O método setLabels()
é aplicado a coleção de widgets incluídos no objeto widgetSchema
.
Vamos ver no capítulo 5 que a noção de "schema widget" faz o gerenciamento de formulários agrupados bem mais simples.
Indo além das tabelas geradas
Mesmo o formulário sendo mostrado por padrão como uma tabela HTML, o formato do layout pode ser modificado. Esse diferentes tipos de layout são definidos em classes que herdam a classe sfWidgetFormSchemaFormatter
. Por padrão, um formulário o formato table
como definido na classe sfWidgetFormSchemaFormatterTable
. Você pode também usar o formato list
:
class ContactForm extends sfForm { public function configura() { $this->setWidgets(array( 'name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setFormFormatterName('list'); } }
Esses dois formatos vem por padrão, no capítulo 5 vamos ver como criar nossos próprios classes de formato. Agora que sabemos como mostrar um formulário, vamos ver como controlar a submissão.
Submetendo o formulário
Quando criamos um template para mostrar um formulário, usamos a URL interna contact/submit
na tag form
para enviar o form. Agora precisamos criar a action submit
no módulo contact
. A Listagem 1-5 mostra como a ação pode pegar a informação do usuário e redirecionar para uma pagina de agradecimento onde apenas mostramos as informações novamente pro usuário.
Listagem 1-5 - Uso da action submit
no módulo contact
public function executeSubmit($request) { $this->forward404Unless($request->isMethod('post')); $params = array( 'name' => $request->getParameter('name'), 'email' => $request->getParameter('email'), 'message' => $request->getParameter('message'), ); $this->redirect('contact/thankyou?'.http_build_query($params)); } public function executeThankyou() { } // apps/frontend/modules/contact/templates/thankyouSuccess.php <ul> <li>Name: <?php echo $sf_params->get('name') ?></li> <li>Email: <?php echo $sf_params->get('email') ?></li> <li>Message: <?php echo $sf_params->get('message') ?></li> </ul>
Nota
http_build_query
é uma função nativa do PHP que gera uma string codificada como URL a partir de um array de paramêtros.
o método executeSubmit()
executa três ações:
Por razões de segurança, checamos se a página foi submetida usando o método HTTP
POST
. Se não foi usado este método, o usuário é redirecionado para uma pagina de erro 404. No templateindexSuccess
, declaramos o método de envio comoPOST
(<form ... method="POST">
):$this->forward404Unless($request->isMethod('post'));
A seguir pegamos os valores digitados pelo usuário e guardamos na tabela
params
:$params = array( 'name' => $request->getParameter('name'), 'email' => $request->getParameter('email'), 'message' => $request->getParameter('message'), );
Por fim, redirecionamos o usuário para a página de agradecimento(
contact/thankyou
) para mostrar as informações dele:$this->redirect('contact/thankyou?'.http_build_query($params));
Ao invés de redirecionar o usuário para outra página, poderiamos ter criado um template submitSuccess.php
. Mesmo sendo isso possível, é uma boa prática sempre redirecionar o usuário após uma requisição do método POST
:
Isso previne a dupla requisição caso o usuário recarrege a página de agradecimento.
O usuário pode clicar no botão de Voltar sem precisar ficar clicando na pop-up de aviso de reenvio de informações de formulário.
Dica Voçê deve ter notado que o método
executeSubmit()
é diferente doexecuteIndex()
. Ao chamar esses métodos o symfony passa o objetosfRequest
atual como primeiro argumento dos métodosexecuteXXX()
. Em PHP, você não precisa coletar todos os parametrôs, por isso que você não define a variavelrequest
no métodoexecuteIndex()
ja que você não precisa dela.
Figura 1-5 mostra o fluxo dos métodos ao interagir com o usuário.
Figura 1-5 - Fluxo dos métodos
Nota Ao mostrar novamente as informações que o usuário digitou no template, você corre o risco de um ataque XSS(Cross-Site Scripting). Você pode achar maiores informações sobre como previnir esses tipos de ataque implementando uma estratégia de escape no capítulo Inside the View Layer do livro "The Definitive Guide to symfony".
Após submeter o formulário, você deve ver agora a pagina conforme a Figura 1-6.
Figura 1-6 - Página mostrada após enviar o formulário
Invés de criar o array params
, é mais fácil pegar a informação vinda do usuário diretamente em um array.
Listagem1-6 modifica o atributo HTML name
dos widgets para armazenar os valores dos campos no array contact
.
Listagem1-6 - Modifica o atributo HTML name
dos widgets
class ContactForm extends sfForm { public function configura() { $this->setWidgets(array( 'name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contact[%s]'); } }
Chamando setNameFormat()
nos permite modificar o atributo HTML name
de todos os widgets. %s
vai ser automaticamente trocado pelo nome do campo durante a geração do formulário. Por exemplo, o atributo name
sera contact[email]
para o campo email
. O PHP automaticamente cria um array com os valores do request incluindo um no formato contact[email]
. Dessa forma os valores dos campo estarão disponíves no array contact
.
Nós agora podemos acessar diretamente o array contact
do objeto request
como mostrado na Listagem 1-7
Listagem 1-7 - Novo formato dos atributos name
na action
public function executeSubmit($request) { $this->forward404Unless($request->isMethod('post')); $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact'))); }
Ao olhar o fonte HTML do formulário, você podera ver que o symfony gerou o atributo name
dependendo não apenas do nome do campo e do formato, mas também do atributo id
. O atributo id
é automaticamente criado a partir do atributo name
trocando os caracteres invalidos por traços (_
):
Nome | Atributo name |
Atributo id |
---|---|---|
name | contact[name] | contact_name |
contact[email] | contact_email | |
message | contact[message] | contact_message |
Outra solução
Neste exemplo, usamos duas actions para controlar o formulário: index
para mostra-lo, submit
para enviar-lo. Sendo o formulário mostrado com o método GET
e enviado com o método POST
, podemos mesclar esses dois métodos no index
, como mostrado na Listagem 1-8.
Listagem 1-8 - Mesclagem de duas actions usadas no formulário
class contactActions extends sfActions { public function executeIndex($request) { $this->form = new ContactForm(); if ($request->isMethod('post')) { $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact'))); } } }
Você também precisa mudar o atributo action
do formulário no template indexSuccess.php
:
<form action="<?php echo url_for('contact/index') ?>" method="POST">
Como veremos depois, nós preferimos usar essa forma ja que ela é menor e torna o código mais coerente e legivel.
configurando os Widgets
Opções dos Widgets
Se website é controlado por varios webmasters, nós com certeza gostariamos de adicionar uma combo com temas para redirecionar a mensagem de acordo com o que é pedido.(Figura 1-7). A Listagem 1-9 adiciona um subject
com uma combo usando o widget sfWidgetFormSelect
.
Figura 1-7 - Adicionado o camposubject
ao Formulário
Listagem 1-9 - Adicionado o camposubject
ao Formulário
class ContactForm extends sfForm { protected static $subjects = array('Subject A', 'Subject B', 'Subject C'); public function configura() { $this->setWidgets(array( 'name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contact[%s]'); } }
sidebar
A opção choices
do widget sfWidgetFormSelect
O PHP não diferencia um array de um array associativo, então o array que usamos na lista de subjects
é o mesmo que o código a seguir:
$subjects = array(0 => 'Subject A', 1 => 'Subject B', 2 => 'Subject C');
O widget gerado pega a chave do array para o atributo value
da tag option
e o valor como conteudo da tag:
<select name="contact[subject]" id="contact_subject"> <option value="0">Subject A</option> <option value="1">Subject B</option> <option value="2">Subject C</option> </select>
Para alterar o atributo value
, só precisamos definir as chaves do array:
$subjects = array('A' => 'Subject A', 'B' => 'Subject B', 'C' => 'Subject C');
Que gera o seguinte HTML:
<select name="contact[subject]" id="contact_subject"> <option value="A">Subject A</option> <option value="B">Subject B</option> <option value="C">Subject C</option> </select>
O widget sfWidgetFormSelect', assim como todos os outros, pega uma lista de opções como primeiro argumento. Uma opção pode ser obrigatória ou opcional. O widget
sfWidgetFormSelectpossui uma opção obrigatória,
choices`. Aqui esta as opções disponíveis para todos os widgets que usamos até aqui:
Widget | Opções Obrigatórias | Opções Adicionais |
---|---|---|
sfWidgetFormInput |
- | type (padrão é text ) |
is_hidden (padrão é false ) |
||
sfWidgetFormSelect |
choices |
multiple (padrão é false ) |
sfWidgetFormTextarea |
- | - |
Dica Se você que conhecer todas as opções para um widget, você olhar documentação completa da API disponível online em (/api/1_1/). Todas as opções são explicadas, assim como os valores padrões da opções adicionais. Por exemplo, todas as opções do widget
sfWidgetFormSelect
estão aqui: (/api/1_1/sfWidgetFormSelect).
Os atributos HTML dos Widgets
Cada widget também pega uma lista de atributos HTML como segundo parametro opcional. Isso é extremamente útil para definir valores padrão da tag gerada. A Listagem 1-10 mostra como adicionar o atributo class
no campo email
Listagem 1-10 - Definindo atributos para um Widget
$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email')); // Generated HTML <input type="text" name="contact[email]" class="email" id="contact_email" />
Atributos HTML permitem também que nós sobrescrevamos o identificador gerado automaticamente, como mostrado na Listagem 1-11.
Listagem 1-11 - Sobrescrevendo o atributo id
$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email', 'id' => 'email')); // Generated HTML <input type="text" name="contact[email]" class="email" id="email" />
É possivel ainda colocar valores padrão nos campos usando o atributo value
, como mostra a Listagem 1-12.
Listagem 1-12 - Valores Default nos Widgets via atributos HTML
$emailWidget = new sfWidgetFormInput(array(), array('value' => 'Your Email Here')); // Generated HTML <input type="text" name="contact[email]" value="Your Email Here" id="contact_email" />
Essa opção funciona para widgets input
, mas é dificil fazer o mesmo com os widgets checkbox
ou radio
, e é impossivel com o widget textarea
. A classe sfForm
tem métodos especificos para definir valores padrão para cada campo de um forma uniforme para qualquer tipo de widget.
Nota Recomendamos definir os atributos HTML dentro dos templates e não no formulário(se forpossível) para presevar a separação em camadas, que vamos ver no capítulo três.
Definindo valores padrão para os campos
Muitas vezes é util definir valores padrão para cada campo. Por exemplo quando vamos mostrar uma mensagem de ajuda em cada campo quando o usuário estiver naquele campo. A Listagem 1-13 mostra como definir valores padão com os métodossetDefault()
e setDefaults()
.
Listagem 1-13 - Valores padrão para Widget via setDefault()
e setDefaults()
class ContactForm extends sfForm { public function configura() { // ... $this->setDefault('email', 'Your Email Here'); $this->setDefaults(array('email' => 'Your Email Here', 'name' => 'Your Name Here')); } }
Os métodos setDefault()
e setDefaults()
são muitos para definir valores padrão iguais para cada instancia de uma mesma classe de formulário. Se você quer modificar um objeto existente usando um formulário, os valores padrão irão depender dessa instancia, portanto, eles devem ser dinâmicos. A listagem 1-14 mostra que o construtor da classe sfForm
recebe como primeiro argumento uma lista de valores padrões que seta dinamicamente os valores padrão
Listagem 1-14 - Valores padrão via construtor da classe sfForm
public function executeIndex($request) { $this->form = new ContactForm(array('email' => 'Your Email Here', 'name' => 'Your Name Here')); // ... }
sidebar
Proteção XSS (Cross-Site Scripting)
Ao setr atributos HTML para widgets ou definindo valores padrão, a classe sfForm
automaticamente protege esses valores contra ataques XSS durante a geração do código HTML. Essa proteção não depende da configuração escaping_strategy
do arquivo settings.yml
. Se um valor ja foi protegido por outro método, essa proteção não sera aplicada novamente.
Ele também protege contra os caracteres '
and "
que podem invalidar o HTML gerado.
Um exemplo dessa proteção:
$emailWidget = new sfWidgetFormInput(array(), array( 'value' => 'Hello "World!"', 'class' => '<script>alert("foo")</script>', )); // HTML gerado <input value="Hello "World!"" class="<script>alert("foo")</script>" type="text" name="contact[email]" id="contact_email" />
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.