Como paginar uma lista
Introdução
Symfony provê um componente paginador: o objeto sfPropelPager. Ele pode separar uma lista de resultados de um objeto criteria (da classe Criteria) , e oferece métodos de acesso as páginas e resultados.
O objeto sfPropelPager
A classe sfPropelPager usa a layer de abstração propel, como descrito no capítulo Model.
Este capítulo ilustrará como usar o método sfPropelPager com um exemplo simples: mostrando uma lista de artigos de 10 em 10. Assumindo que o objeto artigo tem os métodos de acesso getPublished(), getTitle(), getOverview() e getContent().
Se você deseja usar um resultado não paginado de uma criteria mostrando apenas artigos publicados, você necessitaria:
class articleActions extends sfActions { public function executeList() { ... $c = new Criteria(); $c->add(ArticlePeer::PUBLISHED, true); $articles = ArticlePeer::doSelect($c); $this->articles = $articles; ... } }
A variável $articles, disponível no template, conteria uma matriz de todos os artigos encontrados.
Para ter uma lista paginada, você precisa de uma pequena modificação; os resultados devem ser colocados no objeto sfPropelPager instanciados em uma array:
class articleActions extends sfActions { public function executeList() { ... $c = new Criteria(); $c->add(ArticlePeer::PUBLISHED, true); $pager = new sfPropelPager('Article', 10); $pager->setCriteria($c); $pager->setPage($this->getRequestParameter('page', 1)); $pager->init(); $this->pager = $pager; ... } }
As diferenças depois da definição de criteria no action
-
cria um novo paginador para o objeto
Articlede 10 em 10 -
afetando a
criteriado paginador - ajusta a página atual à página pedida ou à primeira
-
inicializa o paginador (i.e. executa o pedido relacionado a
criteria) - passa o pager ao template através da variável do `$pager
O template listsuccess.php agora pode acessar objeto sfPropelPager. Este objeto conhece qual a página corrente e lista de todas as paginas. Tem também os métodos para alcançar páginas e objetos nas páginas. Veja como manipula-lo.
Para mostrar o numero total de resultados, use o método getNbResults():
<?php echo $pager->getNbResults() ?> resultados encontrados.<br /> Mostrando resultados <?php echo $pager->getFirstIndice() ?> a <?php echo $pager->getLastIndice() ?>.
Para mostrar os artigos, use o método getResults() do objeto paginador para recuperar objetos na pagina.
<?php foreach ($pager->getResults() as $article): ?> <?php echo link_to($article->getTitle(), 'article/read?id='.$article->getId()) ?> <?php echo $article->getOverview() ?> <?php endforeach ?>
Navegando nas páginas
O objeto paginador sabe se o numero de resultados excede o numero máximo que pode ser mostrador em uma pagina (10 neste exemplo), obrigado ao método haveToPaginate().
Para adicionar o link de navegador de paginas abaixo da lista (« < > »), use os método getFirstPage(), getPreviousPage(), getNextPage() e getLastPage(). A página atual é dada pelo getPage(). Todos estes métodos retornam um inteiro: o rank da página pedida.
Para apontar para uma página específica, faça um laço na coleção de links obtidos com a chamada do método getLinks():
<?php if ($pager->haveToPaginate()): ?> <?php echo link_to('«', 'article/list?page='.$pager->getFirstPage()) ?> <?php echo link_to('<', 'article/list?page='.$pager->getPreviousPage()) ?> <?php $links = $pager->getLinks(); foreach ($links as $page): ?> <?php echo ($page == $pager->getPage()) ? $page : link_to($page, 'article/list?page='.$page) ?> <?php if ($page != $pager->getCurrentMaxLink()): ?> - <?php endif ?> <?php endforeach ?> <?php echo link_to('>', 'article/list?page='.$pager->getNextPage()) ?> <?php echo link_to('»', 'article/list?page='.$pager->getLastPage()) ?> <?php endif ?>
Isto deve renderizar algo como:
[<<](#) [<](#) [1](#) - 2 - [3](#) - [4](#) - [5](#) [>](#) [>>](#)Uma vez que o artigo indicado, para permitir uma navegação direta ao artigo precedente ou seguinte sem ir para trás à lista paginada, você necessitará um cursor.
Dica: O código acima é automatizado pelo plugin
sfPagerNavigation. Consulte sua pagina de descrição para mais informação em instalação e uso.
Navegando através dos objetos
Navegar página a página com a lista é fácil, mas os usuários não podem querer ir para trás à lista navigate o objeto pelo objeto. O cursor atribui ao objeto sfPropelPager pode prender o offset do objeto atual.
Isto permitira uma navegação artigo por artigo template readsuccess.php. Primeiro, vamos modificar um pedaço do código do template listSuccess.php:
<?php $cursor = $pager->getFirstIndice(); foreach ($pager->getResults() as $article): ?> <?php echo link_to($article->getTitle(), 'article/read?cursor='.$cursor) ?> <?php echo $article->getOverview() ?> <?php ++$cursor; endforeach ?>
O action read will need to know how to handle a cursor parameter:
class articleActions extends sfActions { public function executeRead() { ... if ($this->getRequestParameter('cursor')) { $article = $pager->getObjectByCursor($this->getRequestParameter('cursor')); } else if ($this->getRequestParameter('id')) { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); } // Error $this->forward404Unless($article); } }
O método getObjectByCursor($cursor) seta o cursor a uma posição especifica e retorna o objeto a melhor posição.
Você pode setar o cursor sem iniciar o objeto de resultado com o método setCursor($cursor). E uma vez que o cursor é ajustado, você pode pegar o objeto atual nesta posição (getCurrent()) mas também na precedente (getPrevious()) e na seguinte (getNext()).
Isto significa que a action read pode passar ao template a informação necessária para uma navegação artigo-por-artigo com algumas modificações:
class articleActions extends sfActions { public function executeRead() { ... if ($this->getRequestParameter('cursor')) { $pager->setCursor($this->getRequestParameter('cursor')); $previous_article = $pager->getPrevious(); $article = $pager->getCurrent(); $next_article = $pager->getNext(); } else if ($this->getRequestParameter('id')) { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); } // Error $this->forward404Unless($article); } }
Nota: Os métodos
getPrevious()egetNext()retornamnullse se não houver nenhum objeto precedente ou seguinte.
O template readSuccess.php se parece com:
<h1><?php echo $article->getTitle() ?></h1> <p class="overview"><?php echo $article->getOverview() ?></p> <div class="content"> <?php echo $article->getContent() ?> </div> < <?php echo link_to_if($previous_article, $previous_article->getTitle(), 'article/read?id='.$previous_article->getId()) ?> - > <?php echo link_to_if($next_article, $next_article->getTitle(), 'article/read?id='.$next_article->getId()) ?>
Trocando a ordem
Como o objeto sfPropelPager confia em um objeto Criteria, mudar a ordem do paginador, é simplesmente adicionar um critério de ordenação à criteria, antes de atribuí-lo ao objeto do paginador.
Por exemplo, você pode escolher a coluna de ordenação em relação a lista:
class articleActions extends sfActions { public function executeList() { ... $c = new Criteria(); $c->add(ArticlePeer::PUBLISHED, true); if ($this->getRequestParameter('sort')) { $c->addAscendingOrderByColumn(ArticlePeer::translateFieldName($this->getRequestParameter('sort'), BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME)); } else { // sorted by date by default $c->addAscendingOrderByColumn(ArticlePeer::UPDATED_AT); } $pager = new sfPropelPager('Article', 10); $pager->setCriteria($c); $pager->init(); $this->pager = $pager; ... } }
Adicione o seguinte ao template listSuccess.php:
Ordenar por : <?php echo link_to('Title', 'article/list?sort=title') ?> - <?php echo link_to('Id', 'article/list?sort=Id' ?>
Mudando o numero de página do resultado por página
O metodo setMaxPerPage($max) muda o numero de resultado mostrado por página, sem a necessidade de repocessar a página (não precisa chamar init() novamente). Se você passar o valor 0 como parâmetro, o paginador mostrará todos os resultados em uma única página.
class articleActions extends sfActions { public function executeList() { ... $c = new Criteria(); $c->add(ArticlePeer::PUBLISHED, true); $pager = new sfPropelPager('Article', 10); $pager->setCriteria($c); if ($this->getRequestParameter('maxperpage')) { $pager->setMaxPerPage($this->getRequestParameter('maxperpage')); } $pager->init(); $this->pager = $pager; ... } }
Assim você pode adicionar o seguinte ao template listSuccess.php:
Mostrar : <?php echo link_to('10', 'article/list?maxperpage=10' ?> - <?php echo link_to('20', 'article/list?maxperpage=20' ?> resultados por paginas
Mudando o método de seleção
Se você necessitar otimizar o desempenho de uma ação que confia em um sfPropelPager, você pôde querer forçar o paginador a usar um doSelectJoinXXX() em vez de um simples doSelect(). Isto é conseguido fàcilmente pelo método do setPeerMethod() do objeto sfPropelPager:
$pager->setPeerMethod('doSelectJoinUser');
Note que o pager processa realmente a pergunta do doSelect() ao mostrar uma página. A primeira pergunta (provocada pelo $pager->init()) fá-lo somente um doCount, e pode também customizar este método chamando:
$pager->setPeerCountMethod('doCountJoinUser');
Armazenando a informação adicional no pager
Você pode necessitar às vezes manter um determinado contexto em um objeto do pager. Isso é porque a classe do sfPropelPager pode segurar parâmetros na maneira usual
$pager->setParameter('foo', 'bar'); if ($pager->hasParameter('foo')) { $pager->getParameter('foo'); $pager->getParameterHolder()->removeParameter('foo'); } $pager->getParameterHolder()->clearParameters();
Estes parâmetros são usados nunca diretamente pelo pager.
Para aprender mais sobre parametros customizados, leia o Capítulo 2.