Customizando modelos no zend framework

Neste artigo iremos aprender a customizar nossos modelos, o zend framework oferece uma hierarquia de classes para abstrair cada nível do banco de dados, desde uma tabela até um resgistro dessa tabela, dando flexibilidade aos modelos.

Camadas de abstração para banco de dados no zend framework

Confira abaixo as classes que o zend framework oferece para abstrair o banco de dados.

  • Zend_Db_Table – Está classe representa uma tabela do nosso banco de dados e oferece métodos para funcionalidades básicas de manipulação, inserir, editar, remover registros, etc.
  • Zend_Db_Table_Row – Quando trabalhamos com modelos no zend framework, cada registro de uma tabela é representado por um objeto e a classe responsável por isso é a classe Zend_Db_Table_Row.
  • Zend_Db_Table_Rowset – Está classe representa um conjunto de registros, ou seja, um conjunto de objetos do tipo Zend_Db_Table_Row.

Então para mastigar de vez, a classe Zend_Db_Table representa uma tabela, Zend_Db_Table_Row representa um registro da tabela e Zend_Db_Table_Rowset representa um conjunto de registros de uma tabela.

Resgatando um objeto do tipo Zend_Db_Table_Row

A classe Zend_Db_Table oferece o método fetchRow(), que retorna um objeto do tipo Zend_Db_Table_Row representando um registro da tabela. O zend framework permite a criação de classes customizadas para representar registros de determinadas tabelas, para isso, precisamos criar uma classe extendendo Zend_Db_Table_Row_Abstract e configurar o modelo informando qual classe representará os seus registros, veremos mais detalhes dessa configuração no final do artigo.

Resgatando um objeto do tipo Zend_Db_Table_Rowset

A classe Zend_Db_Table oferece os métodos find() e fetchAll(), ambos os métodos retornam um objeto do tipo Zend_Db_Table_Rowset representando um conjunto de registros, o zend framework também permite a criação de classes customizadas para representar esses conjuntos, para isso, precisamos criar uma classe extendendo Zend_Db_Table_Rowset_Abstract e configurar o modelo informando qual classe representará os seus conjuntos de registros.

Vale lembrar que ao iterar um objeto do tipo Zend_Db_Table_Rowset, cada item da iteração será um objeto Zend_Db_Table_Row.

// fetchAll retornando um objeto Zend_Db_Table_Rowset
$rowset = $db->fetchAll();

// realizamos um loop
foreach($rowset as $row){
    // cada item é um objeto Zend_Db_Table_Row
    echo $row->name;
}

Normalmente o método find() é utilizado para resgatar um registro pelo seu ID, mas o método aceita varios ID´s, motivo pelo qual retorna um objeto do tipo Zend_Db_Table_Rowset e não Zend_Db_Table_Row, para transformar o resultado obtido pelo método find() para um objeto Zend_Db_Table_Row, utilizamos o método current(), utilizado para retornar o item atual.

// retorna um objeto do tipo Zend_Db_Table_Rowset
$rowset = $db->find(1);
// current() para acessar o item
$row = $rowset->current();
// imprimindo uma coluna
echo $row->name;

Projeto usando modelos customizados

Agora que vimos um pouco sobre as classes responsáveis pela customização dos modelos, vamos criar um projeto para trabalhar, baseado na estrutura apresentada no tópico Preparando o ambiente para desenvolvimento com Zend Framework, crie um projeto com nome de example-custom-models.

Estrutura do nosso projeto
Estrutura do nosso projeto

No 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 banco de dados
Diagrama do banco de dados

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.

Criando modelos e seus métodos

Com o banco devidamente criado, crie os arquivos das abas abaixo na pasta “application/models”.

User.php

No modelo User, realizamos a configuração e o mapeamento do modelo.

<?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, realizamos a configuração e o mapeamento do modelo.

<?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, além de realizar a configuração e o mapeamento do modelo, informamos ao modelo Order qual será sua classe Row através do atributo $_rowClass, ou seja, informamos qual classe será responsável por representar um registro do modelo Order.

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

    /**
    * The default row class
    */
    protected $_rowClass = 'Row_Order';

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

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

OrderItem.php

