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

Anleitung zum Erstellen von blätterbaren Listen

1.2
Symfony version
1.1
Language

Überblick

Symfony beinhaltet ein Pager Element: das sfPropelPager Objekt. Es teilt eine Liste von Ergebnissen eines Crtiteria Objektes (der Criteria Klasse) in eine Sammlung von Seiten zur Anzeige auf. Ausserdem bietet es Zugriffsmethoden auf Seiten und Ergebnissobjekte.

Das sfPropelPager Objekt

Die sfPropelPager Klasse benutzt die Propel Abstraktionsschicht, wie im Kapitel Modell beschrieben.

Diese Anleitung zeigt die Nutzung der sfPropelPager Methoden anhand eines einfachen Beispiels: Eine Liste von Artikeln mit je 10 Einträgen je Seite. Angenommen das Article Objekt hat folgende Zugriffsmethoden: getPublished(), getTitle(), getOverview() und getContent()

Um eine durchgängige Liste, nur der veröffentlichten Artikel, mittels einer Abfrage über ein 'criteria' Ausdruck zu bekommen, würde man folgendes benutzen:

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

Die Variable $articles, welche vom Template angesprochen werden kann, beinhaltet ein Array von Article Objekten welche zur Abfrage passen.

Um eine blätterbare Liste zu erhalten, muss man eine etwas andere Vorgehensweise wählen. Die Ergebnisse werden in ein sfPropelPager Objekt gepackt, anstatt in ein 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;
    ...
  }
}

Der Unterschied beginnt nach der Definition der Abfrage (criteria), da die Aktion:

  • einen neuen Pager erzeugt, um die Article zehnstückweise zu blättern
  • die Abfrage (criteria) dem Pager zuweist
  • die aktuelle Seite auf die angefragte oder auf die erste Seite setzt
  • den Pager initialisiert (d.h. die Abfrage im criteria durchführt)
  • den Pager unter der $pager Variable für das Template verfügbar macht

Im Template listSuccess.php kann nun auf das sfPropelPager Objekt zugegriffen werden. Das Objekt kennt die aktuelle Seitennummer und eine Liste aller Seiten. Es hat Methoden um auf die Seiten und auf die in den den Seiten enthaltenen Objekte zuzugreifen. Schauen wir mal wie man dies macht.

Um die Anzahl aller Ergebnisse anzuzeigen benutzt man die getNbResults() Methode:

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

Um die Artikel der angeforderten Seite anzuzeigen, benutzt man die getResults() Methode des pager Objektes und erhält damit die Objekte der Seite.

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

Seitenübergreifende Navigation

Das Pager Objekt weis, ob die maximale Anzahl an Ergebnissen welche auf eine Seite passen (10 in diesem Beispiel) überschritten wird. Dies zeigt es in der haveToPaginate() Methode.

Um die Seitennavigations-Links am Ende der Liste anzuzeigen, benutzt man die Navigationsmethoden getFirstPage(), getPreviousPage(), getNextPage() and getLastPage(). Die aktuelle Seite bekommt man mit getPage(). Alle Methoden geben einen Integer-Wert zurück, den Rang der angefragten Seite.

Um auf eine bestimmte Seite zu zeigen, durchläuft man die Collection von Links, welche man durch den Aufruf der getLinks() Methode:

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

Dies führt zu einer Ausgabe, ähnlich der folgenden:

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

Um eine unmittelbare Navigation zum vorhergehenden oder nachfolgenden Artikel zu erhalten, ohne zurück zur blätterbaren Liste zu gehen, brauch man einen Cursor.

Navigation zwischen den Objekten

Seitenweise Navigation der Liste ist einfach, aber möglicherweise möchte der Benutzer nicht zurück zur Liste gehen um Objekt für Objekt durch zu gehen. Die cursor Eigenschaft des sfPropelPager kann den Offset des aktuellen Objektes aufnehmen.

Dies erlaubt eine Artikelweise Navigation im readSuccess.php Template. Zuerst wird der Code des listSuccess.php Templates etwas angepasst:

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

Die read Aktion muss wissen wie sie mit einem cursor Parameter umgehen muss:

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

Die getObjectByCursor($cursor) Methode setzt den Cursor an eine angegebene Position und gibt das Objekt an genau dieser Position zurück.

Man kann den Cursor mit der setCursor($cursor) Methode auch setzen, ohne das entsprechende Objekt zurück zu geben. Ist der Cursor erst mal gesetzt, kann man das Objekt an der akutellen Position (getCurrent()) holen, eben so wie das vorhergehende (getPrevious()) und das nachfolgende (getNext()).

Das bedeutet, dass die read Aktion mit geringen Anpassungen, die benötigten Informationen für eine Artikelweise Navigation an das Template übergeben kann.

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

Die getPrevious() und getNext() Methoden geben null zurück, wenn es kein vorhergehendes oder nachfolgendes Objekt gibt.

Das readSuccess.php Template kann wie folgt aussehen:

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

Ändern der Sortierreihenfolge

Da das sfPropelPager Objekt von einem Criteria Objekt abhängt, wird das Ändern der Sortierreihenfolge einfach. Es erfolgt durch das Hinzufügen eines Sortierkriterums zum Criteria Objekt bevor dieses dem Pager Objekt zugewiesen wird.

Zum Beispiel kann man die Auswahl der zu sortierenden Spalte zum Navigationsinterface hinzufügen:

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

Folgendes wird zum listSuccess.php Template hinzugefügt:

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

Ändern der Anzahl von Elementen je Seite

Die setMaxPerPage($max) Methode ändert die Anzahl der Elemente die auf einer Seite angezeigt werden, ohne dass der Pager neu initialisiert werden muss (kein erneuter Aufruf von init() notwendig). Wenn man den Wert 0 als Parameter übergibt, zeigt der Pager alle Elemente auf einer einzelnen Seite an.

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

Nun kann man folgendes zum listSuccess.php Template hinzufügen:

Anzeigen : <?php echo link_to('10', 'article/list?maxperpage=10') ?> - <?php echo link_to('20', 'article/list?maxperpage=20') ?> Elemente je Seite

Ändern der Select Methode

Wenn man die Geschwindigkeit einer Action optimieren muss, welche auf einem sfPropelPager beruht, dann kann es sein, dass man den Pager dazu bewegen will einen doSelectJoinXXX() statt einem einfachen doSelect(). Dies wird einfach durch die setPeerMethod() des sfPropelPager Objektes erreicht:

$pager->setPeerMethod('doSelectJoinUser');

Zu beachten ist, dass der Pager die doSelect() Abfrage dann durchführt, wenn eine Seite angezeigt wird. Die erste Abfrage (ausgelöst durch $pager->init()) macht lediglich einen doCount, und man kann auch diese Methode anpassen durch Aufruf von:

$pager->setPeerCountMethod('doCountJoinUser');

Weitere Informationen im Pager speichern

Manchmal ist es notwendig bestimmte Context Informationen in einem Pager Objekt zu speichern. Aus diesem Grund behandelt die sfPropelPager Klasse Parameter auf die übliche Weise:

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

Diese Parameter werden nie direkt durch den Pager benutzt.

Mehr zu Benutzerparametern erfährt man im Kapitel 2.