Just when you thought I had run out of material to write about I am back! I have a few topics still lined up, but I have been so busy that I haven't had time to write anything.
The project I have been working on at work has lots of pagination in it, and the other day I was really frustrated with how difficult it was to do simple pagination and how many lines of code it took in the controller. Here I am going to present to you a pagination class I wrote and a usage example.
I am aware that there is a Zend_Pagination class in Zend Framework, but I'm not crazy about it. It offers some nice view helpers to draw pagination on a page, however, it is over 900 lines long, contains a lot of code you probably will never need, and is missing simple functions that you may need such as determining the page offset or the first and last item on the current page.
Here is the class I have written in its entirety (all of 237 lines including comments):
<?php
class Pager
{
protected $_totalItems;
protected $_totalPages;
protected $_pageSize;
protected $_currentPage;
protected $_firstOnPage;
protected $_lastOnPage;
public function __construct($page = 1, $pageSize = 10)
{
if ($page < 1) {
throw new Exception('page cannot be less than 1!');
}
$this->_currentPage = $page;
if ($pageSize < 1) {
throw new Exception('page size cannot be less than 1!');
}
$this->_pageSize = $pageSize;
}
public function setTotalItems($number)
{
$this->_totalItems = $number;
$this->_firstOnPage = $this->getOffset() + 1;
$this->_totalPages = ceil($number / $this->_pageSize);
if ($number > $this->_firstOnPage &&
$this->_totalPages > $this->_currentPage) {
$this->_lastOnPage = $this->getOffset() + $this->_pageSize;
return;
}
$this->_lastOnPage = $number;
}
public function getOffset()
{
return ($this->_currentPage - 1) * $this->_pageSize;
}
public function hasPrevious()
{
return ($this->_currentPage - 1) > 0;
}
public function hasNext()
{
return ($this->_currentPage + 1) <= $this->_totalPages;
}
public function getPrevious()
{
if ($this->hasPrevious()) {
return $this->_currentPage - 1;
}
return false;
}
public function getNext()
{
if ($this->hasNext()) {
return $this->_currentPage + 1;
}
return false;
}
public function getCurrentPageSize()
{
return $this->_lastOnPage - $this->_firstOnPage + 1;
}
public function getPageSize()
{
return $this->_pageSize;
}
public function getTotalPages()
{
return $this->_totalPages;
}
public function getFirstOnPage()
{
return $this->_firstOnPage;
}
public function getLastOnPage()
{
return $this->_lastOnPage;
}
public function getCurrentPage()
{
return $this->_currentPage;
}
public function getTotalItems()
{
return $this->_totalItems;
}
public function hasContent()
{
return $this->_currentPage <= $this->_totalPages;
}
}
Keep in mind this is a rough draft, but it seems to work pretty well. All you need to tell the class is 3 things:
- The current page number in the constructor
- The number of items you would like to see per page in the constructor
- The total number of items that you will be paginating in the setTotalItems() method
Now here is an example of this class in action (controller):
$pager = new Pager($_GET['page'], 15);
$dao = new User_Dao();
$users = $dao->getAll($pager);
Here is the method in the User_Dao to get the users from the database:
public function getAll(Pager $pager)
{
$query = '/* ' . __METHOD__ . ' */' .
"SELECT SQL_CALC_FOUND_ROWS
u.id id,
u.email email
FROM user u
ORDER BY u.id
LIMIT $pager->getPageSize()
OFFSET $pager->getOffset()";
$sth = $this->_db->prepare($query);
$sth->execute();
$records = $sth->fetchAllAssoc();
$query = '/* ' . __METHOD__ . ' */' .
"SELECT FOUND_ROWS() as total_records";
$totalStatement = $this->_db->prepare($query);
$totalStatement->execute();
$record = $totalStatement->fetchAssoc();
$pager->setTotalItems($record['total_records']);
return $this->_buildCollection($records);
}
There are a few things to note here. SQL_CALC_FOUND_ROWS is a totally awesome mysql function that calculates the number of rows that would be returned if you did not include a LIMIT on the query. This is what is used to tell the pager how many total items there are. Also the DAO here is returning a collection. To see how that works check out this post I wrote a little while ago.
Since PHP passes objects by reference by default we can now use the same Pager object that we instantiated in our controller in our view. That would look something like this:
<h2>Page <?php echo $pager->getCurrentPage(); ?> of <?php echo $pager->getTotalPages(); ?></h2>
<h3>
Displaying <?php echo $pager->getFirstOnPage(); ?> —
<?php echo $pager->getLastOnPage(); ?> of <?php echo $pager->getTotalItems(); ?> Users
</h3>
<?php foreach ($users as $user): ?>
<dl>
<dt>id</dt>
<dd><?php echo $user->id; ?></dd>
<dt>email</dt>
<dd><?php echo $user->email; ?></dd>
</dl>
<?php endforeach; ?>
<?php if ($pager->hasPrevious()): ?>
<p><a href="/view-users?page=<?php echo $pager->getPrevious(); ?>">previous</a></p>
<?php endif; ?>
<?php if ($pager->hasNext()): ?>
<p><a href="/view-users?page=<?php echo $pager->getNext(); ?>">next</a></p>
<?php endif; ?>
I'm probably forgetting something here, but I think it is a good start and in my opinion it certainly eliminates any headaches caused from having to deal with pagination.