I did most of my PHP coding back with PHP 4, and among the projects I did was a popular pagination class. I've been meaning to bring it up to date with PHP 5 and OOP practices. I've also been working on updating my database code, using PDO over the deprecated mysql* functions. What I'd like from you is your input and feedback about my updates.
Pagination Class
class Paginator{
public $items_per_page;
public $total_items;
public $current_page;
public $num_pages;
public $mid_range;
public $limit;
public $limit_start;
public $limit_end;
public $return;
public $querystring;
public $ipp_array;
public $get_ipp;
public $get_page;
public function __construct($total,$mid_range=7,$ipp_array=array(10,25,50,100,'All')) {
$this->total_items = $total;
$this->mid_range = $mid_range;
$this->ipp_array = $ipp_array;
$this->items_per_page = (isset($_GET['ipp'])) ? $_GET['ipp'] : $this->ipp_array[0];
$this->get_ipp = isset($_GET['ipp']) ? $_GET['ipp'] : NULL;
$this->get_page = isset($_GET['page']) ? (int) $_GET['ipp'] : 1;
$this->default_ipp = $this->ipp_array[0];
if($this->get_ipp == 'All') {
$this->num_pages = 1;
} else {
if(!is_numeric($this->items_per_page) OR $this->items_per_page <= 0) $this->items_per_page = $this->ipp_array[0];
$this->num_pages = ceil($this->total_items/$this->items_per_page);
}
$this->current_page = (isset($_GET['page'])) ? (int) $_GET['page'] : 1 ; // must be numeric > 0
if($_GET) {
$args = explode("&",$_SERVER['QUERY_STRING']);
foreach($args as $arg) {
$keyval = explode("=",$arg);
if($keyval[0] != "page" And $keyval[0] != "ipp") $this->querystring .= "&" . $arg;
}
}
if($_POST) {
foreach($_POST as $key=>$val) {
if($key != "page" And $key != "ipp") $this->querystring .= "&$key=$val";
}
}
if($this->num_pages > 10) {
$this->return = ($this->current_page > 1 And $this->total_items >= 10) ? "<a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=".($this->current_page-1)."&ipp=$this->items_per_page$this->querystring\">Previous</a> ":"<span class=\"inactive\" href=\"#\">Previous</span> ";
$this->start_range = $this->current_page - floor($this->mid_range/2);
$this->end_range = $this->current_page + floor($this->mid_range/2);
if($this->start_range <= 0) {
$this->end_range += abs($this->start_range)+1;
$this->start_range = 1;
}
if($this->end_range > $this->num_pages) {
$this->start_range -= $this->end_range-$this->num_pages;
$this->end_range = $this->num_pages;
}
$this->range = range($this->start_range,$this->end_range);
for($i=1;$i<=$this->num_pages;$i++) {
if($this->range[0] > 2 And $i == $this->range[0]) $this->return .= " ... ";
// loop through all pages. if first, last, or in range, display
if($i==1 Or $i==$this->num_pages Or in_array($i,$this->range)) {
$this->return .= ($i == $this->current_page And $this->get_page != 'All') ? "<a title=\"Go to page $i of $this->num_pages\" class=\"current\" href=\"#\">$i</a> \n":"<a class=\"paginate\" title=\"Go to page $i of $this->num_pages\" href=\"$_SERVER[PHP_SELF]?page=$i&ipp=$this->items_per_page$this->querystring\">$i</a> \n";
}
if($this->range[$this->mid_range-1] < $this->num_pages-1 And $i == $this->range[$this->mid_range-1]) $this->return .= " ... ";
}
$this->return .= (($this->current_page < $this->num_pages And $this->total_items >= 10) And ($this->get_page != 'All') And $this->current_page > 0) ? "<a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=".($this->current_page+1)."&ipp=$this->items_per_page$this->querystring\">Next</a>\n":"<span class=\"inactive\" href=\"#\">Next</span>\n";
$this->return .= ($this->get_page == 'All') ? "<a class=\"current\" style=\"margin-left:10px\" href=\"#\">All</a> \n":"<a class=\"paginate\" style=\"margin-left:10px\" href=\"$_SERVER[PHP_SELF]?page=1&ipp=All$this->querystring\">All</a> \n";
} else {
for($i=1;$i<=$this->num_pages;$i++) {
$this->return .= ($i == $this->current_page) ? "<a class=\"current\" href=\"#\">$i</a> ":"<a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=$i&ipp=$this->items_per_page$this->querystring\">$i</a> ";
}
$this->return .= "<a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=1&ipp=All$this->querystring\">All</a> \n";
}
$this->return = str_replace('&','&',$this->return);
$this->limit_start = ($this->current_page <= 0) ? 0:($this->current_page-1) * $this->items_per_page;
if($this->current_page <= 0) $this->items_per_page = 0;
$this->limit_end = ($this->get_ipp == 'All') ? (int) $this->total_items: (int) $this->items_per_page;
}
public function display_items_per_page() {
$items = NULL;
natsort($this->ipp_array); // This sorts the drop down menu options array in numeric order (with 'all' last after the default value is picked up from the first slot
if(is_null($this->get_ipp)) $this->items_per_page = $this->ipp_array[0];
foreach($this->ipp_array as $ipp_opt) $items .= ($ipp_opt == $this->items_per_page) ? "<option selected value=\"$ipp_opt\">$ipp_opt</option>\n":"<option value=\"$ipp_opt\">$ipp_opt</option>\n";
return "<span class=\"paginate\">Items per page:</span><select class=\"paginate\" onchange=\"window.location='$_SERVER[PHP_SELF]?page=1&ipp='+this[this.selectedIndex].value+'$this->querystring';return false\">$items</select>\n";
}
public function display_jump_menu() {
$option=NULL;
for($i=1;$i<=$this->num_pages;$i++) {
$option .= ($i==$this->current_page) ? "<option value=\"$i\" selected>$i</option>\n":"<option value=\"$i\">$i</option>\n";
}
return "<span class=\"paginate\">Page:</span><select class=\"paginate\" onchange=\"window.location='$_SERVER[PHP_SELF]?page='+this[this.selectedIndex].value+'&ipp=$this->items_per_page$this->querystring';return false\">$option</select>\n";
}
public function display_pages() {
return $this->return;
}
}
Sample Instantiation
<?php
include('paginator.class.php');
try {
$conn = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$num_rows = $conn->query('SELECT COUNT(*) FROM City')->fetchColumn();
$pages = new Paginator($num_rows,9,array(15,3,6,9,12,25,50,100,250,'All'));
echo $pages->display_pages();
echo "<span class=\"\">".$pages->display_jump_menu().$pages->display_items_per_page()."</span>";
$stmt = $conn->prepare('SELECT City.Name,City.Population,Country.Name,Country.Continent,Country.Region FROM City INNER JOIN Country ON City.CountryCode = Country.Code ORDER BY City.Name ASC LIMIT :start,:end');
$stmt->bindParam(':start', $pages->limit_start, PDO::PARAM_INT);
$stmt->bindParam(':end', $pages->limit_end, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll();
echo "<table><tr><th>City</th><th>Population</th><th>Country</th><th>Continent</th><th>Region</th></tr>\n";
foreach($result as $row) {
echo "<tr><td>$row[0]</td><td>$row[1]</td><td>$row[2]</td><td>$row[3]</td><td>$row[4]</td></tr>\n";
}
echo "</table>\n";
echo $pages->display_pages();
echo "<p class=\"paginate\">Page: $pages->current_page of $pages->num_pages</p>\n";
echo "<p class=\"paginate\">SELECT * FROM table LIMIT $pages->limit_start,$pages->limit_end (retrieve records $pages->limit_start-".($pages->limit_start+$pages->limit_end)." from table - $pages->total_items item total / $pages->items_per_page items per page)";
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
?>
Live Demos
Areas of Concern
I noticed in my query that if I try to execute the SQL with:
$stmt->execute( array(':start'=>$pages->limit_start,':end'=>$pages->limit_end) );
I get an error unless I also use $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
. However I can get around that if I use bindParam
instead:
$stmt->bindParam(':start', $pages->limit_start, PDO::PARAM_INT);
$stmt->bindParam(':end', $pages->limit_end, PDO::PARAM_INT);
$stmt->execute();
Should I be using $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
or the separate bindParam calls?bindParam
seems like a decent solution, whereas using ATTR_EMULATE_PREPARES
seems a little hacky.
Within the class, am I setting the visibility (public, private, protected) of my parameters and methods properly?
If you see anything that jumps out at you like "what in the world was he thinking?", you can assume that I was dealing with something I hadn't done before. Any tips or suggestions overall would be appreciated.