Caution: You are browsing the legacy symfony 1.x part of this website.

Comment paginer une liste

1.2
Symfony version
1.1
Language

Apperçu général

Symfony fournit un composant de pagination : l'objet sfPropelPager. Il est capable de séparer une liste de résultats issue d'un objet Criteria en un groupe de page à afficher, proposant les résultat par l'intermédiaire de méthodes.

L'objet sfPropelPager

La classe sfPropelPager utilise la couche d'abstraction de Propel, décrite au chapitre Modèle.

Le chapitre couvrira l'utilisation des méthodes de sfPropelPager à travers un example simple : Afficher une liste d'articles par bloc de 10. On suppose que l'objet Article a bien les méthodes d'accès getPublished(), getTitle(), getOverview() et getContent().

Si vous ne voulez que les résultats non-paginés d'une requête générée par Criteria selectionnant que les articles publiés, vous utilisez quelque chose du genre :

class articleActions extends sfActions
{
  public function executeList()
  {
    ...
    $c = new Criteria();
    $c->add(ArticlePeer::PUBLISHED, true);
    $articles = ArticlePeer::doSelect($c);
    $this->articles = $articles;
    ...
  }
}

La variable $articles, disponible dans le template, contiendrait alors un tablea contenant tous les objets résultant de la requête.

Afin d'optenir unen liste paginée, une approche quelque peu différente est nécéssaire. En effet, les résultats doivent être stockés non plus dans un tableau, mais dans un objet sfPropelPager.

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;
    ...
  }
}

Les changements interviennent après la définition du critère, étant donné que cette action :

  • créée un nouveau paginateur pour paginer des objets Articles dix par dix
  • affecte un critère au paginateur
  • initialise la page courante à la page demandée ou bien à la première
  • initialise le paginateur (éxécute la requête correspondant au critère)
  • passe le paginateur au template via la variable $pager

Le template listSuccess.php a maintenant accès à l'objet sfPropelPager. Cet objet connait la page courante et la liste de toute les pages. Il dispose de méthode d'accès au pages ainsi qu'aux objets. Voyons comment le manipuler.

Pour afficher le nombre total de résultats, utilisez la méthode getNbResults():

<?php echo $pager->getNbResults() ?> results found.<br />
Displaying results <?php echo $pager->getFirstIndice() ?> to  <?php echo $pager->getLastIndice() ?>.

Pour afficher les articles dans une page demandée, utilisez la méthode getResults()de l'objet pager afin de récupérer les objets de la page:

<?php foreach ($pager->getResults() as $article): ?>
  <?php echo link_to($article->getTitle(), 'article/read?id='.$article->getId()) ?>
  <?php echo $article->getOverview() ?>
<?php endforeach ?>

Naviguer de pages en pages

Grâce à la méthode haveToPaginate(), l'objet de pagination sait si le nombre de résultats est supérieur au maximum affichable.

Pour ajouter les liens de navigation en bas de la liste (" < > "), utilisez les méthodes de navigation getFirstPage(), getPreviousPage(), getNextPage() et getLastPage(). La page courante est disponible par getPage(). Toute ces méthodes retournent un entier : l'indice de la page demandées.

Pour pointer sur page spécifique, boucler sur la collection de liens, obtenus grâce à la méthode 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 ?>

Le rendu devrait être quelque chose du genre :

   « < 1 - 2 - 3 - 4 - 5 > »

Une fois l'article affiché, un curseur est nécéssaire afin d'autoriser la navigation directe vers un article précédent ou suivant, sans retourner à la liste paginée.

Naviguer entre les objets

Naviguer page par page dans une liste est simple, mais l'utilisateur voudrait sûrement ne pas avoir à retourner dans la liste pour naviguer objet par objet. L'attribut curseur de l'objet sfPropelPager peut garder un indice de l'objet courant.

Cela permet un parcours article par article dans le template readSuccess.php. Modifions d'abord un peu de code dans le 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 ?>

L'actin read aura besoin de savoir comment gérer le paramètre cursor.

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);
  }
}

La méthode getObjectByCursor($cursor) place le curseur à la position spécifiée et retourne l'objet à cette position précise.

Vous pouvez placer le curseur sans récupérer l'objet résultant avec la méthode setCursor($cursor). Une fois le curseur placé, vous pouvez récupérer l'objet à cette position (getCurrent()) mais égallement à sa position précédente (getPrevious()) ou suivante (getNext()).

L'action read sait donc passer au template les informations nécéssaires à une navigation article par article, en apportant toutefois quelques modifications:

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);
  }
}

note

Les méthodes getPrevious() et getNext() retournent null si aucun n'objet ne précède ou ne suit.

Voici une suggestion pour le template readSuccess.php:

<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()) ?>

Changer l'order du tri

Comme l'objet sfPropelPager est basé sur un objet Criteria, on peut facilement changer l'ordre du tri en appliquant un tri au critère avant de l'assigner à l'objet de pagination.

Par exemple, vous pouvez ajouter à l'interface de navigation le choix de la colonne de tri:

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;
    ...
  }
}

Ajoutez le code suivant au template listSuccess.php:

Sort by : <?php echo link_to('Title', 'article/list?sort=title') ?> - <?php echo link_to('Id', 'article/list?sort=Id') ?>

Modifier le nombre de résultats par page

La méthode setMaxPerPage($max) modifie le nombre de résultats affichable dans une page, sans recharger le paginateur (pas besoin de rapeller la méthode init()). Si le paramètre fourni est 0, le paginateur affichera tous les résultats dans la même page.

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;
    ...
  }
}

Vous pouvez donc ajouter au template listSuccess.php le code suivant:

Display : <?php echo link_to('10', 'article/list?maxperpage=10') ?> - <?php echo link_to('20', 'article/list?maxperpage=20') ?> results per page

Changer la méthode de sélection

Si vous avez besoin d'optimiser la performance d'une action basée sur sfPropelPager, vous aurez surement besoin d'utiliser une méthode doSelectJoinXXX() au lieu d'un simple doSelect(). Cela peut être facilement fait grâce à la méthode setPeerMethod() de l'objet sfPropelPager.

$pager->setPeerMethod('doSelectJoinUser');

Notez que le paginateur éxécute réellement la requête doSelect() à l'affichage de la page. La première requête (initié par $pager->init()) ne fait qu'un doCount(), qui est aussi personnalisable en appelant :

$pager->setPeerCountMethod('doCountJoinUser');

Stocker des informations supplémentaires dans le paginateur

Vous aurez sans doute besoin de garder un certain context dans l'objet de pagination. Pour ce besoin, la classe sfPropelPager peut gérer des paramètres de manière habituelle:

$pager->setParameter('foo', 'bar');
 
if ($pager->hasParameter('foo'))
{
  $pager->getParameter('foo');
  $pager->getParameterHolder()->removeParameter('foo');
}
 
$pager->getParameterHolder()->clearParameters();

Ces paramètres ne sont jamais utilisé directement par le paginateur.

Pour plus d'informations sur les paramètres, référez vous au Chapitre 2.