goncin@wordpress.com:~$ _

Linux, programação e toda sorte de nerdices

Arquivos de tags: formulários

Gerador de formulários HTML em PHP orientado a objetos (parte 2)

Se você chegou aqui diretamente, recomendo que você dê uma olhada na parte 1, sob pena de não entender nada 😛 …

Aqui estou eu novamente, continuando meu brainstorm a respeito de um gerador de formulários HTML implementado em PHP orientado a objetos. Como eu havia dito, ainda havia controles a implementar, e então aqui vai mais um, o <select> (drop down):

class GSelect extends GFormControl {

    private $_items = array();
    private $_selectedValue = null;

    public function  __construct($id, $label = '', $attributes = array(), $items = array(), $selectedValue = null) {
      $this->setId($id);
      $this->setLabel($label);
      $this->setAttributes($attributes);
      $this->setItems($items);
      $this->setSelectedValue($selectedValue);
    }

    protected function getOpenTag() {
      return '<select';
    }

    protected function getCloseTag() {
      return "\t</select>";
    }

    public function setItems($items) {
      if (is_array($items)) $this->_items = $items;
      return $this;
    }

    public function getItems() {
      return $this->_items;
    }

    public function setSelectedValue($value) {
      $this->_selectedValue = $value;
      return $this;
    }

    public function getSelectedValue() {
      return $this->_selectedValue;
    }

    protected function renderField() {
      $html = $this->getOpenTag() . " id='{$this->getId()}' ";
      $html .= $this->renderFieldAttributes() . ">\n";

      $items = $this->getItems();

      foreach($items as $value => $text) {
        $html .= "\t\t<option value='{$value}'";
        if ($value === $this->getSelectedValue())
           $html .= " selected='selected'";
        $html .= ">{$text}</option>\n";
      }

      $html .= $this->getCloseTag();

      return $html;
    }

  }

Este controle deu um pouco mais de trabalho para codificar porque, ao contrário dos anteriores, ele não é baseado na tag <input>. Basicamente, foi necessário fazer isso:

  • implementar métodos para configurar e retornar os itens da lista de seleção (setItems() e getItems());
  • implementar para configurar e retornar o valor que já virá selecionado na lista (setSelectedValue() e getSelectedValue());
  • sobrescrever método renderField(), a fim de refletir as particularidades desse controle; e
  • sobrescrever o construtor para que este aceitasse dois novos parâmetros, $items e $selectedValue, de forma que o controle pudesse ser totalmente configurado no momento de sua criação.

Por fim, ficou faltando escrever o controle que represente o próprio formulário (<form>). Isso foi feito facilmente, estendendo também o GForm a partir de GFormControl.

  class GForm extends GFormControl {

    private $_controls = array();

    protected function getOpenTag() {
      return '<form';
    }

    protected function getCloseTag() {
      return '</form>';
    }

    public function addControl($control) {
      if ($control instanceof GFormControl) $this->_controls[] = $control;
      return $this;
    }

    protected function getControls() {
      return $this->_controls;
    }

    protected function renderField() {
      $html = $this->getOpenTag() . " id='{$this->getId()}' action='{$this->getLabel()}' ";
      $html .= $this->renderFieldAttributes() . ">\n";

      $controls = $this->getControls();

      foreach($controls as $ctl)
        $html .= "{$ctl->render()}\n";

      $html .= $this->getCloseTag();

      return $html;

    }

    public function render() {
      return $this->renderField();
    }

  }

O grande diferencial do GForm em relação aos controles anteriormente desenvolvidos é sua capacidade de ter controles filhos, cujo gerenciamento básico é feito pelos métodos addControl() e getControls(). Assim, quando for chamado o método render() do formulário, este fará a chamada ao método render() de cada um de seus filhos, poupando-nos linhas de código. E, por tocar no assunto de economia de codificação, reparem que o método addControl() retorna a instância do objeto ($this), permitindo-nos montar o formulário mediante chamadas de métodos encadeados. Vejam só:

  $opcoes = array (
    0 => '(Selecione)',
    1 => 'Aluno',
    2 => 'Professor',
    3 => 'Administrativo'
  );

  $form = new GForm('form1', 'proc_login.php');

  echo $form
    ->addControl(new GTextBox('usuario', 'Usuário:', array('maxlength'=>50, 'size'=>15)))
    ->addControl(new GPassword('senha', 'Senha:', array('maxlength'=>50, 'size'=>15)))
    ->addControl(new GSelect('tipo_acesso', 'Tipo de Acesso:', null, $opcoes, 2))
    ->addControl(new GCheckBox('guardar_info', 'Guardar informações?'))
    ->addControl(new GSubmit('enviar', 'Enviar'))
    ->render();

O segundo parâmetro do construtor, que nos outros controles é o texto descritivo (label), no caso do GForm serve para especificar o valor do atributo action. As linhas 10 a 16 constituem uma única instrução, encadeando métodos. O <select> gerado vem com a opção de valor 2 pré-selecionada (último parâmetro do construtor do GSelect).

