Módulo LosBase

Hoje vou falar um pouco sobre meu primeiro módulo ZF2: LosBase.

Comecei meu primeiro projeto com ZF2 quando ainda estava no beta2 e, como acredito que a melhor forma de aprender é colocando a mão na massa, comecei a aprender o ZF2 fazendo este módulo e estudando os poucos que estavam sendo feitos.

Vou falar sobre as principais funcionalidades do módulo e darei alguns exemplos. Chamo de LosBase pois é uma base para outros módulos e todos meus projetos.

Atualmente o módulo está na sua versão 2.5.x, onde fiz uma faxina, melhorei o código, e acrescentei várias coisas.

Instalação

Para instalar, basta acrescentar no seu composer.json:

    "los/losbase": "~2.5"

e fazer o composer update.

Criação automática de módulo

Para não criar um módulo no seu projeto manualmente, existe a ferramenta da zend framework chamada zftool para criar projetos, módulos, etc. O módulo LosBase disponibiliza também um comando para criar um módulo com tudo o que é necessário para um CRUD simples:

php public/index.php los crud Cliente

Para usar este comando, você já precisa ter o LosBase instalado no seu projeto. Ele automaticamente já cria:

  • Estrutura de diretórios para o novo módulo
  • config/module.config.php
  • Rotas para o crud
  • Arquivos do autoload
  • Module.php
  • Controller (usando AbstractCrudController abaixo)
  • Entity (Usando Entity\AbstractEntity abaixo)
  • Serviço (Usando Service\AbstractEntity abaixo)
  • Arquivos da view (list.phtml, add.phtml, edit.phtml e delete.phtml)
  • Nova entrada no application.config.php para seu novo módulo

Depois de criado, basta entrar na entidade, adicionar os campos que quiser e nas views, em especial no list.phtml, para montar a tabela como quiser.

Atenção! Os arquivos de view estão estilizados para o bootstrap 3 e usando alguns view helpers do LosUi, então se você não quiser usar o LosUi, altere o necessário nas views.

Controller\AbstractCrudController

Na maioria dos meus projetos uso o doctrine e em todo projeto tenho as entidades e pelo menos alguns CRUDs simples, então resolvi criar as classes abstratas para ganhar tempo (afinal nós desenvolvedores somos preguiçosos por natureza rss) e principalmente evitar erros.

Quando preciso de um crud simples (listar, inserir, alterar e excluir) de uma entidade, meu controller fica assim:

<?php
namespace Cliente\Controller;

use LosBase\Controller\AbstractCrudController;

class CadastroController extends AbstractCrudController
{
}

Pronto! Já tenho um controller que lista os clientes cadastrados e já paginados, cria a form para inserir a trata os dados vindos da form, salva no banco, edita e exclui.

Se quisermos evitar que existam 2 clientes com o mesmo nome, podemos alterar o controller:

<?php
namespace Cliente\Controller;

use LosBase\Controller\AbstractCrudController;

class CadastroController extends AbstractCrudController
{

    protected $uniqueField = 'nome';

    protected $uniqueEntityMessage = 'Já existe um cliente com este nome!';
}

Automaticamente o controller vai checar se já existe um cliente com o nome informado antes de inserir, usando o LosBase\Validator\NoEntityExists.

Durante a alteração, ele checa se você tentar alterar o nome para o mesmo de um cliente já cadastrado usando o LosBase\Validator\NoOtherEntityExists, pois se usarmos apenas o NoEntityExists, você não conseguirá alterar nenhum cliente pois o nome dele já existe e é ele mesmo.

Para que o controller funcione, é necessário que crie 2 classes:

<?php
namespace Cliente\Service;

use LosBase\Service\AbstractEntity;

class Cliente extends AbstractEntity
{
}

Uma classe de serviço, responsável pelas operações de persistência dos dados no banco de dados.

<?php
namespace Cliente\Entity;

use Doctrine\ORM\Mapping as ORM;
use Zend\Form\Annotation as Form;
use LosBase\Entity\AbstractEntity;

/**
 * Classe Cliente
 *
 * @ORM\Entity
 * @ORM\Table(name="cliente")
 * @Form\Name("formCliente")
 * @Form\Hydrator("Zend\Stdlib\Hydrator\ObjectProperty")
 * @Form\Type("LosBase\Form\AbstractForm")
 */
class Cliente extends AbstractEntity
{

