Paging and Sorting Data with Zend Framework, Doctrine and PEAR (part 2)

December 30, 2009

Tutorials, Zend Framework

Paging and Sorting Data with Zend Framework, Doctrine and PEAR (part 2)

Page Boy

In the previous segment of this article, I introduced you to the Zend_Paginator class, which provides a flexible API for paginating any data collection, whether it is expressed as an array or a database result set. However, Zend_Paginator isn’t the only game in town. There are a number of other open-source PHP pagination widgets, which can be used as an alternative to Zend_Paginator.

This article will explore two such alternatives, the PEAR Pager class and the Doctrine Pager class, and give you a crash course in how you can use them to quickly add paging and sorting features to your PHP application.

A Fruity Flavor

First up, PEAR Pager. Like Zend_Paginator, this one too can handle arrays and database result sets. Here’s an illustrative example:

<?php
// include class
require_once 'Pager/Pager.php';

try {  
  // connect to database and get data set
  $dbh = new PDO('mysql:host=localhost;dbname=world', 'root', '');
  $sql = "SELECT ID, Name, CountryCode, District, Population FROM city WHERE ID < 500";
  $sth = $dbh->prepare($sql);
  $sth->execute();
  $data = $sth->fetchAll(PDO::FETCH_NUM);
  unset($dbh);
    
  // set number of items per page from request 
  $itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 20;
  
  // set pager options
  $params = array(
      'mode'     => 'Sliding',
      'perPage'  => $itemsPerPage,
      'delta'    => 2,
      'itemData' => $data
  );
  
  // generate pager object
  $pager =& Pager::factory($params);
  
  // get links for current page and print
  $links = $pager->getLinks();
} catch(Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}  
?>


<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        

      <?php foreach ($pager->getPageData() as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item[0]); ?></td>
          <td><?php echo htmlentities($item[1]); ?></td>
          <td><?php echo htmlentities($item[2]); ?></td>
          <td><?php echo htmlentities($item[3]); ?></td>
          <td><?php echo htmlentities($item[4]); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo $links['all']; ?>
    </div>
  </body>
</html>

This script begins by including the Pager class file, and initializing an array named $data with the complete set of values that need to be paged through. An instance of the Pager class is then created via the class’ factory() method, which is passed an associative array of configuration parameters. Here’s what each of them does:

  • The 'mode' parameter tells the Pager class whether the getLinks() method should generate page links in “jumping” or “sliding” mode.
  • The 'perPage' parameter indicates how many elements from the data set are to be displayed per page of data.
  • The 'itemData' parameter specifies the data set to be paged through.
  • The 'delta' parameter controls how many page numbers appear in the getLinks() output.

Once an instance of the Pager class is initialized, the getPageData() method returns a subset of items for the current page, while the getLinks() method returns an array of links to the first, last, previous and next pages. This getLinks() method is important because, unlike with Zend_Paginator, it automatically generates all the HTML for the page navigation links, as an array. To display these links in the view, you need simply output the contents of this array’s 'all' key.

Here’s what the output looks like:

Of course, as explained previously, this is a terribly inefficient approach to take when working with database result sets. A better approach is to only retrieve the records for the page being requested. However, unlike Zend_Paginator, the PEAR Pager class doesn’t include this feature by default. So, you need to either perform two queries, one to count the total number of records and the other to retrieve the records corresponding to the selected page, or you can download the separate Pager_Wrapper class, which wraps around the Pager class and does this for you automatically. Here’s an example of the former approach:

<?php
// include class
require_once 'Pager/Pager.php';

