Agrupando elementos com Zend_Form (Fieldset)

Olá, hoje falarei de um assunto simples e fácil de implementar em um formulário html e com Zend_Form, apesar de não parecer a primeira vista também é bem simples com o Zend_Form.

Sem nenhum rodeio ou enrolação descreverei como utilizar o Fieldset.

<?php

class Application_Form_Cadastro extends Zend_Form {

	public function __construct($options = null) {
		parent::__construct ($options);

		//campo nome
		$nome = new Zend_Form_Element_Text('nome');
		$nome->setLabel('Nome: ');
		//campo sobrenome
		$sobrenome = new Zend_Form_Element_Text('sobrenome');
		$sobrenome->setLabel('Sobrenome: ');

		//campo telefone
		$telefone = new Zend_Form_Element_Text('telefone');
		$telefone->setLabel('Telefone: ');
		//campo sobrenome
		$email = new Zend_Form_Element_Text('email');
		$email->setLabel('E-mail: ');

		//adiciona elementos ao formulario
		$this->addElements(array($nome, $sobrenome, $telefone, $email));

		//cria grupo no formulario (fieldset) e sua legenda (legend)
		$this->addDisplayGroup(array('nome','sobrenome'), 'dadosPessoais', array('legend'=>'Dados Pessoais:'));
		//caso queira pode tambem inserir decorators
		//$dPessoal = $this->getDisplayGroup('dadosPessoais');
		//$dPessoal->setDecorators(array com as especificacoes do decorator do fieldset);

		//cria grupo no formulario (fieldset) e sua legenda (legend)
		$this->addDisplayGroup(array('telefone','email'), 'dadosContato', array('legend'=>'Contatos:'));
	}
}

?>

Pronto está ai, simples e fácil!

Wildcard challenge (Desafio wildcard)

Em julho de 2011, fiz uma entrevista em uma empresa na qual foi-me solicitado a resolução de um teste, para ser feito em casa e enviado via e-mail, resolvi em 3 dias, após meu horário de trabalho e usando a análise combinatória na solução, fui aprovado, porém infelizmente cancelaram a contratação.

Na época existia apenas uma solução pronta do teste na internet para exemplo e usando binários na sua resolução.

Achei o teste interessante e com um bom nível de complexidade, pois não basta saber apenas PHP, mas também um pouco de estatística e análise combinatória, por isso estou compartilhando a minha solução.

Enunciado do teste

Escrever um algoritmo, em PHP, que tenha como entrada uma string composta por palavras separadas por pipe (|) e  a saída seja uma sequência com todos os wildcards possíveis.

Exemplo 1:

Entrada: “sao-paulo”

Saída:       *

Exemplo 2:

Entrada: “sao-paulo|restaurante”

Saída:

sao-paulo|*

*|restaurante

Exemplo 3:

Entrada: “a|b|c”

Saída:

*|b|c

a|*|c

a|b|*

a|*|*

*|b|*

*|*|c

Solução

<?php
/**
 * @author Daniel Satiro da Rocha
 * Teste - 15/07/2011
 */

/**
 * retorna combinacoes sem repeticao
 * @param array $elementos
 * @param int $s
 * @return Ambigous <multitype:, string>
 */
function combinacoes($elementos = array(), $s) {
	$combinArr = array ();
	$combin = ( int ) sprintf ( "1%0{$s}d", 0 ) - 1;
	for($cur = 0; $cur <= $combin; $cur ++) {
		$number = sprintf ( "%0{$s}d", $cur );
		$equal_digits = array ();
		for($digit = 0; $digit < $s; $digit ++) {
			if (in_array ( $number {$digit}, $elementos ) && ! in_array ( $number {$digit}, $equal_digits )) {
				$equal_digits [] = $number {$digit};
			}
		}
		if (count ( $equal_digits ) == $s) {
			$numArr = array();
			for ($i = 0; $i < strlen($number); $i++) {
				$numArr[] = $number[$i];
			}
			sort($numArr);
			$combinArr [] = implode($numArr);
		}
	}
	$combinArr = array_unique($combinArr);
	$elemComb = array();
	foreach ($combinArr as $value) {
		$numArr = array();
		for ($i = 0; $i < strlen($value); $i++) {
			$numArr[] = $value[$i];
		}
		$elemComb[] = $numArr;
	}
		return $elemComb;
}