Eis o código fonte HTML gerado:

<form id='form1' action='proc_login.php' >
<div>
	<label for='usuario'>Usuário:</label><br />
	<input type='text' id='usuario' maxlength='50' size='15' />
</div>

<div>
	<label for='senha'>Senha:</label><br />
	<input type='password' id='senha' maxlength='50' size='15' />
</div>

<div>
	<label for='tipo_acesso'>Tipo de Acesso:</label><br />
	<select id='tipo_acesso' >
		<option value='0'>(Selecione)</option>
		<option value='1'>Aluno</option>
		<option value='2' selected='selected'>Professor</option>
		<option value='3'>Administrativo</option>
	</select>
</div>

<div>
	<label for='guardar_info'>Guardar informações?</label><br />
	<input type='checkbox' id='guardar_info' />
</div>

<div>
	<input type='submit' id='enviar' value='Enviar' />
</div>

</form>
Aparência do formulário HTML gerado pelo código PHP

Aparência do formulário HTML gerado pelo código PHP

Mais uma vez, devo agradecê-lo por me acompanhar na leitura deste loooooongo e super-hiper-mega-técnico post. Espero que tenha valido a pena, que você tenha compreendido o que eu quis demonstrar (conceitos de programação orientada a objetos), e que tenhamos, eu e você, aprendido coisas novas. 😀

Anúncios

Gerador de formulários HTML em PHP orientado a objetos (parte 1)

Os frameworks para desenvolvimento Web nunca estiveram tão em voga quanto atualmente. De fato, utilizá-los é bem melhor do que reinventar a roda, apesar do esforço despendido no apendizado no aprendizado da ferramenta.

O código fonte de seu framework favorito é uma fonte abundante de técnicas de programação. Não hesite em vasculhá-lo. Foi olhando um desses códigos (no caso, o do Yii Framework), que cogitei em escrever um gerador de formulários HTML, como prova de conceito.

Mas, para quê reimplementar, se já está no framework? No meu caso, pelo próprio aprendizado, para dominar técnicas novas. E, diga-se de passagem, não sou o único a fazer esse tipo de coisa. Obviamente, essas experimentações jamais entrarão em produção. A não ser que, é claro, fiquem tão boas e estáveis que se tornem um novo framework. 😛

A ideia por detrás do gerador de formulários é bastante simples: a partir de uma conjunto de objetos, cada qual representando um controle de formulário e com propriedades corretamente configuradas, obter código o HTML válido correspondente. Não sei quando a vocês, mas acho digitar HTML um porre (abre tag, põe id, configura atributos, etc., etc. e, no fim, acaba esquecendo de fechar a tag). Por que não, então, escrever esses formulários em puro PHP, e deixá-lo gerar o chato do HTML?

De acordo? Vamos lá.

Comecei codificando uma classe genérica e abstrata, para servir de base a todos os controles, chamada GFormControl. Segue o código abaixo:

<?php
  abstract class GFormControl {

    private $_id;                         // ID do campo de formulário
    private $_label;                      // Texto descritivo do campo
    private $_attributes;                 // Outros atributos do campo
    private $_labelSeparator = '
';  // Separador entre o "label" e o campo

    public function  __construct($id, $label = '', $attributes = array()) {
      $this->setId($id);
      $this->setLabel($label);
      $this->setAttributes($attributes);
    }

    /********** SETTERS **********/

    protected function setId($value) {
      $this->_id = $value;
      return $this;
    }

    public function setLabel($value) {
      $this->_label = $value;
      return $this;
    }

    public function setLabelSeparator($value) {
      $this->_labelSeparator = $value;
      return $this;
    }

    public function setAttributes($value) {
      $this->_attributes = $value;
      return $this;
    }

    /********** GETTERS **********/

    public function getId() {
      return $this->_id;
    }

    public function getLabel() {
      return $this->_label;
    }

    abstract protected function getOpenTag();

    abstract protected function getCloseTag();

    public function getLabelSeparator() {
      return $this->_labelSeparator;
    }

    public function getAttributes() {
      return $this->_attributes;
    }

    /***** MÉTODOS PROTEGIDOS *****/

    // Rendereiza a abertura da DIV
    protected function renderOpenDiv() {
      return "<div>\n\t";
    }

    // Renderiza o LABEL que contém a descrição do campo
    protected function renderLabel($labelSeparator = null) {

      if (! is_null($labelSeparator))
        $this->setLabelSeparator($labelSeparator);

      return "<label for="{$this->getId()}">{$this->getLabel()}</label>{$this->getLabelSeparator()}\n\t";

    }

    // Renderiza o campo de formulário propriamente dito
    protected function renderField() {

      $html = $this->getOpenTag() . " id='{$this->getId()}' ";
      $html .= $this->renderFieldAttributes();
      $html .= $this->getCloseTag();

      return $html;

    }

    // Renderiza os atributos do campo de formulário
    protected function renderFieldAttributes() {
      $html = '';

      if ($attrs = $this->getAttributes())
        foreach($attrs as $attr => $value)
          $html .= "{$attr}='{$value}' ";

      return $html;
    }

    // Renderiza o fechamento da DIV
    protected function renderCloseDiv() {
      return "\n</div>\n";
    }

    /***** MÉTODOS PÚBLICOS */

    /*
     * Renderiza o campo de formulário, de acordo com os parâmetros configurados.
     * Para tanto, efetua chamadas aos métodos protegidos responsáveis pela
     * renderização das diferentes partes.
     * Cada campo será renderizado da seguinte forma:
     *
     *
<div>
     *   <label for="nome_campo">Descrição do campo</label>
     *
     *</div>
     */

    public function render($labelSeparator = null) {

      $html = $this->renderOpenDiv();
      $html .= $this->renderLabel($labelSeparator);
      $html .= $this->renderField();
      $html .= $this->renderCloseDiv();

      return $html;

    }

  }