try {  
  // connect to database and get data set
  $dbh = new PDO('mysql:host=localhost;dbname=world', 'root', '');

  $sql = "SELECT COUNT(*) FROM city WHERE ID < 500";
  $sth = $dbh->prepare($sql);
  $sth->execute();
  $row = $sth->fetch(PDO::FETCH_NUM);
  $count = $row[0];
  
  // set number of items per page from request 
  $itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 20;

  // calculate required start offset from current page number
  $currentPage = isset($_GET['pageID']) ? (int) htmlentities($_GET['pageID']) : 1;
  $startOffset = (int) ($currentPage-1) * $itemsPerPage;

  // now execute query with LIMIT clause:
  // get current page number
  // get record offsets for LIMIT clause
  $limitSql = "SELECT ID, Name, CountryCode, District, Population FROM city WHERE ID < 500 LIMIT $startOffset, $itemsPerPage";
  $sth = $dbh->prepare($limitSql);
  $sth->execute();
  $data = $sth->fetchAll(PDO::FETCH_NUM);
  unset($dbh);   
  
  // set pager options
  $params = array(
      'mode'       => 'Sliding',
      'perPage'    => $itemsPerPage,
      'delta'      => 2,
      'totalItems' => $count,
  );

  // generate pager object
  $pager =& Pager::factory($params);
  
  // get links for current page and print
  $links = $pager->getLinks();
    
  // set pager options
  $params['itemData'] = $data;

  // regenerate pager object
  $pager =& Pager::factory($params);  
} catch(Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}  
?>


<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        

      <?php foreach ($pager->getPageData() as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item[0]); ?></td>
          <td><?php echo htmlentities($item[1]); ?></td>
          <td><?php echo htmlentities($item[2]); ?></td>
          <td><?php echo htmlentities($item[3]); ?></td>
          <td><?php echo htmlentities($item[4]); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo $links['all']; ?>
    </div>
  </body>
</html>

In this listing, a SELECT COUNT(*) query is first used to determine the total number of records in the result set, and this value is assigned to the $count variable. The value of the $_GET['pageID'] variable is then used to calculate the starting offset for the LIMIT clause, and a second SQL query is executed with this LIMIT clause, to return only the subset of records relevant for the current page. The Pager object is actually used twice in this listing: first to produce the set of page links taking into account the total number of matching records, and then again to manage the record set relevant to the selected page.

This is clearly not the most elegant solution, and so here’s an example of doing this with the Pager_Wrapper class:

<?php
// include classes
require_once 'Pager/Pager_Wrapper.php';
require_once 'MDB2.php';

try {  
  // set number of items per page from request 
  $itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 20;

  // create database object
  $dsn = 'mysql://root@localhost/world';
  $db =& MDB2::factory($dsn);
    
  // generate pager object
  $sql = "SELECT ID, Name, CountryCode, District, Population FROM city WHERE ID < 500";
  
  // set pager options
  $params = array(
      'mode'     => 'Sliding',
      'perPage'  => $itemsPerPage,
      'delta'    => 2,
      'itemData' => $data
  );
  
  // create pager wrapper and get paged data  
  $pager = Pager_Wrapper_MDB2($db, $sql, $params, false, MDB2_FETCHMODE_NUM);
} catch(Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}  
?>

<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        

      <?php foreach ($pager['data'] as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item[0]); ?></td>
          <td><?php echo htmlentities($item[1]); ?></td>
          <td><?php echo htmlentities($item[2]); ?></td>
          <td><?php echo htmlentities($item[3]); ?></td>
          <td><?php echo htmlentities($item[4]); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo $pager['links']; ?>
    </div>
  </body>
</html>

You'll find more information on this wrapper class, as well as a detailed explanation of the manual calculation approach, in this Pager tutorial by Lorenzo Alberton, one of the PEAR Pager maintainers.

Of Form And Function

The Pager class also comes with some useful HTML widgets, which make it possible to replace or enhance the standard set of page links with some additional tools. For example, there are drop-down list boxes that provide the user with a shortcut to quickly select another page, or the items displayed per page; this is useful when there are a lot of pages to get through and it isn't possible to display links to all of them due to space or other constraints.

Consider the following example, which illustrates:

<?php
// include class
require_once 'Pager/Pager.php';

try {  
  // connect to database and get data set
  $dbh = new PDO('mysql:host=localhost;dbname=world', 'root', '');
  $sql = "SELECT ID, Name, CountryCode, District, Population FROM city WHERE ID < 500";
  $sth = $dbh->prepare($sql);
  $sth->execute();
  $data = $sth->fetchAll(PDO::FETCH_NUM);
  unset($dbh);
    
  // set pager options
  $params = array(
      'mode'     => 'Sliding',
      'delta'    => 2,
      'itemData' => $data
  );
  
  // generate pager object
  $pager =& Pager::factory($params);  
} catch(Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}  
?>


