Mapeando relacionamentos nos modelos

Continuando o tópico Entendendo modelos no zend framework, que fizemos uma introdução sobre o uso de modelos, vamos falar agora sobre o mapeamento de relacionamentos das tabelas do banco de dados nos modelos baseados na classe Zend_Db_Table.

Banco de dados relacional

Um banco de dados relacional é um conjunto de tabelas relacionadas entre si gerenciadas por um SGBD (Sistema Gerenciador de Banco de Dados), que utiliza, por padrão, a linguagem SQL (Structured Query Language – linguagem de Consulta estruturada). Além de possibilitar a criação de tabelas, em um banco de dados relacional é possível criar relacionamentos entre as tabelas, o que garante a integridade dos dados que essas irão receber.

Tipos de relacionamentos de um banco de dados relacional

  • 1 para 1 – Este tipo de relacionamento se dá, de forma direta entre duas tabelas, quando a chave primária do registro de uma determinada tabela pode ser utilizada uma única vez em um dos registros da outra tabela.
  • 1 para N – Também acontece de forma direta entre duas tabelas sempre que a chave primária do registro de uma determinada tabela é utilizada várias vezes em outra tabela, sendo este, o tipo de relacionamento mais comum entre tabelas de um banco de dados relacional.
  • N para N – Esse tipo de relacionamento que acontece de forma indireta entre duas tabelas, pois para que ele possa ser concebido é necessário a geração de uma terceira tabela. Na prática o relacionamento vários para vários não existe de fato, o que existe são dois ou mais relacionamentos um para vários, que ganha o sentido de vários para vários. Ocorre sempre que surge a necessidade de se relacionar duas chaves primárias de registros de diferentes tabelas em vários registros de uma terceira tabela.

Mapeamento usando Zend_Db_Table

Para que os relacionamentos funcionem nos modelos baseados na classe Zend_Db_Table, basta informar quais modelos são dependentes e quais os modelos são referênciados, ou seja, indicamos quais modelos dependem do modelo que está sendo mapeado e quais modelos são necessários para ele.

Tipos de mapeamentos usando Zend_Db_Table

  • $_dependentTables – Responsável por mapear os modelos dependentes, sendo necessário informar o nome de cada modelo dependente.
  • $_referenceMap – Responsável pelo mapeamento dos modelos referênciados, nele informamos as colunas equivalentes e o nome do modelo necessário.

Confira mais sobre mapeamento na documentação sobre relacionamentos com Zend_Db_Table.

Projeto usando relacionamento nos modelos

Agora que vimos um pouco sobre banco de dados relacional e mapeamento usando Zend_Db_Table, vamos criar um projeto para trabalhar com relacionamentos, baseado na estrutura apresentada no tópico Preparando o ambiente para desenvolvimento com Zend Framework, crie um projeto com nome de example-relationship.

Estrutura do nosso projeto
Estrutura do nosso projeto

No exemplo que será visto agora, iremos colocar em prática os dois tipos de mapeamento para que o relacionamento entre as tabelas aconteça, confira o diagrama do nosso banco de dados:

Diagrama do banco de dados
Diagrama do banco de dados

Detalhando o diagrama:

  • Usuário cadastra vários produtos / Produto é cadastrado por um usuário
  • Usuário faz vários pedidos / Pedido é realizado por um usuário
  • Pedido possui vários itens / Item pertence a um pedido
  • Item tem um produto / Produto pode estar em vários itens

Obs: Na prática um relacionamento N para N não existe, no diagrama acima representamos o relacionamento N para N usando dois relacionamentos 1 para N, o relacionamento Order – Product é um relacionamento N para N, um produto pode estar em vários pedidos e um pedido pode ter vários produtos, para isso criamos a tabela order_item, que fica responsável por esse relacionamento, armazenando as chaves primárias das tabelas Order e Product, como vimos no diagrama, esse tipo de tabela também pode ter colunas extras, no caso adicionamos uma coluna para guardar a quantidade de cada item do pedido.

Visualize ou efetue o download do script sql, no script além dos comandos de criação das tabelas, contém comandos para inserir valores nas tabelas para que o exemplo no final do artigo funcione corretamente.

