eCreate Logo

Search site


Tutorial - Pagination in PHP

If you're developing a front end to web applications, sooner or later, you'll find you need to paginate data of some sort.  It might be search results, image galleries, or tabular data.  You'll find that you use the same techniques - perhaps use an old bit of code as a template and change it to suit your needs each time.

This article is explains how you can use a quick class to handle pagination easily - leaving you only the associated CSS presentation markup to add to make your solution complete.  For the sake of completeness, we'll provide a sample CSS snippet that you can alter to fit your site design.

Requirements

Our navigation needs to take a few parameters.  First, it needs to know about the metrics of the data it's being asked to paginate.  This includes the total number of items, the number of items per 'page' and the 'current' element - from which it can establish the current page.

Additionally, we're going to allow for a couple of different styles of pagination (you could easily add more).  OK, the way we're going to do this is not entirely object oriented - probably, we'd have a base class that was responsible for calculations, then extend that class multiple times to override a 'display()' method to allow for these different styles.  But we're not going to.  Sorry if that offends, but the fact of the matter is, this is a simple bit of navigation, and we only want one class to have to think about - we're looking for simple code here.

Outline of the code

Constructor

The constructor of the class will accept the key parameters, so actually, only two lines of code will ever be required to use the class.  The constructor will just sanity-check the input and then call other methods to process the data:

function Paginate($limit, $num, $url, $maxPages){

    //We get the start number from the URL. Casting to int makes it safe
    if (isset($_GET['start'])){
     $this->start = (int) $_GET['start'];
    }

    $this->limit = (int) $limit;
    $this->num = (int) $num;
    $this->url = $url;
    $this->maxPages = (int) $maxPages; //set to a default value of 20

    //The URL might not have any parameters, but since we need to add one,
    //we must know whether to append a ? or a &
    $this->interpretUrl();
    $this->calculate_numPages();
    $this->calculateCurrent();
}

There's nothing difficult here:  We're using a parameter called 'start' in the url to carry the current page through to subsequent pages of data.  Since this is a number, the simplest way to 'clean' it is to cast it explicitly to an int.  $limit is the number of results per page.  The $num parameter is the total number of results that we're paginating through.  $url is the url of the page that the data is on, but we're not using $_SERVER['PHP_SELF'] here in case some flexibility is required to link to different urls.  Normally, you could just pass $_SERVER['PHP_SELF'] through as this parameter.  Finally, $maxPages is the maximum number of links you want.  If you're paging through a lot of data, you don't normally want to show all page links at once - it gets too messy and the navigation might end up flowing onto more than one line.

Once these variables are set, the constructor calls three methods: interpretUrl(), calculate_numPages() and calculateCurrent().

Interpreting the URL

This method simply establishes whether there are already paramters in the URL.  If there are, it knows to append the page number with an ampersand (&).  If not, it will use a question mark (?). 

The code to check the existence of a question mark is simple:

 function interpretUrl(){
     //Check if we need to add ? or & to the end of the url to add start
     //parameter.
     if (strstr ($this->url, "?")){
         $this->urlAppendString = "&";
     } else {
         $this->urlAppendString = "?";
     }
 }

This will return true if a question mark exists in the URL.

Calculating the number of pages

Again, this is a simple calculation:

 function calculate_numPages(){
     $this->numPages = ceil($this->num / $this->limit);
 }

This takes the ceiling (ceil) of the number of results divided by the number of results per page, so if there are 19 results and 10 results per page are wanted, the result is 1.9.  Ceiling will round this up to 2.

Calculating the current page.

Basically, this just performs the calculation if the start parameter in the URL is greater then 1:

 function calculateCurrent(){
     if ($this->start > $this->limit){
        $this->currentPage = ceil($this->start / $this->limit);
     }
 }

Again, it uses the ceil function, but this time divides the start parameter by the number of results per page.  It's worth mentioning here that the start parameter we're using in the url isn't actually the page number, but the record number that we want to start with on that page.  This makes it easier to paginate results from MySQL and other databases, since it can be used directly in search queries.  So, if we're showing 10 results per page, then page 2 will start with record 11.