<html>
  <head></head>
  <body>
  
    <div id="selector">
      <form action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>" method="get"> <br />
      Jump to page: <br />
      <?php echo $pager->getPageSelectBox(); ?> <br />  
    
      Items per page: <br />
      <?php echo $pager->getPerPageSelectBox(25, 100, 25, false); ?> <br />
      <input type="submit" value="submit" />
      </form>
    </div>
  
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        

      <?php foreach ($pager->getPageData() as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item[0]); ?></td>
          <td><?php echo htmlentities($item[1]); ?></td>
          <td><?php echo htmlentities($item[2]); ?></td>
          <td><?php echo htmlentities($item[3]); ?></td>
          <td><?php echo htmlentities($item[4]); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
  </body>
</html>

Here, the getPageSelectBox() method generates an HTML select box, which contains a list of all the page numbers for the current Pager. Selecting any of these immediately jumps to the corresponding page. Similarly, the getPerPageSelectBox() generates an HTML select box, which contains possible values for the number of items to be displayed per page. The first three parameters passed to the getPerPageSelectBox() method are, in order, the starting value, the ending value, and the step value.

Here's an example of what the output looks like:

Off The Grid

How about sorting with PEAR Pager? Well, you can do it the way it was described earlier, by manually appending sorting parameters to the URL as GET variables. Or, you can reach for the PEAR Structures_DataGrid package, which automatically produces a sortable, paginated data grid for you with minimal fuss. Here's an example:

<?php
// include class
require_once 'Structures/DataGrid.php';

// set number of items per page from request 
$itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 20;

// create new datagrid
$dg = new Structures_DataGrid($itemsPerPage);

// bind datagrid using MDB2 driver   
$ds_options = array(
  'dsn' => 'mysql://root@localhost/world',
);
$sql = "SELECT ID, Name, CountryCode, District, Population FROM city WHERE ID < 500";
$dg->bind($sql, $ds_options, 'MDB2');

// render sort form
$hsf_options = array(
  'sortFieldsNum'     => 1,
  'directionStyle' => 'radio',
  'textSubmit'     => 'Sort Grid'
);
$dg->render('HTMLSortForm', $hsf_options);

// render datagrid table
$dg->render();

// render pager links
$p_options = array(
  'pagerOptions' => array(
    'mode' => 'Sliding',
    'delta' => 1,
    'httpMethod' => 'GET',
    'altFirst' => 'First page',
    'altPrev'=> 'Previous page',
    'altNext' => 'Next page',
    'altLast' => 'Last page',
    'separator' => '',
    'spacesBeforeSeparator' => 1,
    'spacesAfterSeparator' => 1,
    'prevImg' => '&lsaquo;',
    'nextImg' => '&rsaquo;',
    'firstPageText' => '&laquo;',
    'lastPageText' => '&raquo;',
  )
);
$dg->render('Pager', $p_options);
?>

Here's what the output looks like:

A Doctrine In Every Page

Doctrine is a popular object-relational mapper (ORM) that's used in many PHP projects as an alternative to Zend_DB, PEAR MDB2 or Propel. It follows the ActiveRecord pattern, and is capable of auto-generating models compliant with this pattern from an existing database schema. The examples below assume that you have already created a set of Doctrine models for the example database and added them to the PHP include path; if this is not the case, visit the relevant section of the Doctrine manual, which will guide you through the process.

In addition to its own Doctrine Query Language (DQL) and Doctrine_Record implementation, Doctrine also comes with a fairly powerful and flexible Doctrine_Pager component. This component integrates tightly with the rest of the Doctrine package, making it possible to create paged queries and return a complete set of page navigation controls in either "sliding" or "jumping" modes.

Here's an example of using the Doctrine_Pager component:

<?php
// include main Doctrine class file
// set up Doctrine auto-loader
require_once 'Doctrine.php';
spl_autoload_register(array('Doctrine', 'autoload'));

// create Doctrine manager
$manager = Doctrine_Manager::getInstance();

// create database connection
$conn = Doctrine_Manager::connection('mysql://root@localhost/world', 'doctrine');

// create query
$query = Doctrine_Query::create()
      ->from('City c')
      ->where('c.ID < 500');
      
// set page number from request
$currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;

// set number of items per page from request
$itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 20;

// initialize pager
$pager = new Doctrine_Pager($query, $currentPage, $itemsPerPage);

// execute paged query
$result = $pager->execute(array(), Doctrine::HYDRATE_ARRAY);            

// initialize pager layout
$layout = new Doctrine_Pager_Layout(
  $pager, 
  new Doctrine_Pager_Range_Sliding(array('chunk' => 5)), 
  "?p={%page}&c=$itemsPerPage"
);

// set page link display template
$layout->setTemplate('<a href="{%url}">{%page}</a>');
$layout->setSeparatorTemplate(' | ');
?>

<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        

      <?php foreach ($result as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item['ID']); ?></td>
          <td><?php echo htmlentities($item['Name']); ?></td>
          <td><?php echo htmlentities($item['CountryCode']); ?></td>
          <td><?php echo htmlentities($item['District']); ?></td>
          <td><?php echo htmlentities($item['Population']); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo $layout->display(null, true); ?>
    </div>
  </body>
</html>

The first few lines of this script simply include the main Doctrine class, and register the Doctrine auto-loader with PHP. This auto-loader takes care of loading other Doctrine components as needed. Then, the Doctrine_Manager component is used to open up a connection to the target database, and the Doctrine_Query component is used to programmatically generate a query string.

So far, so normal, but this is where it gets interesting. The next step is to initialize an instance of the Doctrine_Pager component, and pass the object constructor the Doctrine query object, the current page, and the number of items required per page. This Doctrine_Pager component then takes care of executing a paged query and retrieving only the relevant subset of data for the requested page.

What about the page links? Well, Doctrine also has a Doctrine_Pager_Layout component, which is responsible for the appearance of page navigation controls. This component's object constructor accepts three arguments: the Doctrine_Pager instance, an object representing the paging mode, and the URL request string to use for page navigation links. It also exposes some convenience methods, which can be used to define the HTML code used when generating page links. The '%page' placeholder is automatically replaced with the correct page number at render-time.

Here's an example of what the output looks like:

In case you're wondering why I skipped so quickly over the Doctrine_Manager and Doctrine_Record components...a complete explanation of these components is not possible in this article, but if you're curious, you'll find detailed information on these and other Doctrine components in the Doctrine manual.

Kinda, Sorta, Maybe

It's also quite easy to revise the previous example to support sorting, by updating the URL template passed to Doctrine_Pager_Layout and including additional sorting parameters as GET variables. Here's the revised code:

<?php
// include main Doctrine class file
require_once 'Doctrine.php';
spl_autoload_register(array('Doctrine', 'autoload'));

// set sort parameters from request
$sortField = isset($_GET['s']) ? htmlentities($_GET['s']) : 'ID';
$sortDir = isset($_GET['d']) ? htmlentities($_GET['d']) : 'asc';

// create Doctrine manager
$manager = Doctrine_Manager::getInstance();

// create database connection
$conn = Doctrine_Manager::connection('mysql://root@localhost/world', 'doctrine');

// create query
$query = Doctrine_Query::create()
      ->from('City c')
      ->where('c.ID < 500')
      ->orderBy("$sortField $sortDir");
      
// set page number from request
$currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;

// set number of items per page from request
$itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 20;

// initialize pager
$pager = new Doctrine_Pager($query, $currentPage, $itemsPerPage);

// execute paged query
$result = $pager->execute(array(), Doctrine::HYDRATE_ARRAY);            

// initialize pager layout
$layout = new Doctrine_Pager_Layout(
  $pager, 
  new Doctrine_Pager_Range_Sliding(array('chunk' => 5)), 
  "?p={%page}&c=$itemsPerPage&s=$sortField&d=$sortDir"
);

// set page link display template
$layout->setTemplate('<a href="{%url}">{%page}</a>');
$layout->setSeparatorTemplate(' | ');


function getLink($page, $itemsPerPage, $sortField, $sortDir, $label) {
  $q = http_build_query(array(
      'p' => $page, 
      'c' => $itemsPerPage,
      's' => $sortField,
      'd' => $sortDir,
    )      
  );  
  return "<a href=\"?$q\">$label</a>";  
}
?>