Por que uma classa abstrata? Simples: não desejo criar instâncias dessa classe. Sua razão de ser é servir como ancestral para as classes de cada controle de formulário. Além da própria classe, os métodos protegidos getOpenTag() e getCloseTag() estão também marcados como abstract. Isso significa que qualquer classe que estenda GFormControl fica obrigada a implementar esses dois métodos.

O único método público da classe (desconsiderando-se os getters) é o render(), responsável por gerar o código HTML correspondente ao campo de formulário. Esse método faz chamadas a outros métodos, cada qual responsável por renderizar uma parte diferente do campo. Os métodos renderXXX() foram definidos como protegidos – isso significa que são visíveis somente pelas classes descendentes, pois não foram projetadas para serem chamados pelo usuário final.

Em suma, esta classe define muita coisa, mas não faz nada. E agora? Veja como ficou o código de quatro controles:

<?php
  // Campo de texto
  class GTextBox extends GFormControl {

    protected function getOpenTag() {
      return "<input type="text" />';
    }

  }

  /***********************************************************/

  // Campo de senha
  class GPassword extends GFormControl {

    protected function getOpenTag() {
      return "<input type="password" />';
    }

  }

  /***********************************************************/

  // Caixa de verificação
  class GCheckBox extends GFormControl {

    protected function getOpenTag() {
      return "<input type="checkbox" />';
    }

  }

  /***********************************************************/

  // Botão de enviar
  class GSubmit extends GFormControl {

    protected function getOpenTag() {
      return "<input type="submit" />';
    }

    /* No caso do botão de enviar, foi necessária uma sobrecarga (override)
     * dos métodos renderField() e render(), porque o controle não tem um
     * LABEL associado a ele, e o texto que iria no LABEL vai no atributo
     * VALUE do próprio controle.
     */

    protected function renderField() {
      $html = $this->getOpenTag() . " id='{$this->getId()}' value='{$this->getLabel()}' ";
      $html .= $this->renderFieldAttributes();
      $html .= $this->getCloseTag();

      return $html;
    }

    public function render() {
      $html = $this->renderOpenDiv();
      $html .= $this->renderField();
      $html .= $this->renderCloseDiv();

      return $html;

    }
  }

?>

Nos casos dos controles de caixa de texto, caixa de senha e caixa de verificação, basta implementar os métodos getOpenTag() e getCloseTag(), para que tudo o que foi definido no ancestral funcione apropriadamente. No caso do botão de enviar, foram necessárias algumas modificações nos métodos definidos na classe base, pois seu comportamento foge um pouco ao padrão (vide comentários no código).

Isso feito, podemos escrever um formulário simples de login da seguinte forma:

<?php   $usuario = new GTextBox('usuario', 'Usuário:', array('maxlength'=-->50, 'size'=>15));
  $senha = new GPassword('senha', 'Senha:', array('maxlength'=>50, 'size'=>15));
  $guardarInfo = new GCheckBox('guardar_info', 'Guardar informações?');
  $enviar = new GSubmit('enviar', 'Enviar');

  echo $usuario->render();
  echo $senha->render();
  echo $guardarInfo->render(' ');
  echo $enviar->render();

?>

O código HTML gerado, por seu turno, é o seguinte:

<div>
	<label for="usuario">Usuário:</label>

	<input id="usuario" maxlength="50" size="15" type="text" /></div>
<div>
	<label for="senha">Senha:</label>

	<input id="senha" maxlength="50" size="15" type="password" /></div>
<div>
	<label for="guardar_info">Guardar informações?</label>

	<input id="guardar_info" type="checkbox" /></div>
<div>
	<input id="enviar" type="submit" value="Enviar" /></div>
Aparência do formulário gerado

Aparência do formulário gerado

Acho que devo parabenizá-lo por ter chegado até aqui na leitura deste post. E, se entendeu o que eu quis fazer, parabéns duplos!!

Todavia, o trabalho está bem longe de terminar. Falta escrever os demais controles de formulário e, se você notou bem, o código sequer gera a tag <form>. Isso é tarefa para as próximas partes. 😉

%d blogueiros gostam disto: