Perfect PHP Pagination Article

Share this article

Pagination is a topic that has been done to death — dozens of articles and reference classes can be found for the management of result sets … however (and you knew there was a “however” coming there, didn’t you?) I’ve always been disgruntled with the current offerings to date. In this article I offer an improved solution.

Some pagination classes require parameters, such as a database resource and an SQL string or two, to be passed to the constructor. Classes that utilize this approach are lacking in flexibility – what if you require a different formatting of page numbers at the top and bottom of your pages, for example? Do you then have to modify some output function, or subclass the entire class, just to override that one method? These potential “solutions” are restrictive and don’t encourage code reuse.

This tutorial is an attempt to further abstract a class for managing result pagination, thereby removing its dependencies on database connections and SQL queries. The approach I’ll discuss provides a measure of flexibility, allowing the developer to create his or her very own page layouts, and simply register them with the class through the use of an object oriented design pattern known as the Strategy Design Pattern.

What Is the Strategy Design Pattern?

Consider the following: you have on your site a handful of web pages for which the results of a query are paged. Your site uses a function or class that handles the retrieval of your results and the publishing of your paged links.

This is all well and good until you decide to change the layout of the paged links on one (or all) of the pages. In doing so, you’re most likely going to have to modify the method to which this responsibility was delegated.

A better solution would be to create as many layouts as you like, and dynamically choose the one you desire at runtime. The Strategy Design Pattern allows you to do this. In a nutshell, the Strategy Design Pattern is an object oriented design pattern used by a class that wants to swap behavior at run time.

Using the polymorphic capabilities of PHP, a container class (such as the Paginated class that we’ll build in this article) uses an object that implements an interface, and defines concrete implementations for the methods defined in that interface.

While an interface cannot be instantiated, it can reference implementing classes. So when we create a new layout, we can let the strategy or interface within the container (the Paginated class) reference the layouts dynamically at runtime. Calls that produce the paged links will therefore produce a page that’s rendered with the currently referenced layout.

Required Files

As I mentioned, this tutorial is not about the mechanics of how results are paged, but how to use an interface to implement this logic while retaining flexibility. I’ve provided as a starting point a class that contains functionality for registering primitive arrays or objects – the Paginated class — as well as an interface that all of our page layouts must implement (PageLayout) and an implementation for a page layout (DoubleBarLayout). And all the code we’ll use in the article is available for download.

A Basic Example

The following examples use an array of strings. Here’s my data set:

  • Andrew
  • Bernard
  • Castello
  • Dennis
  • Ernie
  • Frank
  • Greg
  • Henry
  • Isac
  • Jax
  • Kester
  • Leonard
  • Matthew
  • Nigel
  • Oscar

However, this code could easily be extended to use an array of integers, characters, or other objects that have been fetched from a previous database call.

Here’s how we’d use the Paginated class:

<?php
require_once "Paginated.php"; 
  
//create an array of names in alphabetic order 
$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", Frank",   Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar"); 
   
$pagedResults = new Paginated($names, 10, 1); 
   
echo "<ul>"; 
   
while($row = $pagedResults->fetchPagedRow()) { 
  echo "<li>{$row}</li>"; 
} 
   
echo "</ul>"; 
?>

First, we include the Paginated class and register an array with the constructor. The constructor takes three arguments, the final two of which are optional:

  1. The first parameter is the array of items to display — as I mentioned, these can be primitive data types or more complex objects.

  2. The second parameter is the number of results we want to display on a page. By default, this figure is set to ten.

  3. The third parameter is the current page number.

In the above example, we’ve used the constant 1 to specify “page 1”, however you’re probably going to want to pass this as a parameter from the query string (more on this later). If an invalid page is supplied to the constructor, the page will default to 1.

By calling the fetchPagedRow method from within the while loop, our code iterates through the array, printing out the first ten names in the list (in this example, “Kester”, “Leonard”, “Matthew”, “Nigel” and “Oscar” would be omitted). These items should be included on page two, but, as the image below illustrates, there are no links to page two yet! While Paginated will manage the access to any object registered by the programmer, the responsibility of publishing paged links is delegated to a class that implements the PageLayout interface.

The rendered list doesn't offer a link to page 2

Let’s add some code to display the page numbers, then we’ll dig a little deeper into the interior workings and flexibility of this class.

Create a new file with a PHP extension that contains the following code:

<?php 
require_once "Paginated.php"; 
require_once "DoubleBarLayout.php"; 
 
//create an array of names in alphabetic order 
$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", "Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar"); 
 
$page = $_GET['page']; 
 
$pagedResults = new Paginated($names, 10, 1); 
 
echo "<ul>"; 
 
while($row = $pagedResults->fetchPagedRow()) { 
  echo "<li>{$row}</li>"; 
} 
 
echo "</ul>"; 
 
$pagedResults->setLayout(new DoubleBarLayout()); 
echo $pagedResults->fetchPagedNavigation(); 
?>

When we run the above script now, we’ll see a list of the first ten names, as well as some additional orientation information shown in the image below. Our script now displays the text “Page 1”, as well as a link to the second page that reads “next >”.

The updated list includes page links

In the code snippet above, we’ve made use of a class called DoubleBarLayout, which implements the interface PageLayout and contains an implementation of the fetchPagedLinks method. This method takes two parameters: the Pagination object, and the query parameters that we want to attach to the hyperlinks (if any).

The great thing about this method is that it takes advantage of PHP’s polymorphic capabilities, allowing the strategy that’s currently registered to be called. It’s therefore important for us to set the strategy first, before calling the method. Setting the strategy is done via a call to the setter method setLayout, which takes as a parameter an object that implements the PageLayout interface.

Hover over one of these links, and you’ll notice that the parameter page and its value of 2 are included in the URL page number. However, in its current state, if you click this link to the second page, the names that we would expect to be rendered will not be displayed.

Let’s see why this is the case by revisiting the constructor of Paginated.

The constructor takes three parameters:

  1. the array of primitive variables or objects to be processed

  2. the number of records to display

  3. the page number

Because the method fetchPagedNavigation writes a query parameter, we can replace our hardcoded value of 1 with the value held in $_GET['page']. This way, if the user manually modifies the value in the URL to something that’s invalid, Paginated will default the page number to 1. How you choose to validate your GET parameters is up to you, though, so I won’t dwell on this issue any further.

Flexibility in Page Layout Schemes

The flexibility of this class is accomplished via the PageLayout interface, which is part of the Paginated object. The PageLayout interface can reference any object that implements it, and calls to the Paginated method fetchPagedNavigation will cause the currently registered object to be referenced. If you haven’t used interfaces before, this may seem a little confusing, but basically the end result is that the correct code will be called, and our results will be correctly spread over multiple pages.

To implement this technique, all you need to do is create a layout strategy that implements the PageLayout interface. Then, provide an implementation for the method fetchPagedLinks.

This method takes two parameters:

  1. $parent, which is the Paginated object

  2. $queryVars, which is the list of query parameters to append to the page numbers (and is optional)

There are three important points to note here:

  1. You’ll never be making direct calls to fetchPagedLinks. All methods of Paginated can be accessed through the parent object.

  2. If you want to use your own page layouts, you must change the layout of the paginated result via calls to setLayout.

With those points in mind, let’s create our own page layout! We’ll call it TrailingLayout. Here’s the code:

<?php 
class TrailingLayout implements PageLayout { 
  
  public function fetchPagedLinks($parent, $queryVars) { 
   
    $currentPage = $parent->getPageNumber(); 
    $totalPages = $parent->fetchNumberPages(); 
    $str = ""; 
  
    if($totalPages >= 1) { 
     
      for($i = 1; $i <= $totalPages; $i++) { 
     
        $str .= " <a href="?page={$i}$queryVars">Page $i</a>"; 
        $str .= $i != $totalPages ? " | " : ""; 
      } 
    } 
  
    return $str; 
  } 
} 
?>

The above class, TrailingLayout, implements the PageLayout interface, and provides implementation for fetchPagedLinks. Remember the parameter $parent is an instance of the Paginated object, so we can determine the current page, and the total number of pages, by making calls to getPageNumber and fetchNumberPages, respectively.

In this simple layout, once there’s more than a single page, the script will loop through the array of pages, and create a hyperlink and page number for each one. The $queryVars are also written to the href as part of this loop; the parameter $queryVars comes in handy when we’re paging the results of a search that may have included a few parameters.

Note that the string "Page" is not a part of the queryVars, but is written by the loop that appends the page numbers to the string.

Now let’s try implementing our new layout:

<?php 
require_once "Paginated.php"; 
//include your customized layout 
require_once "TrailingLayout.php"; 
  
//create an array of names in alphabetic order. A database call could have retrieved these items 
$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", "Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar"); 
  
$page = $_GET['page']; 
  
$pagedResults = new Paginated($names, 10, $page); 
  
echo "<ul>"; 
  
while($row = $pagedResults->fetchPagedRow()) { 
  echo "<li>{$row}</li>"; 
} 
 
echo "</ul>"; 
  
