Uso avançado de modelos no zend framework

Neste artigo iremos abordar o uso avançado de modelos no zend framework, que consiste em trabalhar com um conjunto de classes, Models, Mappers e DbTables. Essas classes quando utilizadas, dividem entre si as responsabilidades de um modelo, melhorando na organização e entendimento do mesmo.

Estrutura utilizada nos tópicos anteriores

Nos tópicos anteriores foi utilizado uma classe Zend_Db_Table como responsável pelos modelos, mas esse uso por mais que funcione não é o recomendado, confira a estrutura utilizada nos tópicos anteriores:

Estrutura anterior
Estrutura anterior

Estrutura para uso avançado de modelos

O uso recomendado da classe Zend_Db_Table(DbTable) consiste na representação de uma tabela do banco de dados, manipulação de registros, mapeamento de relacionamentos e etc, para adicionar nossa lógica de negócio é recomendado a utilização de classes Mapper além do Model para definição de atributos, confira a estrutura para o uso avançado de modelos:

Estrutura avançada
Estrutura avançada

Conhecendo as classes envolvidas

Model – Classe responsável pela definição dos atributos e métodos get e set de cada atributo.

Mappers – Classe intermediária, responsável por mapear a classe de acesso ao banco de dados(DbTable) e o modelo, é na classe mapper que definimos nossas regras de negócio.

DbTables – Classe responsável pelo acesso ao banco de dados, onde mapeamos os relacionamentos das tabelas, definimos qual a tabela responsável por determinado modelo, rowClass, rowsetClass e etc.

Você define como irá utilizar seus modelos

O zend framework em sua documentação de quickstart apresenta está estrutura para o uso de modelos, mas nada impede de você adotar a maneira que achar melhor para o seu projeto, não existe um padrão para utilizar modelos no zend framework, apenas recomendações.

Uso de rowClass e rowsetClass

Para utilizar uma classe Zend_Db_Table_Row e Zend_Db_Table_Rowset, apresentadas no tópico Customizando modelos no zend framework, basta escolher o local ideal para guardar as classes e configurar na classe Zend_Db_Table(DbTable) do modelo.

Projeto usando Model, Mapper e DbTable

Agora que vimos um pouco sobre as classes envolvidas no uso avançado de 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-advanced-models.

Estrutura do nosso projeto
Estrutura do nosso projeto

No exemplo que será visto agora, iremos utilizar o banco apresentado no artigo Entendendo modelos no zend framework, confira o script do banco abaixo, para detalhes de configuração acesse o tópico.

CREATE TABLE user (
    user_id bigint(20) NOT NULL AUTO_INCREMENT,
    name varchar(50) NOT NULL,
    email varchar(100) NOT NULL,
    PRIMARY KEY(user_id)
);

Acesse o phpmyadmin, http://localhost/phpmyadmin, crie uma base de dados com o nome de “zf-models” e adicione o script acima para criar a tabela “user”. Caso você já tenha esse banco e tabela porque realizou o outro tópico também, apenas certifique-se de que a tabela está vazia.

Criando os arquivos do modelo

Para criar os arquivos do nosso modelo, iremos utilizar a estrutura apresentada no início do artigo, que consta com dois sub-diretórios na pasta “models” da nossa aplicação, DbTable e Mapper, confira novamente como deve ser nossa organização.

Estrutura avançada
Estrutura avançada

Agora vamos criar os arquivos do modelo, crie os arquivos das abas abaixo.

Model User.php

Nossa classe model User está seguindo os atributos da tabela “user” do banco de dados, mas como podemos perceber estamos personalizando um atributo, na tabela “user” temos o campo “user_id”, chave primária da tabela, na classe model para simplificar chamamos apenas de “id”. Poderiamos utilizar os métodos mágicos do php para personalizar os métodos get e set dos atributos, mas optei por deixar nosso exemplo o mais simples possível, passando apenas o conceito necessário para o uso avançado de modelos.

models/User.php

<?php
class User
{
    private $_id;
    private $_name;
    private $_email;

    public function getId(){
        return $this->_id;
    }

    public function setId($id){
        $this->_id = $id;
        return $this;
    }

    public function getName(){
        return $this->_name;
    }

    public function setName($name){
        $this->_name = $name;
        return $this;
    }

