Seitenweises Blättern mit PHP - Klasse

Wer regelmäßig programmiert, wird sicher schon einmal über das Problem gestolpert sein, dass er eine Unmenge an Datensätzen hat, welche auf mehrere Seiten aufgeteilt werden müssen.
Seitenweises Navigieren, das Seite für Seite Durchblättern von Datensätzen, wird an vielerlei Stellen benötigt.
Ich habe mir dazu immer wieder aufs Neue in meine Applikationen irgendwelche Hilfskonstrukte gebastelt, bis ich irgendwann gemerkt habe, dass ich eigentlich ständig gezwungen bin, irgendwelche Datensätze auf mehrere Seiten aufzuteilen.
Nachdem diese Erkenntnis sich in meinem Kopf durchgesetzt hatte, habe ich mich doch irgendwann dazu entschlossen, mir eigens für das seitenweise Durchblättern eine kleine Klasse zu schreiben, die ich jederzeit überall wieder verwenden kann.

Die nachfolgende Klasse erstellt ganz einfach aus der Zahl der vorhandenen Datensätze eine komplette Navigation zum Blättern in Datensätzen.
Damit bei sehr vielen Seiten nicht für jede Seite ein Navigationspunkt erstellt wird, gibt es in der Klasse den Wert $_maxNeighbours.
$_maxNeighbours = 2; bedeutet, das jeweils vor und nach der aktuellen Seite auf der wir uns gerade befinden, nur 2 weitere, direkt benachbarte Seiten angezeigt werden.
Die Navigation durch sehr viele Seiten könnte also etwa wie folgt aussehen:

Beispiel:
[ Seite 1 ] [...] [ Seite 5 ] [ Seite 6 ] [ Seite 7 ] [ Seite 8 ] [ Seite 9 ] [...] [ Seite 800 ]

<?php

