Zend Framework

Como validar CPF e CNPJ usando Zend Framework 2

Por em

Sobre o que é este tutorial

Sabemos o quão importante é validação de dados em uma aplicação, para manter as informações concisas e garantir a confiabilidade do sistema. Não basta apenas validar os dados no cliente, porque, um usuário bem informado ou avançado, pode desativar o javascript e passar os dados na requisição do modo que ele bem entender, prejudicando a aplicação. Portanto, tão importante é a validação dos dados no cliente, mais ainda no servidor.

Podemos ver em muitos sistemas por aí, de comerciais a bancários, em que há somente a validação no front-end abrindo brechas para ataques na aplicação. Aí, entraríamos em uma discussão no porque de não ter uma validação no servidor: Custos? Prazo? Desconhecimento? Não entrarei nestes detalhes, até mesmo porque não é o foco deste artigo, mas sabemos que é uma tarefa um tanto maçante estruturar uma validação no back-end e de modo ainda que possamos fazer um código padronizado e pronto para o reuso em outras partes ou outras aplicações.

O Zend Framework 2 disponibiliza um incrível sistema de validações de dados e que na minha opinião é um dos mais maduros componentes encontrados no mercado de frameworks PHP. Vou chamá-lo de Zend\Validator, referindo-se ao pacote/namespace que ele se encontra. Ele é apenas um componente do Zend Framework 2 de outros tantos e é desacoplado, ou seja, não é preciso ficar preso ao framework inteiro para se usar apenas um “pedaço” dele, além disso, é uma característica marcante do Zend Framework 2, ser desacoplado, podemos usar seus componentes separadamente sem vínculo nenhum com a estrutura do framework e isto será ainda mais enfático no Zend Framework 3.

O Zend\Validator já traz uma estrutura pré-definida para montagem de qualquer validação. Quando queremos montar um validador específico, temos que herdar da classe AbstractValidator, que obriga a implementarmos um método isValid para fazer a validação e retornar verdadeiro ou falso (true ou false), também, ele nós dá uma estrutura de mensagens de erros de validação além de administrar variáveis internas que podemos criar para auxiliar na validação.

Mostrarei a seguir como usar o Zend\Validator de modo a criar uma estrutura para validar CPF e CNPJ e também a deixar um código estruturado para o reuso.

Qual é a ideia?

A ideia é usar o Zend\Validator e criar uma estrutura de validação única para validar CPF e CNPJ, ou seja, como a principal validação do dado é um cálculo para obter os dígitos verificadores, comparar com os dados passados e a conta entre CPF e CNPJ é semelhante, construirei uma estrutura única.

O que faremos?

  1. Instalar e configurar o esqueleto do Zend Framework 2 em nossa máquina.
  2. Criar os arquivos de validação necessários.
  3. Criar uma pequena estrutura para trabalhar com a validação de CPF e CNPJ.
  4. Testar o que fizemos. 

Passos

  1. Instalar o esqueleto do Zend Framework 2 em nossa máquina (instalaremos via git).

Veja como instalar o Zend Framework 2

  1. Criar os arquivos de validação necessários.

Tenho uma implementação feita para validar CPF e CNPJ e através dela falarei do que foi implementado, de como criar validadores específicos e de como usa-lo.

Criarei um módulo chamado JS, apenas para manter o mesmo namespace da minha implementação e agilizar o uso, o módulo será criado com zftool.

./module/JS/src/JS/Validator/CgcAbstract.php

<?php

namespace JS\Validator;

use Zend\Validator\AbstractValidator;

/**
 * Classe CgcAbstract para validar tanto cpf quanto cnpj.
 * Existem no pacote as classes Cpf e Cnpj que a extendem.
 */
abstract class CgcAbstract extends AbstractValidator {

    /**
     * Tamanho Inválido
     * @var string
     */
    const SIZE = 'size';

    /**
     * Números Expandidos
     * @var string
     */
    const EXPANDED = 'expanded';

    /**
     * Dígito Verificador
     * @var string
     */
    const DIGIT = 'digit';

    /**
     * Tamanho do Campo
     * @var int
     */
    protected $size = 0;
    

    /**
     * Modelos de Mensagens
     * @var string
     */
    protected $messageTemplates = [
        self::SIZE => "'%value%' não possui tamanho esperado.",
        self::EXPANDED => "'%value%' não possui um formato aceitável.",
        self::DIGIT => "'%value%' não é um documento válido."
    ];

    /**
     * Modificadores de Dígitos
     * @var array
     */
    protected $modifiers = array();
    protected $validIfEmpty = true;

    public function __construct($options = null) {
        parent::__construct($options);
        if (array_key_exists('valid_if_empty', $options))
            $this->validIfEmpty = $options['valid_if_empty'];
    }

    /**
     * Validação Interna do Documento
     * @param string $value Dados para Validação
     * @return boolean Confirmação de Documento Válido
     */
    protected function check($value) {
        // Captura dos Modificadores
        foreach ($this->modifiers as $modifier) {
            $result = 0; // Resultado Inicial
            $size = count($modifier); // Tamanho dos Modificadores
            for ($i = 0; $i < $size; $i++) {
                $result += $value[$i] * $modifier[$i]; // Somatório
            }
            $result = $result % 11;
            $digit = ($result < 2 ? 0 : 11 - $result); // Dígito
            // Verificação
            if ($value[$size] != $digit) {
                return false;
            }
        }
        return true;
    }

    public function isValid($value) {
        if (!$this->validIfEmpty && empty($value)) {
            return true;
        }
        // Filtro de Dados
        $data = preg_replace('/[^0-9]/', '', $value);
        // Verificação de Tamanho
        if (strlen($data) != $this->size) {
            $this->error(self::SIZE, $value);
            return false;
        }
        // Verificação de Dígitos Expandidos
        if (str_repeat($data[0], $this->size) == $data) {
            $this->error(self::EXPANDED, $value);
            return false;
        }
        // Verificação de Dígitos
        if (!$this->check($data)) {
            $this->error(self::DIGIT, $value);
            return false;
        }
        // Comparações Concluídas
        return true; // Todas Verificações Executadas
    }

}

Vejam que praticamente esta classe faz toda mágica da validação. Criei esta classe para centralizar a validação de CPF e CNPJ, porque, vejam, o cálculo é idêntico o que mudará serão a sequência de números multiplicadores.

A variável $modifiers será uma coleção de dois conjuntos de arrays que serão os multiplicadores do primeiro dígito do CPF/CNPJ e do segundo dígito, já a variável $validIfEmpty será um flag pra quando atribuirmos a ela false, e o dado passado for vazio, ela não validará e retornará true e a variável $size será para registrar o tamanho do dado, no caso 11 pra CPF e 14 para CNPJ.

Para criar um validador com Zend Framework 2 precisamos herdar a classe Zend\Validator\AbstractValidator e implementar o método isValid(), e, fazer o código que tem que ser feito. Só isto já basta. Mas, o Zend\Validator tem muitas outras funcionalidades. Como no caso eu quero trabalhar com mensagens de erro de validação, eu sobreescrevi a variável $messagesTemplates, que é a variável que armazenará todas as mensagens de erros de validação da nossa classe e criei algumas constantes para facilitar o manuseio e melhorar a legibilidade do código na validação, normalmente sempre fazemos assim, com o uso de contantes. Serão três tipos de validação:

1. Olhar o tamanho do dado passado e comparar com o tamanho de $size

2. Olhar se o dado passado possui dígitos iguais (uma sequência de 0000 ou 1111, etc), não podemos permitir isto porque o cálculo dos dígitos verificadores darão certo, mas o dado é inválido.

3. E finalmente fazer o cálculo dos dígitos verificadores.

Veja que antes de tudo eu removo quaisquer caracteres que não sejam números para facilitar o manuseio da string com:

$data = preg_replace('/[^0-9]/', '', $value);

Para atribuir um erro com mensagem basta usarmos o método $this->error(), passar o índice do erro de $messagesTemplates e se necessário o dado digitado apenas para substituir o valor aonde está %value% nas mensagens, isto é muito legal! O método faz isto.

Se nada disto for incorreto o método isValid retornará true informando o dado é válido.

Agora temos que criar uma classe CPF e CNPJ para herdar do CgcAbstract e definir o $size e o $modifiers.

./module/JS/src/JS/Validator/Cpf.php

<?php

namespace JS\Validator;

class Cpf extends CgcAbstract {

    /**
     * Tamanho do Campo
     * @var int
     */
    protected $size = 11;

    /**
     * Modificadores de Dígitos
     * @var array
     */
    protected $modifiers = [
        [10, 9, 8, 7, 6, 5, 4, 3, 2],
        [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]
    ];

}

Veja como a classe ficou pequena, apenas defini o tamanho do CPF e os multiplicadores.

./module/JS/src/JS/Validator/Cnpj.php

<?php

namespace JS\Validator;

/**
 * Description of Cgc
 *
 * @author Luiz Carlos
 */
class Cnpj extends CgcAbstract {

    /**
     * Tamanho do Campo
     * @var int
     */
    protected $size = 14;