No modelo OrderItem, além de realizar a configuração e o mapeamento do modelo, nós informamos ao modelo OrderItem qual será sua classe Rowset através do atributo $_rowsetClass, ou seja, informamos qual classe será responsável por representar um conjunto de registros do modelo OrderItem, também criamos um método, findByOrder($order), que lista todos os produtos e quantidade de um determinado pedido, que será passado por parâmetro.

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

    /**
    * The default Rowset Class
    */
    protected $_rowsetClass = 'Rowset_OrderItem';

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

    /**
    * findByOrder
    *
    * @param <int> $order
    */
    public function findByOrder($order)
    {
        $select = $this->select()
                       ->setIntegrityCheck(false)
                       ->from(array('o'=>'order'), array())
                       ->joinInner(array('i'=>'order_item'), 'o.order_id = i.order_id',
                                   array('amount'))
                       ->joinInner(array('p'=>'product'), 'i.product_id = p.product_id')
                       ->where('o.order_id = ?', $order);

        return $this->fetchAll($select);
    }
}

Criando as classes Order Row e OrderItem Rowset

Para manter nosso projeto organizado, crie duas pastas, Row e Rowset, dentro da pasta “application/models”, agora crie um arquivo chamado Order.php na pasta Row e outro arquivo chamado OrderItem.php na pasta Rowset, confira o conteúdo desses arquivos a seguir.

Row/Order.php

<?php
class Row_Order extends Zend_Db_Table_Row_Abstract
{
    private $user = null;
    private $itens = null;

    /**
    * getUser
    *
    * @return <User Row> $user
    */
    public function getUser()
    {
        if(!$this->user)
        {
            $this->user = $this->findParentRow('User');
        }

        return $this->user;
    }

    /**
    * getItens
    *
    * @return <Itens Rowset> $itens
    */
    public function getItens()
    {
        if(!$this->itens)
        {
            // modelo OrderItem
            $orderItem = new OrderItem();

            // resgata itens com quantidade
            $this->itens = $orderItem->findByOrder($this->order_id);
        }

        return $this->itens;
    }
}

Como vimos anteriormente, para criar uma classe Row personalizada, precisamos extender a classe Zend_Db_Table_Row_Abstract, a finalidade dessa classe row é dar flexibilidade ao manipular um registro do modelo Order, adicionamos dois métodos, getUser(), que retorna o registro do usuário que realizou o pedido e getItens(), que retorna os itens do pedido.

Rowset/OrderItem.php

<?php
class Rowset_OrderItem extends Zend_Db_Table_Rowset_Abstract
{
    /**
    * getAsArray
    *
    * @return <array> $itens
    */
    public function getAsArray()
    {
        $itens = array();
        foreach($this as $item)
        {
            $itens[] = array('name'=>$item->name, 'amount'=>$item->amount);
        }
        return $itens;
    }
}

Para criar uma classe Rowset personalizada precisamos extender a classe Zend_Db_Table_Rowset_Abstract, a finalidade dessa classe rowset é manipular um conjunto de registros do modelo OrderItem, nesse caso criamos o método getAsArray(), que personaliza os resultados em um array, adicionando apenas as colunas name e amount.

Estrutura da pasta models, após a criação das classes Row e Rowset:

Estrutura da pasta models
Estrutura da pasta models

Trabalhando com os modelos customizados

Com os nossos modelos criados, nosso Row e Rowset configurado, vamos trabalhar com os modelos customizados, para isso vamos utilizar o IndexController.php e sua view, index.phtml.

IndexController.php

<?php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        // modelo
        $order = new Order();

        // resgata um pedido
        $pedido = $order->find(1)->current();
        $this->view->assign('pedido', $pedido);
    }
}

index.phtml

<h3>Detalhes do pedido</h3>
<p><?php echo $this->pedido->getUser()->name; ?> - <?php echo $this->pedido->create_date; ?></p>

<h4>Imprimindo de um Rowset acessando a classe Row_Order</h4>
<?php foreach($this->pedido->getItens() as $item): ?>
<p>Produto: <?php echo $item->name; ?> - Quantidade: <?php echo $item->amount; ?></p>
<?php endforeach; ?>