/**
* @author Heiko Ramaker
* @name pageTurn
* @version beta 1
*/
class pageTurn {
    
    
// default maximum pages showing ahead and after current page
    
public $_maxNeighbours 2;
    
// show a "jump to start/end" link
    
public $_jumpToEndings true;
    
// should actual page page be a clickable link?
    
public $_actualPageClickable false
    
// holder for the link
    
public $_link
    
// true if -1 in link for the pagecounting
    
public $_subPageInLink true
    
// maybe you would set a special linktext every time?
    
public $_linkText 'Seite %%page%%';
    
// word or sign listed leftsided of every link
    
public $_startLinkSigns '[ ';
    
// word or sign listed rightsided of every link
    
public $_endLinkSigns ' ] ';
    
    
// the default value of actual page
    
const DEFAULT_PAGE 1
    
// the default value of entries showing per page
    
const DEFAULT_ENTRIES_PER_PAGE 10
    
// replace in the URL with the internal page-counting
    
const INLINK_PAGE_WILDCARD '%%count%%'
    
// replace in Link-Title with the page number
    
const PAGECOUNT_WILDCARD '%%page%%'
    
    
/**
    * Instance Holder
    */
    
private static $_instance

    
/**
    * create a singleton
    */
    
public static function get_instance() {
        static 
$_instance false;
        if(!
$_instance){
            
$_instance = new pageTurn();
        }
        return 
$_instance;
    }
    
    
/**
    * @param int $entries
    * @param int $actPage    
    * @param string $link
    * @param int $perPage
    * @return string
    */
    
public function createPageTurn($link$entries$actPage null$perPage null) {
        if(
is_null($actPage)) {
            
$actPage self::DEFAULT_PAGE;
        }
        if(
is_null($perPage)) {
            
$perPage self::DEFAULT_ENTRIES_PER_PAGE;
        }
        
        
$this->setLink($link);
        
$totalPages $this->getTotalNumOfPages($entries$perPage);
        if(
$totalPages == 1) {
            return 
$this->createActual(0);
        } else {
            
$ahead $this->createAhead($actPage);
            
$actual $this->createActual($actPage);
            
$after $this->createAfter($actPage$totalPages);
            return 
$ahead.$actual.$after;
        }
    }
    
    
/**
    * @param int $actPage    
    * @return string
    */
    
private function createAhead($actPage) {
        
$return '';
        if(
$this->_subPageInLink === true) {
            
$starter 0;
            
$backTo max($starter$actPage $this->_maxNeighbours);
        } else {
            
$starter 1;
            
$backTo max($starter$actPage $this->_maxNeighbours);
        }
        if(
$backTo $starter && $this->_jumpToEndings === true) {
            
$difference $backTo $starter;
            if(
$difference == 1) {
                
$return .= $this->createSingleLink($starter);
            } elseif(
$difference 1) {
                
$return .= $this->createSingleLink($starter);
                
$return .= '[...] ';
            }
        }
        
//echo $backTo.' => '.$starter.' => '.$actPage.'<br>';
        
for($i $backTo$i $actPage$i++) {
            
$return .= $this->createSingleLink($i);
        }
        return 
$return;
    }
    
    
/**
    * @param int $page
    * @return string
    */
    
private function createSingleLink($page) {
        if(
$this->_subPageInLink === true) {
            
$showPage $page 1;
        } else {
            
$showPage $page;
        }
        
$return .= str_replace(self::PAGECOUNT_WILDCARD$showPage$this->_link);
        
$return str_replace(self::INLINK_PAGE_WILDCARD$page$return);
        return 
$return;
    }
    
    
    
/**
    * @param int $actPage    
    * @param int $pages    
    * @return string
    */
    
private function createAfter($actPage$pages) {
        
$return '';
        
        if(
$this->_subPageInLink === true) {
            
$ender $pages 1;
        } else {
            
$ender $pages;
        }
        
$forwardTo min($ender$actPage $this->_maxNeighbours);

        for(
$i $actPage+1$i <= $forwardTo$i++) {
            
$return .= $this->createSingleLink($i);
        }
        
        
        if(
$forwardTo $ender && $this->_jumpToEndings === true) {
            
$difference $ender $forwardTo;
            if(
$difference == 1) {
                
$return .= $this->createSingleLink($ender);
            } elseif(
$difference 1) {
                
$return .= '[...] ';
                
$return .= $this->createSingleLink($ender);
            }
        }
        return 
$return;
    }
    
    
/**
    * @param string $link
    * @param int $actPage    
    * @return string
    */
    
private function createActual($actPage) {
        if(
$this->_actualPageClickable === true) {
            return 
$this->createSingleLink($actPage);
        } else {
            if(
$this->_subPageInLink === true) {
                
$showPage str_replace(self::PAGECOUNT_WILDCARD$actPage 1$this->_linkText);
            } else {
                
$showPage str_replace(self::PAGECOUNT_WILDCARD$actPage$this->_linkText);
            }
            return 
$this->_startLinkSigns.'<u>'.$showPage.'</u>'.$this->_endLinkSigns;
        }
    }
    
    
/**
    * @param int $entries
    * @param int $perPage
    * @return int
    */
    
protected function getTotalNumOfPages($entries$perPage null) {
        if(
is_null($perPage)) {
            
$perPage self::DEFAULT_ENTRIES_PER_PAGE;
        }
        if(
$entries 0) {
            return 
ceil($entries/$perPage);
        } else {
            return 
1;
        }
    }
    
    
/**
    *    Set the _link value
    *    @param string $link
    */        
    
public function setLink($link) {
        if (
is_string($link)) {
            
$this->_link str_replace(self::PAGECOUNT_WILDCARD$this->_linkText $this->_startLinkSigns.$link.$this->_endLinkSigns);
        }
    }
    
    
/**
    *    Set the _maxNeighbours value
    *    @param int $maxNeighbours
    */        
    
public function setMaxNeighbours($max) {
        if (
is_int($max)) {
            
$this->_maxNeighbours $max;
        }
    }
    
    
/**
    *    Set the $_actualPageClickable value
    *    @param boolean $bool
    */
    
public function setActualPageClickable($bool) {
        if(
is_bool($bool)) {
            
$this->_actualPageClickable $bool;
        }
    }
    
    
    
/**
    *    Set the $_jumpToEndings value
    *    @param boolean $bool
    */
    
public function setJumpToEndings($bool) {
        if (
is_bool($bool)) {
            
$this->_jumpToEndings $bool;
        }
    }
    
    
/**
    *    Set the $_startLinkSigns value
    *    @param boolean $bool
    */
    
public function setStartLinkSigns($val) {
        
$this->_startLinkSigns $val;
    }
    
    
/**
    *    Set the $_endLinkSigns value
    *    @param boolean $bool
    */
    
public function setEndLinkSigns($val) {
        
$this->_endLinkSigns $val;
    }
    
    
/**
    *    Set the $_linkText value
    *    @param boolean $bool
    */
    
public function setLinkText($val) {
        
$this->_linkText $val;
    }
    
    
/**
    *    Set the $_subPageInLink value
    *    @param boolean $bool
    */
    
public function setsubPageInLink($bool) {
        if(
is_bool($bool)) {
            
$this->_startLinkSigns $bool;
        }
    }
    
}


?>


Diese Klasse liefert als Endausgabe einen String mit dem kompletten Navigationsmenü zum Durchblättern von Seiten bzw. Datensätzen.
Um eine neue Navigation in die Seite einzubinden und die Links zum Blättern darzustellen, muss lediglich nachfolgender Code in die eigene Seite eingebunden werden:

