Otimizando paginações com Zend_Paginator | Diogo Matheus
« Entendendo namespaces no PHP
Estruturas de controle no PHP »
O que tinha de errado na paginação do último artigo?
Basicamente no último artigo para criar a paginação foram resgatados todos os registros da tabela, através de um fetchAll(), dependendo do caso essa tabela poderia ter poucos registros ou milhares e o pior, pode ser que não esteja no mesmo servidor da aplicação, o que iria gerar mais lentidão para trazer todos esses registros.
Além disso, definimos o script e o tipo de paginação na action, o que fica um pouco deselegante, como diria Sandra Annenberg, no exemplo desse artigo iremos configurar no bootstrap da aplicação.
O fetchAll() não é recomendado para criar paginações, porque resgata todos os registros e consequentemente utiliza o adaptador Iterator para controlar os itens de cada página e de preferência defina script e tipo de paginação no bootstrap ou na view.
Solução: Adaptadores DbSelect e DbTableSelect
A solução básica para otimizar paginações de dados vindos do banco de dados com Zend_Paginator é utilizar os adaptadores DbSelect e DbTableSelect, a diferença entre eles é que o DbSelect retorna um array, já o DbTableSelect retorna um objeto rowset.
Para utilizar esses adaptadores, devemos passar um objeto da classe Zend_Db_Select ou Zend_Db_Table_Select como parâmetro, seja intanciando um dos adaptadores ou através do método estático factory() da classe Zend_Paginator.
Qual a vantagem desses adaptadores?
A grande vantagem é que o objeto passado como parâmetro é manipulado por esses adaptadores visando a página que será exibida, adicionando limite de registros baseado na configuração de itens por página e ignorando registros já exibidos caso seja necessário.
Mas ao fazer isso nosso resultado é limitado, se nossa paginação estiver configurada para 10 itens por página, será resgatado 10 itens apenas da tabela, logo, como iremos montar o controle de paginação? se só temos base em 10 registros. Agora entra a outra grande sacada, esses adaptadores criam uma query dinâmica apenas para calcular qual seria o total de itens afetados pela query original, ou seja, um count para calcular essa quantidade, com isso temos todos os ingredientes para uma paginação otimizada.
Confira os passos realizados pelos adaptadores:
1. Primeiro precisamos de um objeto Zend_Db_Select ou Zend_Db_Table_Select.
$users = $userModel->select()
// SELECT `user`.* FROM `user`
2. Quando o adaptador recebe esse objeto, ele verifica qual página será exibida, quantos itens por página deve buscar e manipula a query original para limitar a quantidade de registros que serão resgatados.
// Manipulating the query to retrieve only the necessary
SELECT `user`.* FROM `user` LIMIT 20, 10
3. Além disso o adaptador gera uma segunda query dinamicamente, baseada na original, para verificar quantos registros seriam afetados.
// Dynamic counter to basic queries
SELECT COUNT(1) AS zend_paginator_row_count FROM `user`
// Dynamic counter to complex queries
SELECT COUNT(1) AS zend_paginator_row_count
FROM ( SELECT `user`.* FROM `user` )
Podemos optar por uma contagem personalizada de registros afetados, para isso devemos utilizar o método setRowCount() do adaptador, onde podemos passar uma um objeto select ou um inteiro para forçar um determinado número como quantidade de registros afetados, nesse caso o adaptador não gera a query dinâmica.
Solução alternativa: Adaptador Null
Uma solução alternativa mas um pouco mais arriscada e complexa é utilizar o adaptador Null, que deixa quase tudo na mão do programador, resgatar os dados corretamente, enviar para view e iterar os dados. O adaptador fica responsável apenas por exibir o controle da paginação, para isso é necessário informar o total de registros afetados pela pesquisa na hora de instanciar o adaptador ou utilizar o método estático factory() da classe Zend_Paginator.
Dicas para melhorar a paginação
O que vimos até agora nada mais é do que o modo correto de criar uma paginação proveniente de banco de dados, que por mais simples que pareça é difícil encontrar exemplos na internet, mas não deixa de ser o uso otimizado de paginações, porque é mais do que pegar todos os dados e exibir determinados registros por páginas.
Para melhorar ainda mais nossas paginações podemos:
Utilizar cache nas paginações
Criar índices otimizados para as tabelas
Essas dicas ficarão para outro artigo, mas nada impede de você começar a pesquisar sobre o uso otimizado de índices ou de cache. O Zend_Paginator da suporte ao uso de cache de maneira simples, confira na documentação oficial.
Trabalhando com paginações otimizadas
Agora que já temos uma base de como otimizar paginações com Zend_Paginator, vamos criar uma paginação otimizada, para botar em pratica os conhecimentos deste artigo. Baseado na estrutura apresentada no tópico Preparando o ambiente para desenvolvimento com Zend Framework, crie um projeto com nome de example-advanced-paginator.
No exemplo que será visto agora, iremos utilizar uma tabela no banco de dados, será a tabela "user", com dois campos user_id e name, além disso vamos precisar de alguns registros cadastrados para listar os resultados, para facilitar essa tarefa, acesse o script de criação e inserção de dados, copie este conteúdo, acesse o phpmyadmin, http://localhost/phpmyadmin, crie uma base de dados com o nome de "zf-paginator" e adicione o conteúdo do script para criar a tabela e seus registros, caso você tenha realizado o último exemplo pode pular essa parte.
Configure a aplicação para acessar o banco de dados "zf-paginator", caso tenha dúvida, visualize o artigo Entendendo modelos no zend framework.
Criando nosso modelo
No artigo anterior criamos um modelo básico, mas dessa vez como queremos enviar um objeto da classe Zend_Db_Table_Select para ser manipulado pelo paginator, iremos criar um método.
class User extends Zend_Db_Table_Abstract
* getAll function
* @desc basic query to retrieve all the data
* @param boolean $paginate
public function getAll($paginate = false) {
->from($this->_name);
return ($paginate) ? $select : $this->fetchAll($select);
Definindo um padrão para as paginações
Nesse momento vamos definir um padrão no bootstrap da aplicação de como deverão ser as paginações, mas nada impede que isso seja alterado em casos específicos, de preferência utilizando o view helper paginationControl() quando necessário.
* Init Paginator
protected function _initPaginator()
Criando nosso controller e action
Neste exemplo iremos criar o controlador UserController.php contendo uma action, nomeada de list( listAction ), ou seja, utilizaremos a view list.phtml, o template de paginação que vamos utilizar será do tipo search, disponível na documentação do componente.
Adicione o arquivo "UserController.php" na pasta "application/controllers" com o seguinte conteúdo:
// Returns an instance of the class Zend_Db_Table_Select
$users = $userModel->getAll(true);
// $users = $userModel->getAll();
// First option to use Zend_Paginator_Adapter_DbTableSelect
$adapter = new Zend_Paginator_Adapter_DbTableSelect($users);
// $adapter->setRowCount($customCount);
// Second option to use Zend_Paginator_Adapter_DbTableSelect
// $paginator = new Zend_Paginator($users);
// Note: You cannot customize the count in this option
->setItemCountPerPage(10);
$this->view->assign('paginator', $paginator);
Agora vamos preparar nossa view, crie uma pasta nomeada de "user" em "application/views/scripts" e depois adicione o arquivo list.phtml com o seguinte conteúdo:
<?php if(sizeof($this->paginator)): ?>
<?php foreach($this->paginator as $user): ?>
<li><?php echo $this->escape($user->name); ?></li>
<li>Nenhum usuário encontrado.</li>
Só falta nosso template de paginação, adicione o arquivo pagination.phtml em "application/views/scripts" com o seguinte conteúdo:
<span>Next &gt;</span>
Estrutura final do nosso exemplo:
Ao acessar nossa action list do controller user, teremos o seguinte resultado:
Resultado Paginator, página 1
Caso seja clicado no link para página 2 ou em next, iremos obter outra lista de resultado, confira:
Resultado Paginator, página 2
Aparentemente nada mudou, mas em questão de desempenho, nossa aplicação agradece, isso pode ser calculado verificando o tempo de execução dos scripts, não esqueça de adicionar bastantes registros antes de fazer os cálculos para ver a diferença.
http://www.slideshare.net/norm2782/20090828-php-benelux-bbq-advanced-usage-of-zend-paginator
Este artigo foi escrito por Diogo Matheus em 19/12/2011 às 08:30 arquivado em Zend Framework.
19/12/2011 às 17:39
Muito interessante isso sobre o Zend_Paginator!
Gostaria de saber se você conhece algum método que possibilite ordenar o resultado dessas paginações…
20/12/2011 às 01:35
Boa garoto, mandou bem!!
mas que post elegante
06/01/2012 às 18:15
Caro Diogo, tenho trabalhado com paginações há algum tempo e percebi que o Zend não faz as coisas como deveria.
Internamente, se você fizer alguns debugs, vai perceber que no fundo ele faz a consulta na tabela inteira e apenas vai eliminando os registros desnecessários. Ao meu ver, ele não é feliz nesta implementação, o que, a grosso modo, não significa que ela não funcione bem.
Apenas é ruim trabalhar com tabelas muito grandes sem nenhum limit, claro, porque com o tempo fica praticamente inviável fazer uma listagem.
Tive de implementar tudo com Adapter Null, que te dá muita liberdade, mas nenhuma flexibilidade de cara. É preciso avaliar como deixar o método genérico pro seu sistema e estender uma classe pra isso, ou um helper, tanto faz.
Experimente fazer um debug mais profundo e conferir o que digo aqui. No mais, seu post é bem claro e explicativo. Parabéns!
07/01/2012 às 05:26
Muito legal seu comentário, ainda não parei para ver o quanto realmente otimiza comparando com outras soluções, quando eu tiver algum tempo tentarei verificar os pontos que você colocou.
Quanto ao adapter null é um ótimo caminho para quem realmente sabe o que precisa ser feito para otimizar as paginações.
Como falei no artigo, essa otimização mostrada nada mais é do que o uso verdadeiro do zend_paginator com registros resgatados do banco de dados, mas não quer dizer que é o mais otimizado possível.
07/01/2012 às 05:33
José Hélio:
Olha não creio em um método para isso, mas não acho que seja difícil realizar uma ordenação, caso seja padrão, basta ordenar pelo select antes de enviar para o adaptador, caso seja escolhido pelo usuário, basta implementar em forma de filtro, caso ele clique em uma coluna por exemplo, para organizar digamos uma listagem de notícias pelo título, basta passar isso como parâmetro e ter uma verificação na hora de montar o select de consulta.
Caso eu saiba de algo diferente sobre eu posto aqui.
Fiz a paginção com resultado que vem de um formulário, a primeira página carrega normalmente, mas quando mudo de página perde as informalções como posso manter as informações em todas as páginas.
28/08/2012 às 21:42
Que tipo de informações está perdendo? Já vi casos de locais que não estavam trabalhando com urls absolutas e acontecia da página não renderizar corretamente.
Muito bom o artigo, parabéns pelo site, e obrigado seus artigos sempre me ajudaram muito.
Sei que tem muito tempo o artigo, já temos o zend 2, mas estou em um projeto com Zend 1 e utilizei essa otimização numa listagem aqui, precisava realizar um inner join se puder me dar uma ideia ficarei muito grato.
27/01/2015 às 11:24
Prezado Diogo, seu post foi muito esclarecedor a respeito do uso do Zend_Paginator. Obrigado!
Estou prestando manutenção a uma aplicação com ZF 1.12 e ExtJS 4.1. Realmente, ajudou-me muito!
Em tempo: o colega acima disse ter realizado debugs, onde constatou que o Zend_Paginator não utilizou LIMIT e OFFSET. Realizei um debug e pude perceber que o Zend_Paginator_Adapter_DbTableSelect utiliza sim, o limit.
O que me vem a cabeça seria um bug em alguma versão anterior à 1.12, onde esta não executava o limit satisfatoriamente.