Development

Documentation/pt_BR/forms_in_action/1.1/01-Criacao-de-Formulario

You must first sign up to be able to contribute.

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

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

Página de Agradecimento

Figura 1-3 - Interação entre a aplicação e o usuário

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

[php]
// 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 campo input.
  • 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ório lib/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

[php]
// 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

[php]
// 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

Formulário de Contato(Contact) Gerado Gerado")

Listagem 1-4 Mostra o código gerado no template.

[html]
<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:

[php]
$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:

[php]
$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();

[php]
$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:

[php]
$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:

[php]
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

[php]
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 template indexSuccess, declaramos o método de envio como POST (<form ... method="POST">):

    [php]
    $this->forward404Unless($request->isMethod('post'));
    
  • A seguir pegamos os valores digitados pelo usuário e guardamos na tabela params:

    [php]
    $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:

    [php]
    $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 do executeIndex(). Ao chamar esses métodos o symfony passa o objeto sfRequest atual como primeiro argumento dos métodos executeXXX(). Em PHP, você não precisa coletar todos os parametrôs, por isso que você não define a variavel request no método executeIndex() 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

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

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

[php]
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

[php]
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 | | email | 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

[php]
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:

[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

![Adicionado o camposubject ao Formulário](/images/forms_book/en/01_07.png "Adicionado o camposubject ao Formulário")

Listagem 1-9 - Adicionado o camposubject ao Formulário

[php]
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:

[php]
$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:

[php]
<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:

[php]
$subjects = array('A' => 'Subject A', 'B' => 'Subject B', 'C' => 'Subject C');

Que gera o seguinte HTML:

[php]
<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 sfWidgetFormSelect possui 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 (http://www.symfony-project.org/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: (http://www.symfony-project.org/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

[php]
$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

[php]
$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

[php]
$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()

[php]
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

[php]
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:

[php]
$emailWidget = new sfWidgetFormInput(array(), array(
  'value' => 'Hello "World!"',
  'class' => '<script>alert("foo")</script>',
));
<br />
// HTML gerado
<input
  value="Hello &quot;World!&quot;"
  class="&lt;script&gt;alert(&quot;foo&quot;)&lt;/script&gt;"
  type="text" name="contact[email]" id="contact_email"
/>