    /**
     * @ORM\Column(type="string", length=128, unique=true)
     * @Form\Filter({"name":"StringTrim"})
     * @Form\Validator({"name":"StringLength", "options":{"min":2, "max":128}})
     * @Form\Attributes({"type":"text","id":"nome"})
     * @Form\Options({"label":"Nome"})
     * @Form\Type("text")
     * @Form\Required(true)
     */
    private $nome;

    /**
     *
     * @return the $nome
     */
    public function getNome()
    {
        return $this->nome;
    }

    /**
     *
     * @param array $nome
     */
    public function setNome($nome)
    {
        $this->nome = (string) $nome;

        return $this;
    }

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

A nossa entidade em si.

Existem outras opções no controller que podem ser usadas:

protected $defaultSort = 'id';

protected $defaultOrder = 'asc';

protected $defaultPageSize = 20;

protected $paginatorRange = 15;

protected $uniqueField = null;

protected $uniqueEntityMessage = null;

protected $successAddMessage = 'Operação realizada com sucesso!';

protected $successEditMessage = 'Operação realizada com sucesso!';

protected $successDeleteMessage = 'Operação realizada com sucesso!';

protected $errorDeleteMessage = 'Erro ao excluir entidade!';

Entity\AbstractEntity

Normalmente, todas as minhas entidades possuem um id no banco de dados, data de criação e alteração. Então criei esta classe abstrata e minhas entidades a usam.

Se por algum motivo não quiser usar esta classe (por exemplo usa “extends” em outra), pode usar os mesmos campos apenas usando as traits:

  • LosBase\Entity\Db\Field\Id
  • LosBase\Entity\Db\Field\Created
  • LosBase\Entity\Db\Field\Updated

DBAL\Types\BrPriceType

Com frequência, precisamos usar preço nos sistemas, mas o formato que usamos para números (1.234,56) é diferente do padrão do banco dados (1234.56), então sempre acabamos fazendo conversões antes de salvar no banco e após ler dele.

Este tipo automatiza este processo. Basta definir a propriedade da entidade com este tipo que automaticamente ele converte nossos números para o formato do banco de dados e vice-versa:

/**
 * @ORM\Column(type="brprice")
 */
protected $preco;

Por padrão, ele usa precisão de 9 e 2 decimais mas, caso precise, basta passar novos valores como de costume no ORM:

/**
 * @ORM\Column(type="brprice",precision=7,scale=3)
 */
protected $precoCombustivel;

Apesar do nome, você pode usar este tipo para qualquer tipo numérico.

DBAL\Types\BrDateTimeType e UtcDateTimeType

Falando em data, um grande problema quando se trabalha com datas em projetos não regionais (usamos em mais de uma zona datetime) ou em servidores no exterior, é como armazenar corretamente as datas no banco de dados de forma consistente.

O BrDateTimeType automaticamente converte a data para UTC antes de salvar no banco de dados e converte de volta para “America/Sao_Paulo” (não, não sou paulista, sou carioca rss) quando é lido do banco. Isso já facilita muito o trabalho.

O UtcDateTime faz a mesma coisa, mas retorna em UTC.

Para usar, basta definir no annotation do Doctrine da entidade:

/**
 * @ORM\Column(type="brdatetime")
 */
protected $created;
/**
 * @ORM\Column(type="utcdatetime")
 */
protected $created;

Não precisa alterar nenhum configuração, o módulo já faz isso para você.

Entity\EntityManagerAwareTrait

Com frequência crio serviços ou controllers que usam o EntityManager do Doctrine e esta trait já me traz o necessário, com o setter e getter:

setEntityManager(EntityManager $em)
getEntityManager()

 ORM\Tools\Pagination\Paginator

Criei este paginator para disponibilizar a quantidade total dos registros para losPaginator do módulo LosUi.

Service\AbstractEntity

Mais uma classe visando agilizar o desenvolvimento. Todo controller de crud precisa salvar os dados no banco (inserir, alterar e excluir) e costumo separar estas operações num serviço da entidade ao invés de fazer direto no controller.

Esta classe abstrata já valida a form, salva a entidade com os dados submetidos, atualiza o campo updated se estiver disponível, etc. Também tem um método para excluir a entidade. São usados automaticamente pelo AbstractCrudController.

Resumindo, é um módulo que facilita em muito o desenvolvimento do projeto e ajuda a evitar erros de copy/paste onde muitas vezes se esquece de trocar uma variável, namespace, etc e que já traz várias classes úteis para o doctrine.

Até a próxima!

Leandro Silva

 

Deixe uma resposta

This site uses Akismet to reduce spam. Learn how your comment data is processed.