Tutorial ZF2 + Doctrine + ZfcUser + Los – Parte 2

Neste segundo post do tutorial vou falar sobre o Doctrine.

O que é Doctrine?

Praticamente todo sistema usa uma forma de armazenar os dados, principalmente banco de dados (MySql, Postgresql, Oracle, SqlServer, etc). Num projeto usando Zend Framework 2, podemos fazer nossa abstração dos dados usando o Zend\Db que é excelente, mas em todos meus projetos uso o Doctrine, em especial o Doctrine ORM.

Doctrine ORM é Object Relational Mapper, que traduzindo ao pé da letra significa, Mapeador de Objeto Relacional, que nada mais é que uma camada no nosso sistema que salva nossas entidades num banco de dados e lê de volta.

A equipe do Doctrine fez 2 módulos para facilitar o uso num projeto ZF2: DoctrineModule e DoctrineORMModule.

Configuração

No primeiro post, colocamos o doctrine-orm-module como dependência, e apenas ele é suficiente, pois o composer irá instalar os outros módulos necessários.

Vamos rever nosso config/autoload/global.php:

A configuração do doctrine está destacada. Vamos falar das principais linhas.

Aqui definimos qual banco de dados vamos usar. Está configurado para o MySql, mas se estiver usando outro, basta consultar na documentação do doctrine qual a classe correta a ser usada.

Aqui informamos as configurações de acesso ao banco. Lembro que nunca se deve colocar senhas no global.php. Toda informação sensitiva ou que se altera de acordo com a instalação, deve ficar no local.php.

É sempre bom usar cache, e com o doctrine não é diferente. Essas 3 linhas configuram o doctrine para usar o APC como cache para as principais operações.

No nosso local.php, configuramos a senha do banco de dados e, no caso do desenvolvimento, definimos o cache como array, ou seja, só usará o cache apenas naquela página. Isso é importante, pois como o cache armazena estrutura das tabelas e até as consultas ao banco, vai atrapalhar bastante o desenvolvimento.

Com tudo configurado, vamos testar o Doctrine. O Doctrine nos oferece algumas ferramentas de console:

Um dos comandos que mais uso, é o ora:schema-tool:update. Ele varre nossas entidades e compara com a estrutura do banco de dados. Se houve uma nova entidade, será criada uma nova tabela. Se você adicionou ou alterou uma propriedade à uma entidade existente, ele reflete a adição ou alteração no banco. Uso tanto este comando que nos meus projetos, crio um diretório bin com 2 scripts:

Se tudo correr bem, deve aparecer a mensagem acima. É normal que ainda não tenha nada para ser alterado, pois não criamos nossas entidades.

É importante criar a database antes de continuarmos, pois os comandos doctrine esperam que pelo menos a database esteja criada.

Entidade Cliente

Vamos começar criando um módulo chamado Cliente. Podemos criar um módulo de várias maneiras, manualmente, pela IDE (Zend Studio por exemplo), zftool ou, como vamos usar agora, o comando do LosBase:

Simples não é? E já cria todos os arquivos que vamos precisar:

Módulo ClienteVamos nos concentrar na nossa Entidade Cliente, que é o assunto desde post. Abrindo o arquivo module/Cliente/src/Cliente/Entity/Cliente.php:

Nossa entidade já está criada. Definimos o nome da tabela no banco de dados como “cliente”. Vamos adicionar 2 campos: nome e crédito:

Neste post, estou retirando os Annotations do Form do ZF2 para mais clareza na parte do Doctrine.

O campo nome é uma string de 250 caracteres enquanto que o crédito é um brprice, um tipo de dado do LosBase que converte nosso formato de números (1.234,56) para o de banco de dados (1234.56) e vice-versa.

Neste tutorial estou usando Annotations para definir os campos e a form do ZF2. Existe uma parte da comunidade que não gosta de Annotations e prefere usar código PHP. Não vou entrar no mérito de qual opção é a melhor, mas acho que no fundo é mais uma escolha pessoal.