Acesse o phpmyadmin, crie o banco “zf-order” e execute o script sql acima para criar e preencher as tabelas do banco. Após criar o banco edite o arquivo application.ini com as informações do banco de dados, caso tenha dúvida, visualize o artigo Entendendo modelos no zend framework.

Mapeando os relacionamentos

Agora que conheçemos os tipos de relacionamentos, tipos de mapeamentos e o banco de dados que iremos trabalhar, vamos mapear os relacionamentos das tabelas nos modelos, para isso crie os arquivos das abas abaixo na pasta “application/models”.

User.php

No modelo User mapeamos os modelos Product e Order como dependentes, já que o usuário cadastra produto e faz pedido, logo usuário é referênciado pelos modelos Product e Order que são dependentes do modelo User.

<?php
class User extends Zend_Db_Table_Abstract
{
    /**
    * The default table name
    */
    protected $_name = 'user';

    /**
    * Dependent tables
    */
    protected $_dependentTables = array('Product', 'Order');
}

Product.php

No modelo Product além de mapear o modelo OrderItem(que representa a tabela de junção do relacionamento N para N), como dependente, informamos que o modelo Product faz referência ao modelo User, perceba que nesse tipo de mapeamento precisamos informar o nome do modelo(refTableClass), coluna de referência(refColumns) e a coluna do modelo produto que corresponde a coluna de referência(columns).

<?php
class Product extends Zend_Db_Table_Abstract
{
    /**
    * The default table name
    */
    protected $_name = 'product';

    /**
    * Dependent tables
    */
    protected $_dependentTables = array('OrderItem');

    /**
    * Reference map
    */
    protected $_referenceMap = array
    (
        array(
            'refTableClass' => 'User',
            'refColumns' => 'user_id',
            'columns' => 'user_id',
        )
    );
}

Order.php

No modelo Order é feito um mapeamento semelhante ao do produto, informando que o modelo OrderItem é dependente e o modelo User é referênciado para saber quem realizou o pedido.

<?php
class Order extends Zend_Db_Table_Abstract
{
    /**
    * The default table name
    */
    protected $_name = 'order';

    /**
    * Dependent tables
    */
    protected $_dependentTables = array('OrderItem');

    /**
    * Reference map
    */
    protected $_referenceMap = array
    (
        array(
            'refTableClass' => 'User',
            'refColumns' => 'user_id',
            'columns' => 'user_id',
        )
    );
}

OrderItem.php

Para finalizar no modelo OrderItem, mapeamos a referência aos modelos Order e Produto, que são os dois modelos que fazem o relacionamento N para N.

<?php
class OrderItem extends Zend_Db_Table_Abstract
{
    /**
    * The default table name
    */
    protected $_name = 'order_item';

    /**
    * Reference map
    */
    protected $_referenceMap = array
    (
        array(
            'refTableClass' => 'Order',
            'refColumns' => 'order_id',
            'columns' => 'order_id',
        ),
        array(
            'refTableClass' => 'Product',
            'refColumns' => 'product_id',
            'columns' => 'product_id',
        )
    );
}

Trabalhando com os relacionamentos

Com os nossos modelos criados e devidamente mapeados, vamos explorar alguns métodos que utilizam os relacionamentos e facilita o resgate dos registros, para isso vamos utilizar o IndexController.php e sua view, index.phtml.

IndexController.php

<?php
class IndexController extends Zend_Controller_Action
{

    public function indexAction()
    {
        // modelos
        $user = new User();
        $product = new Product();
        $order = new Order();

        // lista de usuários
        $users = $user->fetchAll();
        $this->view->assign('users', $users);

        // lista de produtos
        $products = $product->fetchAll();
        $this->view->assign('products', $products);

        // resgatando o usuário "Diogo Matheus"
        $diogo = $user->find(1)->current();
        // quais produtos foram cadastrados por ele?
        $diogo_products = $diogo->findDependentRowset('Product');
        $this->view->assign('diogo_products', $diogo_products);

        // resgatando o produto "Casaco"
        $casaco = $product->find(4)->current();
        // quem cadastrou esse produto?
        $casaco_user = $casaco->findParentRow('User');
        $this->view->assign('casaco_user', $casaco_user);

        // resgata um pedido, usuário que realizou e produtos que comprou
        $pedido = $order->find(1)->current();
        $pedido_user = $pedido->findParentRow('User');
        $pedido_produtos = $pedido->findManyToManyRowset('Product', 'OrderItem');
        $this->view->assign('pedido', $pedido);
        $this->view->assign('pedido_user', $pedido_user);
        $this->view->assign('pedido_produtos', $pedido_produtos);
    }

}

