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 Article de 10 em 10
  • afetando a criteria do 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('&laquo;', 'article/list?page='.$pager->getFirstPage()) ?>
      <?php echo link_to('&lt;', '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('&gt;', 'article/list?page='.$pager->getNextPage()) ?>
      <?php echo link_to('&raquo;', '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() e getNext() retornam null se 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>
    &lt; <?php echo link_to_if($previous_article, $previous_article->getTitle(), 'article/read?id='.$previous_article->getId()) ?>
    -
    &gt; <?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.