<?php 
require_once('path/pageturn.class.php'); 
$pageturn = new pageTurn(); 
$link '<a href="index.php?seite=%%count%%">%%page%%</a>'
echo 
$pageturn->createPageTurn($link$gesamtAnzahl$aktuelleSeite$anzeigeProSeite); 
?>


Erläuterung:

Die Funktion $this->createPageTurn() erwartet mindestens 2 und maximal 4 Parameter, um die Navigation zum Blättern erstellen zu können.
  • $link: In dieser Variablen verbirgt sich der Link, so wie er später in der Navigation aussehen soll.
    Je nachdem wie die Seitenstruktur aussieht, sieht natürlich auch der Link immer anders aus und ist an die Seite anzupassen.
    Im Link können zwei Platzhalter gesetzt werden, als default-Werte sind das %%count%% und %%page%%.
    %%count%% wird durch den Parameter ersetzt, der in der URL an das Script übergeben werden soll und %%page%% erhält die tatsächliche Seitenzahl, so wie sie der Besucher zu sehen bekommt.
    %%count%% entspricht dabei entweder exakt dem Wert von %%page%% oder aber %%page%% minus 1. Dazu aber weiter unter mehr ;-)
  • $gesamtAnzahl: Dies ist der numerische Wert der insgesamt vorhandenen Datensätze.
  • $aktuelleSeite: Dies ist der numerische Wert der aktuell angezeigten Seite.
    Wird dieser Wert nicht angegeben, geht die Klasse automatisch davon aus, das wir uns aktuell auf der ersten Seite befinden.
  • $anzeigeProSeite: Wie viele Datensätze sollen pro Seite angezeigt werden?
    Der Wert aus $gesamtAnzahl geteilt durch $anzeigeProSeite ergibt die Gesamtanzahl an Seiten die von der Klasse generiert werden.

Eine zugehörige mySQL-Abfrage bei $_subPageInLink = true; für diese Navigation könnte in etwa nun so lauten:

<?php 
$anzeigeProSeite 
'15'
$offset $_GET['seite'] * $anzeigeProSeite
$sql "SELECT [...werte...] from [...tabelle...] [...where...] limit $offset$anzeigeProSeite"
$result mysql_query($sql); 
?>


Eine (unschöne) Besonderheit dieser Klasse ist die Variable $_subPageInLink.
Ich persönlich bevorzuge es, von der Navigation direkt den Wert geliefert zu bekommen, den ich mit $anzeigeProSeite multiplizieren muss, um die neuen Datensätze aus der Datenbank geliefert zu bekommen.
Das bedeutet, dass meine URLs überlicherweise so aussehen:

<a href="index.php?seite=2">3</a> 
<a href="index.php?seite=3">4</a> 


Der Wert in der URL ist immer um 1 kleiner als die dem Betrachter angezeigte Seitenzahl.
Das heißt also, bei mir beginnt das Counting auf Seite 1 mit einer 0.
Denn: Seite 1 zeigt mir alle Datensätze ab 0 an und 0 multipliziert mit der Anzahl der maximal anzuzeigenden Datensätze bleibt 0.
Seite 4 zeigt mir beispielsweise 15 Datensätze beginnend ab 45 an.
Also ist mein Startwert 3 multipliziert mit 15. ;-)
Wem das mathematisch zu hoch ist oder wer davon irritiert ist, das der Link 2 unterschiedliche Werte hat, der kann den Wert $_subPageInLink auf false setzen.
In dem Fall sind der URL-Wert und der Anzeige-Wert (%%count%% und %%page%%) identisch:

<a href="index.php?seite=3">3</a> 
<a href="index.php?seite=4">4</a> 


Die dazugehörige mySQL-Abfrage bei $_subPageInLink = false; müsste dann folgendermaßen lauten:

<?php 
$anzeigeProSeite 
'15'
$offset = ($_GET['seite']-1) * $anzeigeProSeite
$sql "SELECT [...werte...] from [...tabelle...] [...where...] limit $offset$anzeigeProSeite"
$result mysql_query($sql); 
?>


$_GET['seite'] aus dem o.a. Beispiel müsste also vorher immer erst um 1 subtrahiert werden.
Jeder wie er es mag ;-)
Ach so, und bitte nicht vergessen:
Vor der Abfrage an die Datenbank UNBEDINGT noch überprüfen, ob es sich bei den übergebenden Werten auch wirklich um gültige Werte handelt.
Niemals irgendwelche Daten ungeprüft weiter verarbeiten, wie ich im Beispiel getan habe. ;-)


Selbstverständlich darf diese Klasse verwendet werden.
Für Anregungen, Kritik oder ein "Danke" habe ich immer ein offenes Ohr! ;-)
Ich hoffe, ich habe dem Einen oder Anderen mit dieser Klasse etwas Gutes tun können. Und seien es auch nur neue Denkanstöße.