goncin@wordpress.com:~$ _

Linux, programação e toda sorte de nerdices

Arquivos de tags: métodos encadeados

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

PHP: métodos encadeados e __toString()

Para começar, considere esta classe PHP:

<?php

  class Calculadora {

    private $_resultado = 0;

    public function __construct($valorInicial) {
      $this->_resultado = $valorInicial;
    }

    public function mais($n) {
      if(is_numeric($n)) $this->_resultado += $n;
      return $this;
    }

    public function menos($n) {
      if(is_numeric($n)) $this->_resultado -= $n;
      return $this;
    }

    public function vezes($n) {
      if(is_numeric($n)) $this->_resultado *= $n;
      return $this;
    }

    public function divididoPor($n) {
      if(is_numeric($n)) $this->_resultado /= $n;
      return $this;
    }

    public function limpar() {
      $this->_resultado = 0;
      return $this;
    }

    public function igual() {
      return $this->_resultado;
    }

  }

?>

Sim, é uma classezinha boba. E sim, não tem documentação nenhuma 😛 , mas o que ela faz deve ficar bastante óbvio para quem programa em PHP. Meu objetivo aqui é ser didático com algo minimamente útil – em suma, não suporto aquelas classes Foo com métodos Bar que não fazem nada.

Pois bem. O primeiro ponto que desejo abordar nesse artigo é uma tendência em técnica de programação orientada a objetos chamada encadeamento de métodos (em inglês, method chaining ou ainda fluent interfaces). Esse assunto já foi tratado pelo companheiro Jonnas Fonini, mas não custa retomá-lo.

Vamos comparar a utilização da classe listada acima pela técnica tradicional com a utilização de métodos encadeados. Um sobrinho programando o arroz-com-feijão faria provavelmente assim:

<?php

  $calc = new Calculadora(10);
  $calc->vezes(7);
  $calc->menos(5);
  $calc->divididoPor(13);
  $resultado = $calc->igual();

  echo "O resultado de ((10 * 7) - 5) / 13 é $resultado";

?>

Utilizando métodos encadeados, tudo fica mais simples e direto. É o que um profissional faria.

<?php

  $calc = new Calculadora(10);
  echo 'O resultado de ((10 * 7) - 5) / 13 é ' .
    $calc->vezes(7)->menos(5)->divididoPor(13)->igual();

?>

Métodos encadeados tornam o código mais simples e legível. E, para que possam ser utilizados, basta que todos os métodos encadeáveis tenham como valor de retorno a própria instância da classe ($this), como demonstrado pelas linhas em destaque na listagem inicial.

Reparem que o método igual() não é encadeável, pois ele indica que a série de operações matemáticas está encerrada, e que, portanto, o objeto deve retornar o resultado. Todavia, não seria nada difícil “esquecê-lo” ao final de uma longa sequência de encadeamento, e não veríamos resultado algum.

Uma das possíveis soluções para este problema é o método mágico __toString() provido pela linguagem PHP. Em poucas palavras, este método define o que fazer quando se tenta converter um objeto em string (como, por exemplo, utilizando os construtores de linguagem echo ou print). Ao esquecermos o método igual() ao fim do encadeamento, é exatamente um objeto (o valor de retorno de divididoPor()) que passamos ao echo – e ele, por óbvio, não saberá como imprimir o objeto, já que só sabe lidar com strings (implícita ou explicitamente).

<?php

  class Calculadora {

    private $_resultado = 0;

    public function __construct($valorInicial) {
      $this->_resultado = $valorInicial;
    }

    public function mais($n) {
      if(is_numeric($n)) $this->_resultado += $n;
      return $this;
    }

    public function menos($n) {
      if(is_numeric($n)) $this->_resultado -= $n;
      return $this;
    }

    public function vezes($n) {
      if(is_numeric($n)) $this->_resultado *= $n;
      return $this;
    }

    public function divididoPor($n) {
      if(is_numeric($n)) $this->_resultado /= $n;
      return $this;
    }

    public function limpar() {
      $this->_resultado = 0;
      return $this;
    }

    public function __toString() {
      return "{$this->_resultado}";
    }

  }

?>

E isso é tudo. Ao sobrecarregarmos o método __toString(), estamos provendo, para todos os efeitos, uma representação do objeto em string, com a qual echo e print conseguem lidar. Note, contudo, que o valor de retorno do método deve ser explicitamente uma string (razão por que expandi o  resultado do cálculo, numérico, dentro de uma string) – não é possível, neste contexto, contar com a conversão implícita de tipos do PHP.

De quebra, __toString() resolve o problema do esquecimento de igual() para finalizar as operações. A bem da verdade, igual() sequer é mais necessário, tanto que foi retirado da listagem anterior. 😀

Até mais!

%d blogueiros gostam disto: