Simplify pagination logic using a custom Zend_Paginator_Adapter

Pagination logic is something that I've found myself redoing a number of times over the years, and each time it's been a relatively fiddly and painful process.

This time around I decided to check out the Zend_Paginator component from the Zend Framework, and found the process useful enough to share! In my case I was using Doctrine to retrieve data from the database. I'll skip most of the Doctrine-specific stuff, however, as hopefully this will end up as a decent example of how to integrate Paginator with other non-Zend libraries.

When the Paginator is instanced, it's given an instance of an appropriate Adapter and told what the current page is:

<?php
$paginator 
=  new Zend_Paginator($adapter);
$paginator->setItemCountPerPage(20);
$paginator->setCurrentPageNumber(2);

It can then be used in a View using the Paginator View Helper:

<?php
echo $this->paginationControl(
    
$paginator
    
'All'
    
'my_pagination_control.phtml'
);

The view helper is given an instance of the paginator, a pagination style, and a view script to use to render the pagination. Zend give a few example view scripts in their documentation. They're fairly simple in that they access a straightforward API to see which page would be next, previous, and which pages are in the current range.

So, what I needed to do was write an Adapter to work with Doctrine queries. I wanted it to take a query as a parameter and then paginate across the set of results. This turned out to be pretty simple - the Zend_Paginator_Adapter_Interface that I needed to implement only had 2 methods, count() and getItems(). The finished Adapter looks like this:

<?php
class My_Paginator_Adapter_DoctrineQuery 
    
implements Zend_Paginator_Adapter_Interface
{

    protected 
$_query;
    protected 
$_count_query;
    
    public function 
__construct($query)
    {
        
$this->_query $query;
        
$this->_count_query = clone $query;
    }
    
    public function 
getItems($offset$itemsPerPage)
    { 
        return 
$this->_query
            
->limit($itemsPerPage)
            ->
offset($offset)
            ->
execute();
    }
    
    public function 
count()
    { 
        return 
$this->_count_query->count();
    }
    
}

The count() method of course just returns the total results the query would return if run without constraints, while the getItems() method returns the items on the specified page.

Putting it all together, the code in my Action looks like this:

<?php
$query 
Doctrine_Query::create()
    ->
from('SomeTable')
    ->
where('someField = ?'12);
$adapter = new My_Paginator_Adapter_DoctrineQuery($query);

$paginator =  new Zend_Paginator(
    new 
My_Paginator_Adapter_DoctrineQuery($query));
$paginator->setItemCountPerPage(20);
$paginator->setCurrentPage($this->_request->getParam('page'));

$this->view->paginator $paginator;

In the view I use the paginator to render out the pagination widget, but also for the view to obtain the objects it wants:

<?php
echo $this->paginationControl($this->paginator'All''my_pagination.phtml');
$items $this->paginator->getCurrentPageItems();
foreach(
$items as $item) {
    
// ...
}

And a very simplified pagination partial would look like this:


<?php if (isset($this->previous)){ ?>
  <a href="<?php echo $this->url(array('page' => $this->previous)); ?>">
    prev
  </a>
<?php ?>
<?php 
foreach ($this->pagesInRange as $page){ ?>
    <a href="<?php echo $this->url(array('page' => $page)); ?>">
        <?php echo $page?>
    </a> 
<?php ?>
<?php 
if (isset($this->next)){ ?>
  <a href="<?php echo $this->url(array('page' => $this->next)); ?>">
  next
  </a>
<?php ?>

Overall I like this way of working, and certainly it was easier to make a new Adapter than I'd feared. One future possibility is of making individual adaptors for specific queries, as a way of removing the query logic from my contoller.

Bookmark and Share

Comments

1.

Awsome!. This may come very helpfull, now that Doctrine is a excelent tool to access database.

DavidZB
10th March 2010, 01:47

Add a comment