index.phtml

<h3>Lista de usuários</h3>
<?php foreach($this->users as $user): ?>
<p><?php echo $user->name; ?></p>
<?php endforeach; ?>

<h3>Lista de produtos</h3>
<?php foreach($this->products as $product): ?>
<p><?php echo $product->name; ?></p>
<?php endforeach; ?>

<h3>Produtos adicionados por "Diogo Matheus"</h3>
<?php foreach($this->diogo_products as $product): ?>
<p><?php echo $product->name; ?></p>
<?php endforeach; ?>

<h3>Quem cadastrou o produto "Casaco"?</h3>
<p><?php echo $this->casaco_user->name; ?></p>

<h3>Detalhando um pedido</h3>
<p>Usuário: <?php echo $this->pedido_user->name; ?>, Data do pedido: <?php echo $this->pedido->create_date; ?></p>
<?php foreach($this->pedido_produtos as $produto): ?>
<p>Produto: <?php echo $produto->name; ?></p>
<?php endforeach; ?>

Estrutura final do nosso projeto:

Estrutura final do nosso projeto
Estrutura final do nosso projeto

Resultado

Ao executar nossa aplicação iremos obter o seguinte resultado:

Resultado do projeto usando relacionamentos
Resultado do projeto usando relacionamentos

Visualizar ou efetuar download do exemplo, lembrando que no repositório desse projeto no github não consta os arquivos do framework.