É muito importante ressaltar que o Doctrine usa as propriedades da classe como campo do banco, mas usa o getters e setters para alimentar a entidade quando as lê do banco. Então sempre crie os gets e sets.

Vamos voltar ao nosso comando doctrine e vamos ver o que acontece:

Fácil não é mesmo? Ele já vai criar nossa tabela com o id do cliente, nome, crédito e 2 campos indicando quando aquele cliente foi criado e quando sofreu a última alteração. Mas de onde vieram os campos id, created e updated? Não está na classe Cliente. Na verdade está sim.

Como a classe Cliente estende da LosBase\Entity\AbstractEntity, o doctrine também usa os campos das classes estendidas, interfaces ou traits.

Vamos executar o mesmo comando, mas sem o –dump-sql para criar a tabela:

Pronto, nossa tabela está criada no MySql.

Mas acho que 250 caracteres para o nome do cliente muita coisa … vamos reduzir para 150:

E executar o dump-sql novamente:

E ele altera o tamanho do campo nome para varchar(150).

Um ponto importante de se notar, é que ele também mostra uma alteração no crédito mas para o mesmo campo, sem alterações. A ferramenta do doctrine possui essa característica: Se você usar um campo que você criou, ou fez uma alteração na definição da coluna, o comando sempre vai achar que foi feita uma alteração. Mas normalmente os bancos de dados são espertos o suficiente para não realizar a alteração se realmente não for necessário.

Entidade Usuário

Agora vamos criar nossa entidade usuário, que pertencerá a um Cliente e terá vários Acessos.

Vamos abrir o arquivo module/Usuario/src/Usuario/Entity/Usuario.php e adicionar os campos:

Esta classe tem várias coisas novas, em especial do ZfcUser, ZfcRbac. No post 4 vou entrar em detalhes sobre esta parte. Vamos focar na parte do ORM.

Diferente da classe Cliente, a classe Usuário possui 2 relacionamentos com outras classes: cliente e acessos.

Um Usuário pertence a um único Cliente, por isso o relacionamento é o ManyToOne. Por outro lado, um Usuário possui vários Acessos, então é um OneToMany.

No relacionamento com Cliente, definimos com qual Classe é o relacionamento (Cliente\Entity\Cliente), dizemos que se um cliente for excluído, este usuário também será (onDelete: cascade).

Todo relacionamento OneToMany (entre outros) deve ser iniciado no __construct da entidade como um novo ArrayCollection (como na lista 62).

Outro ponto importante, é que sempre que você definir um relacionamento que é um ArrayCollection, como $acessos na nossa classe Usuario, então você precisa definir 4 métodos:

  • getAcessos()
  • setAcessos($acessos)
  • addAcessos($acessos)
  • removeAcessos($acessos)

Vamos criar nossa entidade Acesso, no mesmo diretório da Usuario:

Como um Acesso pertence a um único Usuario, o relacionamento é representado pelo ManyToOne.

Vamos executar nosso dump:

Bastante coisa desta vez. Ele cria a tabela acesso e usuario já com as chaves estrangeiras ligando as 3 tabelas, incluindo os casdades e restricts. Perfeito!

Vamos realizar as alterações no banco:

Realmente o Doctrine facilita muito nossa vida com o banco de dados.

Na parte 5 do nosso tutorial, vamos entrar no sistema, criar os usuários, clientes e ver o Doctrine realmente em ação.

O Doctrine é MUITO mais do que falo aqui. Me resumi ao que vamos usar neste tutorial, mas convido a todos darem uma olhada no site do doctrine, em especial na parte ORM e DBAL.

Referência:

Parte 1: Instalação e configuração

Parte 2: Banco de dados (Doctrine)

Parte 3: Visual e Layout (LosUi)

Parte 4: Autenticação e Autorização (ZfcUser e ZfcRbac)