<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID
            <?php echo getLink($currentPage, $itemsPerPage, 'ID', 'asc', '&uArr;'); ?> 
            <?php echo getLink($currentPage, $itemsPerPage, 'ID', 'desc', '&dArr;'); ?>
          </th>
          <th>Name 
            <?php echo getLink($currentPage, $itemsPerPage, 'Name', 'asc', '&uArr;'); ?> 
            <?php echo getLink($currentPage, $itemsPerPage, 'Name', 'desc', '&dArr;'); ?>
          </th>
          <th>Country Code 
            <?php echo getLink($currentPage, $itemsPerPage, 'CountryCode', 'asc', '&uArr;'); ?> 
            <?php echo getLink($currentPage, $itemsPerPage, 'CountryCode', 'desc', '&dArr;'); ?>
          </th>
          <th>District 
            <?php echo getLink($currentPage, $itemsPerPage, 'District', 'asc', '&uArr;'); ?> 
            <?php echo getLink($currentPage, $itemsPerPage, 'District', 'desc', '&dArr;'); ?>
          </th>
          <th>Population 
            <?php echo getLink($currentPage, $itemsPerPage, 'Population', 'asc', '&uArr;'); ?> 
            <?php echo getLink($currentPage, $itemsPerPage, 'Population', 'desc', '&dArr;'); ?>
          </th>
        </tr>        

      <?php foreach ($result as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item['ID']); ?></td>
          <td><?php echo htmlentities($item['Name']); ?></td>
          <td><?php echo htmlentities($item['CountryCode']); ?></td>
          <td><?php echo htmlentities($item['District']); ?></td>
          <td><?php echo htmlentities($item['Population']); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo $layout->display(null, true); ?>
    </div>
  </body>
</html>

And here's what the output looks like:

X Marks The Spot

Why stop at paginating database results? Zend_Paginator and PEAR Pager can also be used for other applications, such as paginating XML-encoded content. To illustrate, consider the following example XML document, which contains city information marked up with XML:

<?xml version='1.0'?>
<data>  
  <city>
    <id>1</id>
    <name>Kabul</name>
    <district>AFG</district>
    <code>Kabol</code>
    <population>1780000</population>
  </city>
  <city>
    <id>2</id>
    <name>Qandahar</name>
    <district>AFG</district>
    <code>Qandahar</code>
    <population>237500</population>
  </city>
  <city>
    <id>3</id>
    <name>Herat</name>
    <district>AFG</district>
    <code>Herat</code>
    <population>186800</population>
  </city>
  ...
</data>

To paginate this data with Zend_Paginator, use SimpleXML to access the set of <city> elements and rewrite them into an array. This array can then be passed through to Zend_Paginator using the standard array adapter. Here's the code:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';

// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();

try {  
  // check for XML file
  // load into DOM document if available
  $xml = simplexml_load_file('data.xml');
  
  // get all <city> elements
  // add to $data array
  foreach ($xml->city as $c) {
    $data[] = $c;  
  }
  
  // initialize pager with data set
  $pager = new Zend_Paginator(new Zend_Paginator_Adapter_Array($data));
  
  // set page number from request
  $currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;
  $pager->setCurrentPageNumber($currentPage);
  
  // set number of items per page from request
  $itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 5;
  $pager->setItemCountPerPage($itemsPerPage);
  
  // get page data
  $pages = $pager->getPages();
  
  // create page links
  $pageLinks = array();
  $separator = ' | ';
  for ($x=1; $x<=$pages->pageCount; $x++) {
    if ($x == $pages->current) {
      $pageLinks[] = $x;      
    } else {
      $q = http_build_query(array('p' => $x, 'c' => $itemsPerPage));
      $pageLinks[] = "<a href=\"?$q\">$x</a>";  
    }  
  } 
} catch(Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}    
?>

<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        
      <?php foreach ($pager->getCurrentItems() as $item): ?>
        <tr> 
          <td><?php echo htmlentities($item->id); ?></td>
          <td><?php echo htmlentities($item->name); ?></td>
          <td><?php echo htmlentities($item->code); ?></td>
          <td><?php echo htmlentities($item->district); ?></td>
          <td><?php echo htmlentities($item->population); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo implode($pageLinks, $separator); ?>
    </div>
  </body>
</html>

A similar approach can be followed with PEAR Pager, by passing the array of SimpleXML objects to the Pager constructor through the 'itemData' options array key. Here's the corresponding code:

<?php
// include class
require_once 'Pager/Pager.php';