<h4>Imprimindo de um Array acessando a classe Rowset_OrderItem</h4>
<?php foreach($this->pedido->getItens()->getAsArray() as $item): ?>
<p>Produto: <?php echo $item['name']; ?> - Quantidade: <?php echo $item['amount']; ?></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 customização de modelos
Resultado do projeto usando customização de modelos

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

  • Fernando

    21/06/2011 às 18:03

    Ótimo o blog Diogo.
    Os posts são realmente relevantes e bem explicados.
    Meus parabéns!!

  • Diogo Matheus

    21/06/2011 às 18:19

    Muito Obrigado Fernando.

  • Uso avançado de modelos no zend framework

    04/07/2011 às 10:56

    […] Início « Customizando modelos no zend framework […]

  • Weber Rener Paiva

    09/08/2011 às 09:57

    Programação muito elegante! Melhorou meu conceito sobre o Zend. Parabéns!

  • Diogo Matheus

    10/08/2011 às 12:24

    Muito obrigado pelo comentário Weber.

  • Fernando Falci

    23/09/2011 às 23:40

    Obrigado, me ajudou numa dúvida simples com relação a estrurura de diretórios.

    Gostei das abas nos códigos ;)

    vai para o reader :P

  • Diogo Matheus

    04/11/2011 às 12:21

    Valeu Fernando, que bom que o artigo te ajudou.

  • Juan

    17/06/2012 às 19:16

    Boa Noite, seus artigos estão me ajudando muito, mais comecei a estudar o zend de
    uma forma diferente, e o blog onde aprendi esta off , já aprendi muita coisa sozinho,
    mas gostaria de executar um find() com inner join sera que pode me ajudar.
    Minha model
    public function find($id)
    {
    $dao = new Application_Model_DbTable_TblLivro();
    $arr = $dao->find($id)
    ->toArray();
    return $arr[0];
    Minha action:
    public function showAction() {
    $model = new Application_Model_Livro();
    $livro = $model->find($this->_getParam(‘id’));
    $this->getResponse()->setHeader(‘Content-Type’, ‘text/x-json’);
    $this->_helper->layout->disableLayout();
    $this->view->assign(“livro”, $livro);
    }
    Na view monto uma string que vai ser um json. a tabela é de livros,
    num select() eu usei da seguinte forma e deu certo:
    public function consulta($where = null, $order = null, $limit = null){
    $dao = new Application_Model_DbTable_TblLivro();
    $select = $dao->select()->from($dao)
    ->setIntegrityCheck(false)
    ->order($order)
    ->limit($limit)
    ->joinInner(array(‘a’=>’tbl_autor’), ‘tbl_livro.id_autor = a.id_autor’,array(‘autor’=>’nome’))
    ->joinInner(array(‘e’=>’tbl_editora’), ‘tbl_livro.id_editora = e.id_editora’,array(‘editora’=>’nome’))
    ->joinInner(array(‘g’=>’tbl_genero’), ‘tbl_livro.id_genero = g.id_genero’,array(‘genero’=>’nome’))
    ;
    if(!is_null($where)){
    $select->where($where);
    }
    return $dao->fetchAll($select)->toArray();
    }

    Não sei se estou trabalhando da melhor forma com o Zend.
    Desde já agradeço.

  • Marcel

    10/06/2014 às 14:43

    Boa tarde Diogo!

    Primeiro queria parabenizá-lo pelo blog, ta ajudando muita gente com certeza.

    Amigo, estou lendo seu post e implementando esse uso de modelos customizados. Bom,
    fiz apenas uma pequena alteração, adicionei Mappers, como no seu post sobre “Uso avançado de modelos no zend framework”, a diferença então, é que no DbTable coloquei o atributo protected $_rowClass = “MyRow”. Porém, está acontecendo um problema, quando
    executo um método get, ele não retorna nada, como se não estivesse populando o objeto.

    Ao fazer um foreach de um fetchAll(), por exemplo ($users as $row) { echo $row->name; }, sempre imprime NULL.

    Você pode ma ajudar?
    Grato desde já.

  • Diogo Matheus

    26/02/2015 às 15:38

    Juan,
    Obrigado pelo comentário, verifique se o artigo sobre joins no ZF soluciona essa dúvida.
    http://www.diogomatheus.com.br/blog/zend-framework/realizando-joins-no-zend-framework/

    Marcel,
    Infelizmente sem analisar o código não consigo te ajudar nesse caso, mas obrigado pelo comentário.

Deixe uma resposta

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