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-&gt;total_items = $total;
        $this-&gt;mid_range = $mid_range;
        $this-&gt;ipp_array = $ipp_array;
        $this-&gt;items_per_page = (isset($_GET['ipp'])) ? $_GET['ipp'] : $this-&gt;ipp_array[0];
        $this-&gt;get_ipp = isset($_GET['ipp']) ? $_GET['ipp'] : NULL;
        $this-&gt;get_page = isset($_GET['page']) ? (int) $_GET['ipp'] : 1;
        $this-&gt;default_ipp = $this-&gt;ipp_array[0];

        if($this-&gt;get_ipp == 'All') {
            $this-&gt;num_pages = 1;
        } else {
            if(!is_numeric($this-&gt;items_per_page) OR $this-&gt;items_per_page &lt;= 0) $this-&gt;items_per_page = $this-&gt;ipp_array[0];
            $this-&gt;num_pages = ceil($this-&gt;total_items/$this-&gt;items_per_page);
        }
        $this-&gt;current_page = (isset($_GET['page'])) ? (int) $_GET['page'] : 1 ; // must be numeric &gt; 0
        if($_GET) {
            $args = explode("&amp;",$_SERVER['QUERY_STRING']);
            foreach($args as $arg) {
                $keyval = explode("=",$arg);
                if($keyval[0] != "page" And $keyval[0] != "ipp") $this-&gt;querystring .= "&amp;" . $arg;
            }
        }
        if($_POST) {
            foreach($_POST as $key=&gt;$val) {
                if($key != "page" And $key != "ipp") $this-&gt;querystring .= "&amp;$key=$val";
            }
        }
        if($this-&gt;num_pages &gt; 10) {
            $this-&gt;return = ($this-&gt;current_page &gt; 1 And $this-&gt;total_items &gt;= 10) ? "&lt;a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=".($this-&gt;current_page-1)."&amp;ipp=$this-&gt;items_per_page$this-&gt;querystring\"&gt;Previous&lt;/a&gt; ":"&lt;span class=\"inactive\" href=\"#\"&gt;Previous&lt;/span&gt; ";
            $this-&gt;start_range = $this-&gt;current_page - floor($this-&gt;mid_range/2);
            $this-&gt;end_range = $this-&gt;current_page + floor($this-&gt;mid_range/2);
            if($this-&gt;start_range &lt;= 0) {
                $this-&gt;end_range += abs($this-&gt;start_range)+1;
                $this-&gt;start_range = 1;
            }
            if($this-&gt;end_range &gt; $this-&gt;num_pages) {
                $this-&gt;start_range -= $this-&gt;end_range-$this-&gt;num_pages;
                $this-&gt;end_range = $this-&gt;num_pages;
            }
            $this-&gt;range = range($this-&gt;start_range,$this-&gt;end_range);
            for($i=1;$i&lt;=$this-&gt;num_pages;$i++) {
                if($this-&gt;range[0] &gt; 2 And $i == $this-&gt;range[0]) $this-&gt;return .= " ... ";
                // loop through all pages. if first, last, or in range, display
                if($i==1 Or $i==$this-&gt;num_pages Or in_array($i,$this-&gt;range)) {
                    $this-&gt;return .= ($i == $this-&gt;current_page And $this-&gt;get_page != 'All') ? "&lt;a title=\"Go to page $i of $this-&gt;num_pages\" class=\"current\" href=\"#\"&gt;$i&lt;/a&gt; \n":"&lt;a class=\"paginate\" title=\"Go to page $i of $this-&gt;num_pages\" href=\"$_SERVER[PHP_SELF]?page=$i&amp;ipp=$this-&gt;items_per_page$this-&gt;querystring\"&gt;$i&lt;/a&gt; \n";
                }
                if($this-&gt;range[$this-&gt;mid_range-1] &lt; $this-&gt;num_pages-1 And $i == $this-&gt;range[$this-&gt;mid_range-1]) $this-&gt;return .= " ... ";
            }
            $this-&gt;return .= (($this-&gt;current_page &lt; $this-&gt;num_pages And $this-&gt;total_items &gt;= 10) And ($this-&gt;get_page != 'All') And $this-&gt;current_page &gt; 0) ? "&lt;a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=".($this-&gt;current_page+1)."&amp;ipp=$this-&gt;items_per_page$this-&gt;querystring\"&gt;Next&lt;/a&gt;\n":"&lt;span class=\"inactive\" href=\"#\"&gt;Next&lt;/span&gt;\n";
            $this-&gt;return .= ($this-&gt;get_page == 'All') ? "&lt;a class=\"current\" style=\"margin-left:10px\" href=\"#\"&gt;All&lt;/a&gt; \n":"&lt;a class=\"paginate\" style=\"margin-left:10px\" href=\"$_SERVER[PHP_SELF]?page=1&amp;ipp=All$this-&gt;querystring\"&gt;All&lt;/a&gt; \n";
        } else  {
            for($i=1;$i&lt;=$this-&gt;num_pages;$i++) {
                $this-&gt;return .= ($i == $this-&gt;current_page) ? "&lt;a class=\"current\" href=\"#\"&gt;$i&lt;/a&gt; ":"&lt;a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=$i&amp;ipp=$this-&gt;items_per_page$this-&gt;querystring\"&gt;$i&lt;/a&gt; ";
            }
            $this-&gt;return .= "&lt;a class=\"paginate\" href=\"$_SERVER[PHP_SELF]?page=1&amp;ipp=All$this-&gt;querystring\"&gt;All&lt;/a&gt; \n";
        }
        $this-&gt;return = str_replace('&amp;','&amp;amp;',$this-&gt;return);
        $this-&gt;limit_start = ($this-&gt;current_page &lt;= 0) ? 0:($this-&gt;current_page-1) * $this-&gt;items_per_page;
        if($this-&gt;current_page &lt;= 0) $this-&gt;items_per_page = 0;
        $this-&gt;limit_end = ($this-&gt;get_ipp == 'All') ? (int) $this-&gt;total_items: (int) $this-&gt;items_per_page;
    }
    public function display_items_per_page() {
        $items = NULL;
        natsort($this-&gt;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-&gt;get_ipp)) $this-&gt;items_per_page = $this-&gt;ipp_array[0];
        foreach($this-&gt;ipp_array as $ipp_opt) $items .= ($ipp_opt == $this-&gt;items_per_page) ? "&lt;option selected value=\"$ipp_opt\"&gt;$ipp_opt&lt;/option&gt;\n":"&lt;option value=\"$ipp_opt\"&gt;$ipp_opt&lt;/option&gt;\n";
        return "&lt;span class=\"paginate\"&gt;Items per page:&lt;/span&gt;&lt;select class=\"paginate\" onchange=\"window.location='$_SERVER[PHP_SELF]?page=1&amp;amp;ipp='+this[this.selectedIndex].value+'$this-&gt;querystring';return false\"&gt;$items&lt;/select&gt;\n";
    }
    public function display_jump_menu() {
        $option=NULL;
        for($i=1;$i&lt;=$this-&gt;num_pages;$i++) {
            $option .= ($i==$this-&gt;current_page) ? "&lt;option value=\"$i\" selected&gt;$i&lt;/option&gt;\n":"&lt;option value=\"$i\"&gt;$i&lt;/option&gt;\n";
        }
        return "&lt;span class=\"paginate\"&gt;Page:&lt;/span&gt;&lt;select class=\"paginate\" onchange=\"window.location='$_SERVER[PHP_SELF]?page='+this[this.selectedIndex].value+'&amp;amp;ipp=$this-&gt;items_per_page$this-&gt;querystring';return false\"&gt;$option&lt;/select&gt;\n";
    }
    public function display_pages() {
        return $this-&gt;return;
    }
}