try {  
  // check for XML file
  // load into DOM document if available
  $xml = simplexml_load_file('data.xml');
  
  // get all <city> elements
  // add to $data array
  foreach ($xml->city as $c) {
    $data[] = $c;  
  }
  
  // set pager options
  $params = array(
      'mode'     => 'Sliding',
      'perPage'  => 5,
      'delta'    => 1,
      'itemData' => $data
  );
  
  // generate pager object
  $pager =& Pager::factory($params);
  
  // get links for current page and print
  $links = $pager->getLinks();
} catch(Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}  
?>


<html>
  <head></head>
  <body>
    <div id="data">
      <table border="1">
        <tr> 
          <th>ID</th>
          <th>Name</th>
          <th>Country Code</th>
          <th>District</th>
          <th>Population</th>
        </tr>        

      <?php foreach ($pager->getPageData() as $item) : ?>
        <tr> 
          <td><?php echo htmlentities($item->id); ?></td>
          <td><?php echo htmlentities($item->name); ?></td>
          <td><?php echo htmlentities($item->code); ?></td>
          <td><?php echo htmlentities($item->district); ?></td>
          <td><?php echo htmlentities($item->population); ?></td>
        </tr>        
      <?php endforeach; ?>
      </table>
    </div>

    <br/>
    
    <div id="links">
    Pages: <?php echo $links['all']; ?>
    </div>
  </body>
</html>

And here's an example of what the output looks like:

And that's about it for the moment. Over the course of this and the previous article, I took you on a whirlwind tour of three commonly used pagination components: Zend_Paginator, Doctrine_Pager and PEAR Pager. I showed you the basics of paging static arrays, database result sets and XML node collections. I also showed you how to customize the page links generated by the different components, and how to enhance the basic built-in pagination features with additional sorting capabilities. Hopefully, all of this information will come in useful the next time you sit down to add paging and sorting to a Web application.

Copyright Melonfire, 2009. All rights reserved.

About Vikram Vaswani

Vikram Vaswani is the founder and CEO of "Melonfire":http://www.melonfire.com/, a consultancy specializing in open-source tools and technologies. He is a passionate proponent of the open-source movement and frequently contributes articles and tutorials on open-source technologies, including Perl, Python, PHP, MySQL, and Linux, to the community at large. He is the author of four books on PHP and MySQL, including "MySQL: The Complete Reference":http://www.mysql-tcr.com/, "How to Do Everything with PHP and MySQL":http://www.everythingphpmysql.com/ and "PHP Programming Solutions":http://www.php-programming-solutions.com/. Vikram has more than eight years of experience working with PHP and MySQL as an application developer. He is the author of Zend Technologies' "PHP 101 series":http://devzone.zend.com/tag/PHP101 for PHP beginners, and has extensive experience deploying PHP in a variety of different environments (including corporate intranets, high-traffic Internet Web sites, and mission-critical thin client applications). A Felix Scholar at the University of Oxford, England, Vikram combines his interest in Web application development with various other activities. When not dreaming up plans for world domination, he amuses himself by reading crime fiction, watching old movies, playing squash, blogging, and keeping an eye out for unfriendly Agents.

View all posts by Vikram Vaswani

4 Responses to “Paging and Sorting Data with Zend Framework, Doctrine and PEAR (part 2)”

  1. netsuvi Says:

    some MVC and ZEND_DB would make this more elegant and the code much shorter.
    Redundandecy would be gone.

    It would be cool to have a nice ZEND_HTML_Table for doing such things
    like displaying and sorting.

    Don’t forgett Zebra style. This can also be done easliy with this:

    <table>
    <?php $background = "white";
    $i=0; ?>
    <?php foreach($this->allitmes as $item) : ?>
    <tr bgcolor=<?php echo $background;?>>
    ….display something here
    <?php if (strcmp($background,"white")==0) $background="#EEEEEE"; else $background="white";
    $i++; ?>
    <?php endforeach;?>
    </table>

    See the difference?

  2. salzig Says:

    For Zebra Style you should take a look at Zend_View_Helper_Cycle

  3. bajohns Says:

    I am having great difficulty configuring all the pieces to work together. Can you post the project source somewhere online?

  4. saneesh Says:

    Doctrine pagination works fine for me.
    Thank you very much!
    San.