E a quantidade de cada item do pedido? isso veremos em outro artigo, já que o método findManyToManyRowset() parou de retornar as colunas da tabela de junção, iremos estudar outras maneiras de realizar essa tarefa.
  • Julia Fernandes

    20/05/2011 às 13:38

    Parabéns pelos artigos, você é detalhista, e é nítido que a escrita é bem feita! Continue a escrever! Você acaba de ganhar uma leitora assídua! Vou assinar o RSS.

    Abraços, e mais uma vez parabéns.

  • Diogo Matheus

    20/05/2011 às 19:00


    Julia Fernandes:

    Muito obrigado pelo comentário Julia, espero que goste dos próximos artigos.

  • Rogerio Bene

    21/05/2011 às 12:09

    cara, simplesmente show de bola, gostei muito. Continue sempre assim!
    Assinando o feed … Parabéns!

  • Diogo Matheus

    21/05/2011 às 17:56


    Rogerio Bene:

    Obrigado Rogerio.

  • Augusto César

    21/05/2011 às 20:03

    Realmente muito bem escrito. Parabéns.
    Só uma coisa: Você poderia me explicar melhor o que torna uma tabela dependente de outra? Como sei quem é dependente de quem?
    Obrigado

  • Diogo Matheus

    21/05/2011 às 20:40


    Augusto César:

    Augusto, essa parte da dependência ocorre da seguinte forma, durante o processo de modelagem do banco de dados é definido os relacionamentos entre as tabelas, especificando o tipo do relacionamento seja ele 1 para 1, 1 para N ou N para N, vou te explicar tomando como exemplo o relacionamento 1 para N, no artigo vimos que um usuário pode registrar N(vários) produtos, mas um produto só pode ser registrado por 1 usuário, logo em todo registro da tabela de produtos existe um campo no qual faz referência a um usuário(user_id), isso basta para a modelagem do banco, já no zend framework além de mapear essa referência no modelo do product, devemos mapear que o produto é dependente no modelo user, porque faz referência a ele, possibilitando assim que a gente busque os produtos cadastrados por um usuário a partir do modelo user, $user123->findDependentRowset(‘Product’), retornando todos os produtos cadastrados por um determinado usuário, ou seja, esse mapeamento de dependência é importante para saber quais modelos dependem desse, se você não tiver esse mapeamento e olhar o modelo user, não teria como saber quais modelos dependem dele para funcionar, nem ter uma base para fazer essa busca.

    Para finalizar, se um modelo X faz referência ao modelo Y, o modelo X é dependente do modelo Y.

    Qualquer dúvida poste,
    Abraço

  • Consultas personalizadas com Zend_Db_Select

    30/05/2011 às 09:35

    […] Início « Mapeando relacionamentos nos modelos […]

  • Pablo

    04/06/2011 às 03:30

    Cara, sabe o pq desse erro?

    Fatal error: Call to undefined method Produto::findDependentRowset() in \app\controllers\ProdutoController.php on line 30

  • Diogo Matheus

    04/06/2011 às 14:09


    Pablo:

    Fala Pablo,
    Seria legal ver mais detalhes sobre o código, apenas olhando por alto só posso recomendar que você de uma conferida nos nomes/configuração dos seus modelos que estão usando zend_db_table e no mapeamento deles, o método findDependentRowset() recebe o nome do modelo no qual você busca, para isso ambos precisam estar mapeados.

    dm.matheus@gmail.com, fique a vontade para entrar em contato por email caso não consiga resolver esse problema.

    Att,
    Diogo Matheus

  • Realizando joins no zend framework

    06/06/2011 às 12:29

    […] exemplo que será visto agora, iremos utilizar o banco apresentado no artigo Mapeando relacionamentos nos modelos, para criar nossos métodos usando join, confira o diagrama do nosso banco de dados: Diagrama do […]

  • david

    09/06/2011 às 17:43

    cara parabens pelo artigo é dificil encontrar, artigos no zend tão bem detalhado.

    Att

  • Diogo Matheus

    09/06/2011 às 19:53


    david:

    Muito obrigado David.

  • Rafael Albani

    09/06/2011 às 23:27

    Artigo muito bom, tinha muita dúvida quanto a questão de mapeamento de relacionamentos com o Zend, parabéns.

  • Diogo Matheus

    10/06/2011 às 12:52


    Rafael Albani:

    Obrigado Rafael, espero que você tenha resolvido suas dúvidas.

  • Customizando modelos no zend framework

    13/06/2011 às 10:18

    […] exemplo que será visto agora, iremos utilizar o banco apresentado no artigo Mapeando relacionamentos nos modelos, para criar nossos modelos customizados, confira o diagrama do nosso banco de dados: Diagrama do […]

  • Rafael

    17/06/2011 às 18:38

    Diogo, no mapeamento N para N, você sabe como é feito a filtragem de registros de uma das tabelas do relacionamento?

    Exemplo:
    Tenho 3 tabelas:
    categoria {id_categoria}
    produto {id_produto, status}
    categoria_produto {id_categoria, id_produto}

    Gostaria de buscar somente os produtos que estejam ativos (status = 1) de uma determinada categoria.

    Busco todos produtos de uma categoria dessa forma
    $categoria->findManyToManyRowset(‘Produtos’, ‘CategoriasProdutos’);

    Será que consigo fazer a filtragem através de um dos parâmetros da função findManyToManyRowset ou através do $_referenceMap?

    Parabéns pelo blog, o conteúdo está muito bem explicado.

  • Rafael

    17/06/2011 às 19:40

    Diogo, fiz alguns testes com o 5º parâmetro da função findManyToManyRowset e consegui realizar a filtragem.

    Ficou assim:

    $where = “status = ‘1’ “;
    $select = new Zend_Db_Table_Select( new Produtos() );
    $select->where($where);

    $produtos = $categoria->findManyToManyRowset(“Produtos”, “CategoriasProdutos”, null, null, $select);

    Você conhece outra forma de fazer essa filtragem?

    Abs.

  • Francisco

    22/07/2011 às 06:03

    Saudações.

    Tentando relacionar uma tabela “Usuarios” (no banco, a tabela está com inicial maiúscula) com uma tabela Cidades (cada usuário tem um fk que faz referência à uma cidade), recebo a seguinte mensagem de erro:

    Message: No reference from table Application_Model_Cidades to table Application_Model_Usuarios

    Em um vídeo do site gtivideoaulas, o Felipe Girotti comenta “nos sistemas linux, por serem case sensitive, colocar letra minúscula na palavra Model, em “Application_Model_Usuarios”, que é a tabela que recebe o valor da outra. Tentei isso sem sucesso.

    Ficaria muitíssimo grato por qualquer ajuda quanto à isso.

  • Miguel Oliveira

    19/08/2011 às 14:58

    Boa tarde pessoal;

    Não estou conseguindo fazer a listagem das fk sem passar valor manual igual no exemplo aqui. Eu consegui fazer esse, está funcionando normal. Mas eu quero listar vários registros, sem passar manualmente. Não estou conseguindo fazer isso.

    Obrigado!!

  • Diogo Matheus

    04/11/2011 às 12:38


    Francisco:

    Olá Francisco,

    A única coisa que posso recomendar sem visualizar o código é que verifique se mapeou corretamente em ambos os lados, o modelo Application_Model_Cidades precisa estar mapeado no $_referenceMap do modelo usuário e o modelo Application_Model_Usuarios precisa estar mapeado no $_dependentTables do modelo cidade.

    Fora isso somente verificando e testando mesmo.

    Abraço e boa sorte.

  • Diogo Matheus

    04/11/2011 às 12:47


    Miguel Oliveira:

    Olá Miguel,

    Não entendi muito bem sua dúvida, um bom teste seria mapear corretamente, dar fetchAll em uma tabela que faça referência, iterar utilizando foreach e para cada iteração utilizar o método findParentRow para retornar o registro da fk.

    $results = $table->fetchAll();
    foreach($results as $row) {
    $parent = $row->findParentRow(‘modelo’);
    echo $parent->column;
    }

    Se não ajudar, fique a vontade para detalhar um pouco mais do seu problema.

    Abraço e boa sorte.

  • Diogo Matheus

    04/11/2011 às 13:05


    Rafael:

    Olá Rafael,

    De uma olhada na opção apresentada nesse link: http://stackoverflow.com/questions/1408881/findmanytomanyrowset-with-zend-db-table-select

    Não conheço outra maneira de realizar essa tarefa, qualquer coisa posto aqui.

    Abraço e boa sorte.

  • Wagner Chaves

    29/11/2011 às 19:47

    Diogo,
    Como dito na observação “E a quantidade de cada item do pedido?” vc trataria em outro artigo, vc poderia me indicar o link pq é exatamente esta a dúvida que me trouxe ao seu blog.

    Parabéns.

  • Diogo Matheus

    29/11/2011 às 22:53

    Wagner,

    eu continuei este tema nos seguintes artigos:
    http://www.diogomatheus.com.br/blog/zend-framework/realizando-joins-no-zend-framework/
    http://www.diogomatheus.com.br/blog/zend-framework/customizando-modelos-no-zend-framework/

    Neste artigo do Ralph Schindler(em inglês), ele cria uma classe para substituir o Zend_Db_Table_Row::findManyToManyRowset() adicionando a possibilidade de resgatar o registro da tabela de interseção:
    http://ralphschindler.com/2010/11/15/composite-rowsets-for-many-to-many-relationships-via-zend_db_table

    Você pode combinar com materiais de outros artigos, caso ache uma solução diferente para este caso, compartilhe aqui.

    Abraço e boa sorte

  • Vinicius

    15/06/2012 às 10:53

    Olá Diogo,

    Como você está refatorando os artigos do zend aos poucos, para que este tutorial esteja de acordo com o zend 1.11.11, sugiro apenas algumas correções:
    * mudar os nomes de todas as classes de modelos (User, Product, Order, OrderItem) para Application_Model_DbTable_User, Application_Model_DbTable_Product, Application_Model_DbTable_Order, Application_Model_DbTable_OrderItem.
    * mudar no atributo $_referenceMap, o refTableClass de ‘User’ para ‘Application_Model_DbTable_User’, e assim sucessivamente.
    * mudar os parâmetros em todas as funções acionadas, tais como: de findDependentRowSet(‘Product’) para findDependentRowSet(‘Application_Model_DbTable_Product’). E assim sucessivamente.

    Essa é uma das poucas idéias onde sugiro que você não faça um vídeo-aula, pois aí teria um trabalho redobrado para ajustar. Já que o artigo, apenas necessita alterar os textos e voilá!

    Abrs.

  • Diogo Matheus

    20/07/2012 às 01:36

    Obrigado pelas sugestões Vinicius.

  • Edilson

    17/07/2014 às 16:42

    Você sabe se existe limite para dependentTable para um modelo?

  • Diogo Matheus

    26/02/2015 às 15:49

    Edilson,
    Obrigado pelo comentário, não sei te informar, nunca tive problemas nesse sentido.

Deixe uma resposta

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