Parte 5: Usando o sistema

 

Até a próxima!

PHP developer since 1997, loves movies, music and dogs.

12 Comentários para: “Tutorial ZF2 + Doctrine + ZfcUser + Los – Parte 2

  1. Leandro deu esse erro:

    [Doctrine\Common\Annotations\AnnotationException]
    [Semantical Error] The annotation “@Form\Filter” in property Usuario\Entity
    \Usuario::$confirmesenha was never imported. Did you maybe forget to add a
    “use” statement for this annotation?

    orm:schema-tool:update [–complete] [–dump-sql] [–force]

    dai fui na entidade USUÁRIO e tirei essa linha:

    /**
    * @Form\Filter({“name”:”StringTrim”})
    */

    e ai sim gerou as tabelas no banco…o q devo fazer?

    P.S.: O erro q postei da parte 1 e vc me passou a solução funcionou perfeitamente ok?

  2. Olá Leandro, estou acompanhando esse tutorial. Muito bem explicado! Eu estou iniciando agora em ZF2 e toda essa parte de módulos ainda é um bicho de 7 cabeças pra mim.
    Minha dúvida é a seguinte: Por que você criou a entidade Acesso dentro do diretório de Usuário e não criou um módulo Acesso? E me corrija se minha linha de pensamento estiver errada.

    • Olá Deusmar! Obrigado por acompanhar o Blog!

      Não está errada não e sua pergunta é muito válida! Na verdade módulo em ZF2 não necessariamente é um por entidade, mas pode você ter várias entidades ou até mesmo nenhuma. Módulo é apenas uma forma de organizar o seu código.

      Eu pessoalmente crio um módulo para cada entidade quando elas possuem um papel forte na aplicação, por exemplo, possuem um CRUD próprio, como no caso do cliente e usuário. Já o acesso, é apenas um log das vezes que o usuário logou no sistema, ela não existe sem o usuário e faz parte dele, então eu costumo colocar no mesmo módulo. Tenho um projeto onde o mesmo módulo possui 9 entidades, pois são encadeadas, uma coleção.

      Mas no fundo é questão de preferência pessoal do que regra mesmo.

      Abraços,
      Leandro

      • Olá Leandro, sou novo também no ZF2 e tenho algumas dúvidas quanto aos módulos e entidades.
        Se eu tenho uma aplicação que tenha sessões distintas: Administração, Painel do Usuário e Site, eu poderia ter os seguintes módulos: admin, painel, site ?
        Mas os módulos compartilhariam algumas entidades, por exemplo, o admin pode alterar um usuário, e um usuário pode fazer o cadastro ou editar suas informações.
        Neste caso, criar um módulo para cada entidade seria o correto?
        Não sei se a pergunta fez sentido.
        Mas resumindo, eu posso separar os módulos em sessões e compartilhar as entidades entre eles? Ou isso seria uma gambiarra?

        • Olá Hudolf! Bem vindo.
          Na verdade, módulo é apenas uma forma de organizar seu código (controllers, views, entidades, etc). Você pode inclusive ter apenas o módulo Application, mas o código acaba ficando confuso, então sugiro separar, e a forma como é feita essa separação vai de cada um. Não tem problema algum um módulo acessar entidades e recursos de outro, isso é natural. No meu exemplo, o módulo Usuário acessa as entidades do Cliente para associar um o usuário a um cliente.
          A maneira de organizar por admin/usuário/site não é ruim nem errada, mas pode ficar muita coisa no admin e acabar ficando confuso. Você pode “quebrar” um pouco mais o admin, até porque no mesmo módulo você pode ter funcionalidades (actions do controller por exemplo) que atendem as 3 áreas do sistema.
          Por outro lado, em sistema muito grandes, com 100 entidades por exemplo, se formos separar cada entidade num módulo, seriam mais de 100 módulos (fora os módulos do vendor), o que pesaria nas requisições.
          Espero ter ajudado!
          Abraços,
          Leandro Silva

          • Entendi, está mais para um critério a ser definido pelo projeto em si e não uma norma a ser seguida, obviamente cabe ao programador decidir qual a melhor forma.
            Vlw, muito obrigado, estarei acompanhando esta séria, já me ajudou bastante a entender o funcionamento do zf2.
            Abraço

  3. Após criar um novo CRUD com sucesso

    php public/index.php create crud Projeto
    The module Projeto has been created

    Me deparei com o seguinte erro e não consegui identificar a causa. Alguma dica?

    Fatal error: Uncaught exception ‘Zend\Navigation\Exception\InvalidArgumentException’ with message ‘Invalid argument: Unable to determine class to instantiate’ in /var/www/app-zend/vendor/zendframework/zend-navigation/src/Page/AbstractPage.php:254 Stack trace: #0 /var/www/app-zend/vendor/zendframework/zend-navigation/src/AbstractContainer.php(114): Zend\Navigation\Page\AbstractPage::factory(Array) #1 /var/www/app-zend/vendor/zendframework/zend-navigation/src/AbstractContainer.php(165): Zend\Navigation\AbstractContainer->addPage(Array) #2 /var/www/app-zend/vendor/zendframework/zend-navigation/src/Navigation.php(35): Zend\Navigation\AbstractContainer->addPages(Array) #3 /var/www/app-zend/vendor/zendframework/zend-navigation/src/Service/AbstractNavigationFactory.php(38): Zend\Navigation\Navigation->__construct(Array) #4 [internal function]: Zend\Navigation\Service\AbstractNavigationFactory->createService(Object(Zend\ServiceManager\ServiceManager), ‘navigation’, ‘Navigation’) #5 /var/www/app-zend/vendor/zendframework/zend-serv in /var/www/app-zend/vendor/zendframework/zend-servicemanager/src/ServiceManager.php on line 943

  4. Olá, Leandro. Uso o ZF2, mas com Table Gateways. O primeiro motivo para eu não usar o ORM Doctrine foi o fato de ser “quase” obrigatório a criação de ids de auto incremento em cada tabela. Ao meu ver, isso deturpa a função de um banco de dados relacional, que deve ser criado de acordo com o fluxo de dados do negócio e com independência total de qualquer linguagem. Não é o banco que se adapta à linguagem, mas justamente o contrário. Dadas a lógica relacional comum a todos os bancos relacionais, a linguagem pode ter a camada de abstração, independente do banco usado. O Zend\Db e seus Table Gateways faz isso muito bem, não interferindo no modelo do banco. Em meus bancos de dados eu praticamente não uso id de auto incremento, a não ser em casos muito específicos. Todas as minha chaves são baseadas na análise relacional de quais seriam as candidatas e geralmente acabo usando muitas chaves compostas devido às constraints, que fazem parte do processo natural de relacionamento. Como de cara eu já torci o nariz para o Doctrine (e outros ORM), não consegui ir a fundo e ver se valia a pena mudar o comportamento “padrão” dele para que o banco mantivesse a sua independência de modelo. Por isso, gostaria de perguntar o seguinte. Você que tem experiência no uso dessa ferramenta, o que acha sobre o que expus aqui? É possível usar o Doctrine de forma produtiva com uma camada de abstração de dados sem interferir no modelo de banco de dados?

    • Ola Marcos!

      Verdade, concordo com esse aspecto do Doctrine, que acaba acontecendo com a maioria dos ORMs. Por alguns motivos como: peso adicional, alto consumo de memória quando se tem algumas entidades, etc,, acabei optando por não usar mais ORM, acabo usando o TableGateway direto. Você acaba fazendo mais coisa “na mão”, mas tem muito mais controle da aplicação e menos requerimento em termos de memória e tempo de resposta.

      Espero que tenha ajudado!
      Abraços,
      Leandro Silva

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

*