//$pagedResults->setLayout(new TrailingLayout()); 
echo $pagedResults->fetchPagedNavigation("&firstLetter=l"); 
?>

If we were to run the script above as is, we’d get the following error message:

"Fatal error: Call to a member function fetchPagedLinks() on a non-object".

This error occurs because we haven’t yet registered the strategy that we wish to use before calling fetchPagedNavigation. To change the layout of the paged links, we pass to the setLayout method a parameter, which can be any object that implements the PageLayout interface. In the code from our TrailingLayout example above, uncomment the second-last line of PHP code, and refresh your page to see the final result, shown below.

The finished list

The last line in that code demonstrates how the fetchPagedNavigation method can take an optional parameter string to define the rest of the query (note the inclusion of the ampersand before the firstLetter parameter in the URL to distinguish it from the page parameter).

Summary

In this article, I introduced the Strategy Design Pattern, which can be used to provide flexibility when you’re laying out paged links.

We saw this pattern in action through the Paginated class, which will hopefully prove useful to you when you’re displaying data across multiple pages. The class can be used to display arrays containing primitive data types or more complex objects.

Frequently Asked Questions on PHP Pagination

How can I create a simple PHP pagination?

Creating a simple PHP pagination involves a few steps. First, you need to connect to your database using PHP’s mysqli_connect() function. Then, you need to write a SQL query to fetch data from your database. This query should include the LIMIT clause to limit the number of records returned. Next, you need to calculate the total number of pages by dividing the total number of records by the number of records per page. Finally, you need to create links for each page and display the data for the current page. Remember to always sanitize your data to prevent SQL injection attacks.

How can I customize the appearance of my PHP pagination?

You can customize the appearance of your PHP pagination using CSS. You can style the pagination links, the current page link, and the disabled links (if any). You can also add a “Previous” and “Next” button for easier navigation. Remember to use descriptive class names for your CSS selectors to make your code easier to understand and maintain.

How can I handle large datasets with PHP pagination?

Handling large datasets with PHP pagination can be challenging because fetching all records at once can be slow and consume a lot of memory. One solution is to use the SQL_CALC_FOUND_ROWS option in your SQL query. This option tells MySQL to calculate the total number of records as if there was no LIMIT clause, but without actually fetching all records. Then you can use the FOUND_ROWS() function to get this number. This approach can significantly improve performance when dealing with large datasets.

How can I add search functionality to my PHP pagination?

You can add search functionality to your PHP pagination by modifying your SQL query to include a WHERE clause. The WHERE clause should compare the search term entered by the user with the relevant fields in your database. Remember to use the LIKE operator for partial matches and to sanitize the search term to prevent SQL injection attacks.

How can I add sorting functionality to my PHP pagination?

You can add sorting functionality to your PHP pagination by modifying your SQL query to include an ORDER BY clause. The ORDER BY clause should specify the field to sort by and the sort direction (ASC for ascending order, DESC for descending order). You can also let the user choose the sort field and direction by passing them as parameters in the URL.

How can I handle errors in my PHP pagination?

You can handle errors in your PHP pagination by using PHP’s error handling functions. For example, you can use the mysqli_error() function to get a description of the last error that occurred. You should display a user-friendly error message and log the error for debugging purposes.

How can I optimize my PHP pagination for SEO?

You can optimize your PHP pagination for SEO by using the rel=”next” and rel=”prev” link attributes. These attributes tell search engines that the pages in your pagination are part of a series, which can help them understand the structure of your site and index your content more effectively.

How can I make my PHP pagination responsive?

You can make your PHP pagination responsive by using CSS media queries. Media queries allow you to apply different styles depending on the screen size. For example, you can reduce the size of the pagination links on small screens to make them fit better.

How can I test my PHP pagination?

You can test your PHP pagination by creating a test database with a known number of records. Then you can check if the correct number of pages is calculated and if the correct data is displayed for each page. You should also test edge cases, such as the first and last page, and pages with no data.

How can I improve the performance of my PHP pagination?

You can improve the performance of your PHP pagination by using efficient SQL queries, limiting the number of records fetched, and using caching. Caching involves storing the results of expensive operations, such as database queries, and reusing them when the same operation is performed again. This can significantly reduce the load on your database and improve the response time of your site.

Lyndon BaptisteLyndon Baptiste
View Author

Lyndon is a 24 year-old who has been involved in programming for the past 6 years and currently works as a web developer for the University of the West Indies, where he uses the LAMP architecture. His hobbies include maximizing the reusability of code through abstraction and the usage of Object Oriented Design patterns.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week