/**
 * calcula fatorial
 * @param int $valor
 * @return number|Ambigous <unknown, number>
 */
function fatorial($valor) {
	if ($valor == 0) {
		return 1;
	}
	$result = $valor;
	for ($index = $valor-1; $index > 1; $index--) {
		$result *= $index;
	}
	return $result;
}

/**
 * Calcula quantidade de combinacoes possiveis sem repeticao
 * Onde $n é o total de elementos e $s o número de elementos escolhidos.
 * @param int $n
 * @param int $s
 * @return number
 */
function combinatoria($n, $s){
	$c = fatorial($n)/(fatorial($s)*fatorial($n-$s));
	return $c;
}
/**
 * Calcula arranjo simples
 * Onde $n é o total de elementos e $s o número de elementos escolhidos.
 * @param int $n
 * @param int $s
 * @return number
 */
function arranjos($n, $s) {
	$a = fatorial($n)/fatorial($n-$s);
	return $a;
}
/**
 * Executa calculos conforme entrada e retorna as combinacoes
 * @param string $entrada
 * @return string|string
 */
function processaSaida($entrada) {
	$entArray = explode ( '|', $entrada );
	$n = count ( $entArray );
	if ($n == 1) {
		return '*';
	} else {

		$linhas = array ();
		$html = "";
		for($s = 1; $s < $n; $s ++) {
			$combin = combinatoria ( $n, $s );
			$combArr = combinacoes ( range ( 0, $n - 1 ), $s );
			for($c = 0; $c < $combin; $c ++) {
				$aux = array ();
				$d = 0;
				for($index = 0; $index < $n; $index ++) {
					if ($combArr [$c] [$d] == $index) {
						$aux [$index] = "*";
						$d ++;
					} else {
						$aux [$index] = $entArray [$index];
					}
				}
				$linhas [] = $aux;
			}
		}

		$html = "";
		foreach ( $linhas as $key => $value ) {
			$html .= implode ( '|', $value ) . "<br />";
		}
		return $html;
	}
}
$saida = "Nenhuma saida processada!";
if($_POST){
	$entrada = trim($_POST['entrada']);
	$saida = processaSaida($entrada);
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Teste Wildcard challenge</title>
</head>
<body>
<center>
<form action="<?php echo $_SERVER['PHP_SELF'];?>" method="post">
<table border="0">
	<tr valign="top">
		<td>Entrada:</td>
		<td><input type="text" name="entrada"
			value="<?php echo $_POST['entrada'];?>" /></td>
		<td><input type="submit" value="Enviar" /></td>
	</tr>
	<tr>
		<td>Saída:</td>
		<td colspan="2"><?php echo $saida;?></td>
	</tr>
</table>
</form>
</center>
</body>
</html>

É isso aí, não sei se a empresa ainda usa esse teste para avaliar seus candidatos, mas se ainda utilizar acho que ficará mais difícil para os próximos candidatos encontrar outra solução.

Criação de Validators com Zend_Form

O ZendFramework possui muitos validators prontos, porém algumas vezes é necessário criar alguns específicos, como por exemplo a validação de CPF, que por padrão não tem no Framework. Mas e como fazer isso? Descreverei a seguir:

Primeiro vamos criar o Validate no caminho lybrary/My/Validate/Cpf.php

<?php
/**
 * @see Zend_Validate_Abstract
 */
require_once 'Zend/Validate/Abstract.php';

class My_Validate_Cpf extends Zend_Validate_Abstract {

	const NOT_CPF = 'notCpf';

	protected $_messageTemplates = array(
        self::NOT_CPF        => "'%value%' não é um CPF válido.",
    );

	protected $_numericOnly;
	/**
	 * Se o CPF for apenas numerico 12345678909 então $numbersOnly é true
	 * Se não, 123.456.789-09
	 * @param bool $numbersOnly
	 */
	public function __construct($numbersOnly = false)
	{
		$this->_setNumericOnly($numbersOnly);
	}

    /**
     * Sets $_numericOnly
     * @param bool $bool
     */
    private function _setNumericOnly($bool = false){
        $this->_numericOnly = $bool;
        if($this->_numericOnly === true){
            $this->_regexp = '/^(d){11}$/';
        }
        else{
            $this->_regexp = '/^(d){3}(.d{3}){2}-(d){2}$/';
        }
        return $this;
    }

	/**
	 * 
	 * @param   mixed $value 
	 * @return  boolean 
	 * @throws  Zend_Valid_Exception If validation of $value is impossible      
	 * @see Zend_Validate_Interface::isValid()
	 */
	public function isValid($value) {
		// checks regexp, first and second validation Digit
         if ( preg_match($this->_regexp, $value)
             && $this->_checkDigitOne($this->_removeNonDigits($value))
             && $this->_checkDigitTwo($this->_removeNonDigits($value))
                                                                                ){
            return true;
         }
         $this->_setValue($value);
         $this->_error(self::NOT_CPF);
         return false;
	}

    /**
     *
     * @param string $value
     * @return bool
     */
    private function _checkDigitOne($value)
    {
        $multipliers = array(10,9,8,7,6,5,4,3,2);
        return $this->_getDigit($value, $multipliers) == $value{9};
    }

    /**
     *
     * @param string $value
     * @return bool
     */

    private function _checkDigitTwo($value)
    {
        $multipliers = array(11,10,9,8,7,6,5,4,3,2);
        return $this->_getDigit($value, $multipliers) == $value{10};
    }

    /**
     *
     * @param string $value
     * @param array(int) $multipliers
     * @return int
     */
    private function _getDigit($value, $multipliers)
    {
        foreach($multipliers as $key => $v){
            $sum += $value{$key} * $v;
        }
        $digit = $sum % 11;
        if ($digit < 2) {
            $digit = 0;
        }else{
            $digit = 11 - $digit;
        }
        return $digit;
    }

    /**
     *
     * @param string $value
     * @return string
     */
    private function _removeNonDigits($value)
    {
        return preg_replace('/D/', '', $value);
    }
}
?>

O validate já está criado e a forma de usa-lo é bem semelhante aos demais validators, vamos então criar o formulário no caminho: application/forms/ValidaCpf.php.

<?php
class Application_Form_ValidaCpf extends Zend_Form {
	public function init()
    {
    	Zend_Dojo::enableForm($this);
    }

	public function __construct($options = null) {
		parent::__construct ($options);
		//definicoes do campo CPF
		//$cpf = new Zend_Form_Element_Text('cpf');
		$cpf = new Zend_Dojo_Form_Element_TextBox('cpf');
		$cpf->setLabel('CPF: ')
			->setTrim(true)
			->setRequired(true)
			->addFilter('Alnum')
			->addFilter('StripTags')
			->addFilter('StringTrim')
			->addValidator('NotEmpty', true, array('messages' => array('isEmpty' => 'Preencha este campo antes de prosseguir!')))
			->addPrefixPath('My_Validate', 'My/Validate/', 'validate')
			->addValidator('Cpf', true, array(true));
		//definicoes do botao de enviar
		$enviar = new Zend_Dojo_Form_Element_SubmitButton('enviar');
		$enviar->setLabel('Enviar');

		$this->addElements(array($cpf, $enviar));
	}
}
?>

Instancie o Form onde ele será utilizado e pronto, no caso aqui para exemplo será utilizado no IndexController.php na action validaCpf no caminho: application/controllers/IndexController.php

<?php 
class IndexController extends Zend_Controller_Action
{

    public function init()
    {
        Zend_Dojo::enableView($this->view);
    }

    public function indexAction()
    {
        // action body
    }

    public function validaCpfAction() {
    	$form = new Application_Form_ValidaCpf(array(
        		'action' => $this->view->baseUrl().'/index/valida-cpf',
        		'method' => 'post'));

    	$this->view->form = $form;
    	//se o formulario foi enviado via post
    	if ($this->getRequest()->isPost()) {
    		//recupera os dados enviados
            $formData = $this->getRequest()->getPost();
            //se o formulario foi preenchido corretamente
            if ($form->isValid($formData)) {
            	echo 'valido';
            }else {
            	//popula os campos com os dados preenchidos
                $form->populate($formData);
            }
    	}
    }
?>

e por fim a view no caminho: application/views/scripts/index/valida-cpf.phtml

Validação de CPF
<?php 
echo $this->form;

Variável Static com JavaScript

Variável static : função que retorna o total de checkbox selecionados.

<script type="text/javascript" language="JavaScript">
	function soma(valor, check)    {
		if ( typeof soma.total == 'undefined' ) {
			soma.total = 0;
		}
		if (check.checked == true){
			soma.total += valor;
		} else {
			soma.total -= valor;
		}
		alert(soma.total);
	}
</script>