Sample Instantiation

&lt;?php
include('paginator.class.php');
try {
    $conn = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    $conn-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $num_rows = $conn-&gt;query('SELECT COUNT(*) FROM City')-&gt;fetchColumn(); 

    $pages = new Paginator($num_rows,9,array(15,3,6,9,12,25,50,100,250,'All'));
    echo $pages-&gt;display_pages();
    echo "&lt;span class=\"\"&gt;".$pages-&gt;display_jump_menu().$pages-&gt;display_items_per_page()."&lt;/span&gt;";

    $stmt = $conn-&gt;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-&gt;bindParam(':start', $pages-&gt;limit_start, PDO::PARAM_INT);
    $stmt-&gt;bindParam(':end', $pages-&gt;limit_end, PDO::PARAM_INT);
    $stmt-&gt;execute();
    $result = $stmt-&gt;fetchAll();

    echo "&lt;table&gt;&lt;tr&gt;&lt;th&gt;City&lt;/th&gt;&lt;th&gt;Population&lt;/th&gt;&lt;th&gt;Country&lt;/th&gt;&lt;th&gt;Continent&lt;/th&gt;&lt;th&gt;Region&lt;/th&gt;&lt;/tr&gt;\n";
    foreach($result as $row) {
        echo "&lt;tr&gt;&lt;td&gt;$row[0]&lt;/td&gt;&lt;td&gt;$row[1]&lt;/td&gt;&lt;td&gt;$row[2]&lt;/td&gt;&lt;td&gt;$row[3]&lt;/td&gt;&lt;td&gt;$row[4]&lt;/td&gt;&lt;/tr&gt;\n";
    }
    echo "&lt;/table&gt;\n";
    echo $pages-&gt;display_pages();
    echo "&lt;p class=\"paginate\"&gt;Page: $pages-&gt;current_page of $pages-&gt;num_pages&lt;/p&gt;\n";
    echo "&lt;p class=\"paginate\"&gt;SELECT * FROM table LIMIT $pages-&gt;limit_start,$pages-&gt;limit_end (retrieve records $pages-&gt;limit_start-".($pages-&gt;limit_start+$pages-&gt;limit_end)." from table - $pages-&gt;total_items item total / $pages-&gt;items_per_page items per page)";
} catch(PDOException $e) {
    echo 'ERROR: ' . $e-&gt;getMessage();
}
?&gt;


Live Demos


Demo 1
Demo 2


Areas of Concern

I noticed in my query that if I try to execute the SQL with:

$stmt-&gt;execute( array(':start'=&gt;$pages-&gt;limit_start,':end'=&gt;$pages-&gt;limit_end) );


I get an error unless I also use $conn-&gt;setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);. However I can get around that if I use bindParam instead:

$stmt-&gt;bindParam(':start', $pages-&gt;limit_start, PDO::PARAM_INT);
$stmt-&gt;bindParam(':end', $pages-&gt;limit_end, PDO::PARAM_INT);
$stmt-&gt;execute();


Should I be using $conn-&gt;setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE); or the separate bindParam calls?bindParam seems like a decent solution, whereas using ATTR_EMULATE_PREPARESseems 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.


Question: What is Pointer-chasing and how it is related to BFS
Answer: I find it easiest to think of a Linked List example.

Lets say we have a Linked List with 5 elements.  To get to the 3rd element, you have to use Pointer-chasing to traverse through the elements.