    /**
     * Modificadores de Dígitos
     * @var array
     */
    protected $modifiers = [
        [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2],
        [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
    ];

}

Veja também como a classe ficou pequena, apenas definindo o tamanho do CNPJ e os multiplicadores.

  1. Criar uma pequena estrutura para trabalhar com a validação de CPF e CNPJ

Agora criarei um formulário com um campo CPF e outro CNPJ e mandaremos os dados via POST para a mesma URL para validar os dados.

./module/Application/view/application/index/index.phtml
<form method="post" action="">
    <div>
        <label>CPF</label>
        <input type="text" name="cpf" value=""/>
        <ul>
            <?php foreach($messagesCpf as $v):?>
                <li><?php echo $v?></li>
            <?php endforeach; ?>
        </ul>
    </div>
    <div>
        <label>CNPJ</label>
        <input type="text" name="cnpj" value=""/>
        <ul>
            <?php foreach($messagesCnpj as $v):?>
                <li><?php echo $v?></li>
            <?php endforeach; ?>
        </ul>
    </div>
    <button type="submit" name="enviar">Enviar</button>
</form>

Um formulário básico com CPF e CNPJ e embaixo de cada campo uma estrutura de repetição para mostrar os erros de validação que porventura houverem.

./module/Application/src/Application/Controller/IndexController.php
public function indexAction()
{
    $messagesCpf = [];
    $messagesCnpj = [];
    if ($this->getRequest()->isPost()) {
        $dataCpf = $this->params()->fromPost('cpf');
        $dataCnpj = $this->params()->fromPost('cnpj');

        $cpfValidator = new Cpf(['valid_if_empty' => true]);
        $cnpjValidator = new Cnpj(['valid_if_empty' => true]);

        $cpfValidator->isValid($dataCpf);
        $cnpjValidator->isValid($dataCnpj);

        $messagesCpf = $cpfValidator->getMessages();
        $messagesCnpj = $cnpjValidator->getMessages();
    }
    return new ViewModel([
        'messagesCpf' => $messagesCpf,
        'messagesCnpj' => $messagesCnpj
    ]);
}

Aqui estou fazendo a validação. Primeiramente defino as variáveis que armazenarão os erros de CPF e CNPJ, depois verifico se a requisição é POST, se for, então, a validação será feita. Depois, é pegar os dados passados via POST com $this->params(), aí virá a brincadeira de fato, inicializamos os dois validadores com a restrição de validar quando o valor passado for vazio e logo em seguida realizamos a validação (lembre-se, a função isValid retorna true ou false), agora só pegar as mensagens de erro de validação que existirem. Passando as duas variáveis para o model, elas serão mostradas na view ou não, se não existir algum erro. Veja que neste caso só teremos uma mensagem de erro, porque o validador quando encontra um erro armazena a mensagem e já retorna false, mas poderiámos fazer diferente, verificamos todos as validações e acumular várias mensagens de erro para ser mostradas.

  1. Testar o que fizemos

Para testar o que fizemos usaremos o php built-in-server para executar a aplicação no nosso browser. Faça em seu terminal:

php -S localhost:9999 -t public public/index.php

Agora é só abrir seu browser e digitar localhost:9999. Já quando acessarem poderão ver o formulário com CPF e CNPJ, agora é só brincar.

Se você quiser ver o código deste tutorial acesse http://github.com/codeedu/zf2-cpf-cnpj.git

Há inúmeras maneiras de se usar o Zend\Validator, como foi demostrado ali, é somente um exemplo simples do uso de validação. Se fossemos usar realmente o Zend Framework 2 para criar um cadastro que teria um CPF ou CNPJ, aí a estrutura seria diferente. Nós usuariámos um Zend\Form, com Zend\InputFilter e Zend\Validator embutido, aí então, o formulário trataria os dados e validaria tudo de uma só vez e ainda se quiséssemos filtrar os dados poderíamos usar o Zend\Filter, mas, isto ficará para uma próxima oportunidade.

É isso aí pessoal, espero que tenham gostado. Vocês podem aproveitar o código e fazer um bom uso em suas aplicações ou simplesmente estudar como foi feita a validação. Vejam que o com Zend\Validator é possível criar validações estruturadas, com um código conciso e excelente pra reuso, e, que é possível simplesmente usa-lo em qualquer outro framework ou aplicação para cuidar das validações, mas, que o Zend Framework 2 traz inúmeros componentes testados e pensados para resolver importantes problemas do mercado comercial e do desenvolvimento.

Referências:

http://framework.zend.com/manual/2.3/en/modules/zend.validator.html

https://github.com/argentinaluiz/js_zf2_library/tree/2.1/src/JS/Validator