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: How to show the integers have same cardinality as the natural numbers?
Answer: Your list has absolutely no pattern, because you're doing it "wrong", in the sense that you are not writing it in a way that a pattern emerges. 

Of course there is no order preserving function, so writing both sets with their natural order will promise you that there is no reasonable pattern. Instead write the natural numbers, and then try to write the integers:

$$\begin{array}
&amp; 1 &amp; 2&amp;  3&amp;  4&amp;  5&amp; 6&amp;  7&amp; 8&amp;\ldots\\
  0 &amp; 1&amp; -1&amp; 2&amp; -2&amp;  3&amp; -3&amp; 4&amp;\ldots
\end{array}$$

This gives rise to a bijection from $\Bbb N$ onto $\Bbb Z$. Now just find its inverse.

(Note that it is in general easier to find "pattern" when $\Bbb N$ is the domain, because the list does not require you to go into two directions.)