    public function getEmail(){
        return $this->_email;
    }

    public function setEmail($email){
        $this->_email = $email;
        return $this;
    }
}

Mapper User.php

A classe mapper User possui como atributo interno uma instância da classe DbTable User, e na maioria de seus métodos recebe e/ou retorna um objeto da classe model User, também podemos visualizar um mapeamento usando array no método saveOrUpdate(), no qual dizemos que o atritubo “id” da classe Model user, corresponde ao campo “user_id” do banco de dados(DbTable). No método fetchAll montamos um array de resposta, personalizando o resultado em formato de Zend_Db_Table_Rowset da classe DbTable, nesse ponto vale lembrar que cada iteração corresponde a um objeto do tipo Zend_Db_Table_Row, em caso de customização dos modelos, apresentado no tópico Customizando modelos no zend framework, poderiamos montar facilmente determinado modelo usando os métodos criados na classe row customizada em casos de dependência e referência de outros modelos.

models/Mapper/User.php

<?php
class Mapper_User
{
    private $_dbTable;

    public function saveOrUpdate(User $user)
    {
        // build array with user info
        $data = array(
            'user_id' => $user->getId(),
            'name' => $user->getName(),
            'email' => $user->getEmail()
        );

        // id == null -> insert
        if (null === ($id = $user->getId())){
            unset($data['user_id']);

            // is unique name?
            if($this->isUniqueName($user->getName())){
                $this->getDbTable()->insert($data);
            }
            else {
                throw new Exception('This name is already being used.');
            }
        }
        // id != null -> update
        else {
            $this->getDbTable()->update($data, array('user_id = ?' => $id));
        }
    }

    public function fetchAll()
    {
        $resultSet = $this->getDbTable()->fetchAll();

        // custom return
        $result = array();
        foreach($resultSet as $row){
            $user = new User();
            $user->setId($row->user_id)
                 ->setName($row->name)
                 ->setEmail($row->email);
            $result[] = $user;
        }

        return $result;
    }

    public function isUniqueName($name)
    {
        $where = $this->getDbTable()
                      ->getDefaultAdapter()
                      ->quoteInto('name = ?', $name);

        return (count($this->getDbTable()->fetchAll($where)) == 0) ? true : false;
    }

    public function getDbTable()
    {
        if (null === $this->_dbTable) {
            $this->_dbTable = new DbTable_User();
        }
        return $this->_dbTable;
    }
}

DbTable User.php

Nossa classe DbTable é bem simples, nesse caso apenas definimos qual tabela corresponde ao modelo User.

models/DbTable/User.php

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

Trabalhando com nosso modelo

Com os arquivos do modelo criado, vamos explorar seus recursos, para isso vamos utilizar o IndexController.php e sua view, index.phtml.

IndexController.php

<?php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        // model
        $diogo = new User();
        $diogo->setName('Diogo Matheus')
              ->setEmail('dm.matheus@gmail.com');

        // mapper
        $userMapper = new Mapper_User();
        try{
            // insert user
            $userMapper->saveOrUpdate($diogo);
        }
        catch(Exception $e){
            $this->view->assign('message', $e->getMessage());
        }

        // get all users
        $this->view->assign('users', $userMapper->fetchAll());
    }
}

index.phtml

<?php if($this->message): ?>
<h4><?php echo $this->message; ?></h4>
<?php endif; ?>

<h3>Lista de usuários</h3>
<?php foreach($this->users as $user): ?>
<p><?php echo $user->getName(); ?> - <?php echo $user->getEmail(); ?></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 mapper
Resultado do projeto usando mapper

Agora com o usuário “Diogo Matheus” já inserido no banco de dados, se você executar novamente nossa aplicação iremos obter uma mensagem dizendo que esse nome já está sendo utilizado.

Resultado do projeto ao tentar novamente
Resultado do projeto ao tentar novamente

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