That's all the constructor does.  All other calculations are done from display methods, and consist of working out navigation-specific elements.

Navigation markup methods

A display method will call other methods to get links by their function.  So, if we want a 'First page' link, we simply call that method, which will return link markup.  We won't go into them all here, since they're quite similar.  Here's the code for the previous page link:

 function getPreviousPage(){
     if ($this->currentPage > 1){
         return "<a href='".$this->url.$this->urlAppendString."start=".
         ($this->start-$this->limit).
         "' title='Previous page'>&lt;Previous page</a>";
     }
 }

If the current page is page 1, we suppress the output of the method.  If you want the text to appear, but without the link, you could just add an else clause here.  As you can see, we simply return a series of concatenated strings.  We start with the opening <a tag to create a link.  Then, we make it link to the base url that was passed into the constructor, appended to either a ? or an & depending on what was in the original URL, then we append the start parameter.  The calculation for the value of the start parameter in this case is just the current start value minus the number of results on a page, so if we're on page 2 (with a start value of 11 and a max per page value of 10), then 11-10 = 1, which will be the basis of the link to the first page.

These first, last, next and previous methods are all quite similar.  Slightly more complex is the code to generate a list of links for page numbers:

 function getPaginationLinks(){
    $ar = array();
    if(($this->currentPage-ceil($this->maxPages/2))>1){
        $pageStart =($this->currentPage-ceil($this->maxPages/2));
    } else {
        $pageStart = 1;
    }

    if (($this->currentPage+ceil($this->maxPages/2))<$this->numPages){
        $pageEnd =($this->currentPage+ceil($this->maxPages/2));
    } else {
        $pageEnd = $this->numPages;
    }
   
    for ($i=$pageStart;$i<=$pageEnd;$i++){
        if ($i == $this->currentPage){
            $ar[$i] = "[".$i."]";
        } else {
            $ar[$i] = "<a href='".$this->url.$this->urlAppendString.
            "start=".((($i-1)*$this->limit)+1)."'
            title='Go to page ". $i ."'>".$i."</a>";
        }
     }

     return $ar;
 }

This method returns an array of <a> tags.  Basically, it just uses the maxPages value to set limits for the number of links to the left or right of the current page.  For the current page, it surrounds the page number by [ and ] instead of generating a link.

We start off by establishing the lower limit.  If the current page is more than half of the maxPages value, then the link for page 1, etc. won't be shown.  We do the same with the upper limit.  This is why we would always recommend using 'next page, previous page' links for ease of navigation.

Finally, we make an array of pages within our defined range.  We start a loop at the calculated 'start page' and end it at the calculated end page. 

Using the class

If you perform a database select query, for example, you will be able to determine the number of rows returned in the resultset, and paginate the results easily by returning a set of rows.  In MySQL, you can use the limit keyword to specify the start and end rows that you would like to be returned.  So, on a page showing database results, you could start the constructor like this:

$paginate = new Paginate(10, $numResults, $_SERVER['PHP_SELF'], 20);

In this example, we start an instance of the Paginate class called $paginate.  We set the max results per page to 10, which is what we would use in the limit clause in the SQL.  $numResults would be the result of a mysql_num_rows() call.  $_SERVER['PHP_SELF'] will simply cause the page links to link to the current page, but with the start result row in the URL, which can be accessed with $_GET['start'] for building the select query.  Finally, 20 is the number of page links we would like to display as a maximum.

OK, so that one line has set up the pagination parameters.  To display pagination links, we then just need to call one of the display methods.  Most commonly, we will display an unordered list, since a the pagination links are semantically a list of links:

echo $paginate->displayUl();

Of course, this will display as a vertical list with bullets by default, but by wrapping that output in a div, we can easily apply some clever CSS:

  .pagination ul{
       border-bottom: 1px solid #ccc;
       margin:0;
       padding:1%;
       margin-bottom:2%;
       list-style-type: none;
       background: #f1f1f1;
  }
  .pagination ul li{
       list-style-image: none;
       display:inline;
       padding-left: 1%;
       line-height: 1.2em;
  }

Which will then display output like this:

VIew the code listing.  This code is listed on phpclasses.org.