<?php
class Mapper_User
{
private $_dbTable;public function saveOrUpdate(User $user)
{
// build array with user info
$data = array(
‘user_id’ => $user->getId(),
‘name’ => $user->getName(),
‘email’ => $user->getEmail()
);// id == null -> insert
if (null === ($id = $user->getId())){
unset($data[‘user_id’]);// is unique name?
if($this->isUniqueName($user->getName())){
$this->getDbTable()->insert($data);
}
else {
throw new Exception(‘This name is already being used.’);
}
}
// id != null -> update
else {
$this->getDbTable()->update($data, array(‘user_id = ?’ => $id));
}
}public function fetchAll()
{
$resultSet = $this->getDbTable()->fetchAll();// custom return
$result = array();
foreach($resultSet as $row){
$user = new User();
$user->setId($row->user_id)
->setName($row->name)
->setEmail($row->email);
$result[] = $user;
}return $result;
}public function isUniqueName($name){
$where = $this->getDbTable()
->getDefaultAdapter()
->quoteInto(‘name = ?’, $name);return (count($this->getDbTable()->fetchAll($where)) == 0) ? true : false;
}public function getDbTable()
{
if (null === $this->_dbTable) {
$this->_dbTable = new DbTable_User();
}
return $this->_dbTable;
}
}
  • Jean Michael

    07/07/2011 às 20:25

    Boa Tarde, Diogo,

    acompanhei suas postagem sobre modelos no zend, eu uso um mapper extendido a um modelo padrão que tem os métodos save.

    estancio dessa forma:

    $usuario = new Default_Model_Usuaio();
    $usuario->setId(‘1’); //seu eu setar o id intão ele atualiza (update) senão ele adicionar um novo (insert)
    $usuario->setNome(‘Jean Michael’);
    $usuario->setEmail(‘contato@jeanmichael.com.br’);
    $usuario->save();

    tenho muitos models e dbtables em meu projetos,
    a questão é tenho sempre que utulizar relacionamentos em meus modelos.

    porem tenho que criar um metodo que dentro do modelo usuario por exemplo fetchall(); para descrubir qual o nome do grupo que ele pertence, cujo na tabela usuarios guarda apenas a referencia do id

    exemplo:

    ///////////////////////////////

    public function fetchAll()
    {
    $resultSet = $this->getDbTable()->select()
    ->setIntegrityCheck(false)
    ->from(array(‘usuarios’ => $this->getDbTable()->getTableName()))
    ->joinInner(array(‘grupos’ => ‘usuarios_grupos’), ‘grupos.grupo_id = usuarios.grupo_id’, array(‘grupo_nome’ => ‘grupos.grupo_nome’))
    ->query()
    ->fetchAll();
    $entries = array();
    foreach ($resultSet as $row) {
    $entry = new Default_Model_Usuarios();
    $entry->setUsuarioId($row->usuario_id)
    ->setUsuarioLogin($row->usuario_login)
    ->setUsuarioSenha($row->usuario_senha)
    ->setUsuarioNome($row->usuario_nome)
    ->setUsuarioEmail($row->usuario_email)
    ->setUsuarioAtivado($row->usuario_ativado)
    ->setGrupoId($row->grupo_id)
    ->setGrupoNome($row->grupo_nome)
    ->setUsuarioCriadoPor($row->usuario_criado_por)
    ->setUsuarioCriadoEm($row->usuario_criado_em)
    ->setUsuarioModificadoEm($row->usuario_modificado_em)
    ->setMapper($this);
    $entries[] = $entry;
    }
    return $entries;
    }

    ///////////////////////////////////

    assim funciona qdo quero pegar o nome do grupo

    $usuario->getGrupoNome();

    a questão é tem alguma forma mais facil pra relacionar os modelos entre sí, pois pra cada tabela tenhos modelos, mapper e dbtable.

    muito obrigado

  • Daniel Felgar

    04/11/2011 às 09:32

    Bom dia Diogo, parabens pelo artigo me ajudou muito…
    Fiz algumas modificações que acho que se torna mais facil p\ programar. coloquei um getMapper() na classe model e tbem transformei o mapper em um Singleton…
    Alem disso fiz um programa em delphi onde voce coloca as informaçoes sobre a tabela e ele gera as classes p\ vc…

    Vc acha q usar com singleton o mapper eh uma coisa legal ?

  • Diogo Matheus

    04/11/2011 às 11:56

    Olá Jean, isso depende muito da quantidade de relacionamentos e necessidade, em alguns casos nossa estrutura fica mais organizada utilizando uma camada Row, da uma olhada nesse artigo, http://www.diogomatheus.com.br/blog/index.php/zend-framework/customizando-modelos-no-zend-framework/, mas em outros casos essa organização pode causar perda de performance, realizando diversas consultas.

    Enfim como diversas coisas depende muito da sua necessidade e preferência.

    Abraço e desculpa pela demora em responder.

  • Diogo Matheus

    04/11/2011 às 12:04

    Olá Daniel,

    Ainda não utilizei Mapper como singleton, mas em java por exemplo a camada Mapper normalmente é chamada de Manager e quando trabalhamos com injeção de dependência utilizando Spring(framework) o mesmo gerencia a instância do Manager, meio que transformando em singleton, ou seja, acho que não tem problema em usar singleton nos mappers, mas confesso que ainda não utilize, depois vou dar uma pesquisada sobre.

    Abraço e boa sorte.

  • Tadeu

    22/11/2011 às 11:49

    Caro Diogo, fiz todos os passos e quando acesso o projeto acontece o seguinte erro:

    PHP Fatal error: Class ‘User’ not found in C:\Zend\Apache2\htdocs\example-advanced-models\application\controllers\IndexController.php on line 7

    Eu criei todos os arquivos exatamente como vc esta mostrando.

    Obrigado e parabens pelo blog.

  • Diogo Matheus

    22/11/2011 às 15:30

    Tadeu,

    Nessa sequência de artigos sobre modelos optei por utilizar o autoload com setFallbackAutoloader(true), você pode ver mais sobre isso na documentação oficial: http://framework.zend.com/manual/en/learning.autoloading.usage.html, ou no artigo Entendendo modelos no zend framework: http://www.diogomatheus.com.br/blog/zend-framework/entendendo-modelos-no-zend-framework/ onde mostro como configurar, por ser mais didático, mas estou devendo um artigo de como trabalhar da maneira recomendada.

    O erro que está ocorrendo no seu caso é que ele não está conseguindo encontrar o modelo User, para trabalhar com a nomenclatura “User” você precisa utilizar setFallbackAutoloader(true), ou adaptar o exemplo para o uso recomendado, onde ficaria “Application_Model_User”, adaptando o modelo e onde o mesmo é chamado, com isso o autoload funciona seguindo o padrão.

    Abraço e boa sorte.

  • Ronald

    27/06/2012 às 18:57

    Segui o exemplo e no meu projeto não estava funcionando legal o fetchAll(), na view ele não mostrava os dados. Resolveu quendo eu inclui na minha classe do modelo o construtor e set e get da classe, conforme documentação do zend:

    public function __construct(array $options = null)
        {
            if (is_array($options)) {
                $this->setOptions($options);
            }
        }
     
        public function __set($name, $value)
        {
            $method = 'set' . $name;
            if (('mapper' == $name) || !method_exists($this, $method)) {
                throw new Exception('Invalid user property');
            }
            $this->$method($value);
        }
     
        public function __get($name)
        {
            $method = 'get' . $name;
            if (('mapper' == $name) || !method_exists($this, $method)) {
                throw new Exception('Invalid user property');
            }
            return $this->$method();
        }
    

  • Bruno

    04/04/2013 às 16:53

    Boa tarde,

    percebi a condição para verificar se o nome é unico no momento do insert, porém não tem a verificação no momento da edição. Como ficaria para verificarmos essa edição?

    Sendo que o mesmo id pode editar com o mesmo nome e diferente que não tenha no banco.

  • Robson Alexandre

    28/07/2013 às 19:50

    Diogo,

    existe algum modo de criar a Mapper através da Zf_Tools?

  • Diogo Matheus

    26/02/2015 às 15:34

    Ronald,
    Obrigado pelo comentário.

    Bruno,
    Seria necessário adaptar esse exemplo, retirar essa validação de dentro do controle de ID e adaptar para excluir um possível ID já existente no caso da edição, para ele não considerar o próprio registro como um já existente.

    Robson,
    Obrigado pelo comentário.

  • Rodrigo

    29/04/2016 às 15:39

    Olá Diogo

    Fiz um questionamento em um outro artigo seu (Entendendo modelos no zend framework) porém neste artigo que li já visualizei a resposta, hehe.

    Muito obrigado e parabéns por mais este artigo!

    Abraço,
    Rodrigo

Deixe uma resposta

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