domingo, 7 de noviembre de 2010

Using Pagination with Symfony and Doctrine

In order to understand how the Doctrine pager works, I created a admin module with the admin generator task and started snooping around the cache to see what it generated.

This is how I figured it out, may no be pretty but it works.

Create a Pagination Control


Create a file named /myproject/apps/myapplication/templates/_pagination_control.php and copy the following code on it. Then save it.

<div class="pagination">
<a href="<?php echo url_for($module) ?>?page=1">
<?php echo image_tag('/images/first.png', array('alt' =>
__('First page', array(), 'sf_admin'), 'title' =>
__('First page', array(), 'sf_admin'))) ?>
</a>

<a href="<?php echo url_for($module) ?>?page=
<?php echo $pager->getPreviousPage() ?>">
<?php echo image_tag('/images/previous.png', array('alt' =>
__('Previous page', array(), 'sf_admin'), 'title' =>
__('Previous page', array(), 'sf_admin'))) ?>
</a>

<?php foreach ($pager->getLinks() as $page): ?>
<?php if ($page == $pager->getPage()): ?>
<?php echo $page ?>
<?php else: ?>
<a href="<?php echo url_for($module) ?>?page=
<?php echo $page ?>"><?php echo $page ?></a>
<?php endif; ?>
<?php endforeach; ?>

<a href="<?php echo url_for($module) ?>?page=
<?php echo $pager->getNextPage() ?>">
<?php echo image_tag('/images/next.png', array('alt' =>
__('Next page', array(), 'sf_admin'), 'title' =>
__('Next page', array(), 'sf_admin'))) ?>
</a>

<a href="<?php echo url_for($module) ?>?page=
<?php echo $pager->getLastPage() ?>">
<?php echo image_tag('/images/last.png', array('alt' =>
__('Last page', array(), 'sf_admin'), 'title' =>
__('Last page', array(), 'sf_admin'))) ?>
</a>
</div>

You'll need to have the last.png, first.png, next.png and previous.png on your /myproject/web/images directory in order to get the pretty arrows. You can get the png files from any source you like, I extracted them from the my symonfy installation /symfony_installation/data/web/sf/sf_admin/images.

Editing the Action


Open your /myproject/apps/myapplication/config/app.yml and add the variable max_items_x_page:
all:
max_items_x_page: 5

Open your action file in my case the module is named repairrequest and the class is RepairRequest.
On the executeIndex function change the code to:
public function executeIndex(sfWebRequest $request) {
if ($request->getParameter('sort') && $this->isValidSortColumn(
$request->getParameter('sort'))) {
$this->setSort(array($request->getParameter('sort'),
$request->getParameter('sort_type')));
}

$items_x_page = sfConfig::get('app_max_items_x_page');
$this->pager = new sfDoctrinePager('RepairRequest', $items_x_page);
$this->pager->setQuery($this->buildQuery());
$this->pager->setPage($request->getParameter('page', 1));
$this->pager->init();
$this->sort = $this->getSort();
}

Change RepairRequest for your class.

Insert the following code at the end of your Action class:
//******************************************************************************************
//********************** ROUTINES FOR PAGING AND SORTING ***********************************
//******************************************************************************************
protected function buildQuery() {
$et = Doctrine_Core::getTable('RepairRequest');

$query = $et->createQuery();

$this->addSortQuery($query);

return $query;
}

protected function addSortQuery($query) {
if (array(null, null) == ($sort = $this->getSort())) {
return;
}

if (!in_array(strtolower($sort[1]), array('asc', 'desc'))) {
$sort[1] = 'asc';
}
$query->addOrderBy($sort[0] . ' ' . $sort[1]);
}

protected function getSort() {
if (null !== $sort = $this->getUser()->getAttribute('repairrequest.sort',
null, 'admin_module')) {
return $sort;
}

$this->setSort(array(null, null));

return $this->getUser()->getAttribute('repairrequest.sort', null,
'admin_module');
}

protected function setSort(array $sort) {
if (null !== $sort[0] && null === $sort[1]) {
$sort[1] = 'asc';
}
$this->getUser()->setAttribute('repairrequest.sort', $sort, 'admin_module');
}

protected function isValidSortColumn($column) {
return Doctrine::getTable('RepairRequest')->hasColumn($column);
}


Replace RepairRequest for your class name and repairrequest.sort for yourmodulename.sort.

Editing the Template


Open your /myproject/apps/myapplication/modules/mymodule/templates/indexSuccess.php at the top of the page ad the helper
for i18n.
<?php use_helper('I18N') ?>


The following code will change depending on your class
<tbody>
<?php foreach ($pager->getResults() as $i => $repair_request): ?>
<tr>
<td><a href="<?php echo url_for('repairrequest/show?id='.$repair_request->getId()) ?>">
<?php echo $repair_request->getId() ?></a></td>
<td><?php echo $repair_request->getRequestDate() ?></td>
<td><?php echo $repair_request->getLocation() ?></td>
<td><?php echo $repair_request->getDescription() ?></td>
<td><?php echo $repair_request->getRepairType() ?></td>
<td><?php echo $repair_request->getRepairStatus() ?></td>
<td><?php echo $repair_request->getRealStateId() ?></td>
<td><?php echo $repair_request->getUpdatedAt() ?></td>
<td><?php echo $repair_request->getUpdatedByFk()->getUsername() ?></td>
</tr>
<?php endforeach; ?>
</tbody>

What your doing is using the pager instead of the full cursor your get by default.
Next insert the pagination code.
<?php include_partial('global/pagination_control', array('pager' => $pager, 'module' => 'repairrequest')) ?>
<a href="<?php echo url_for('repairrequest/new') ?>">New</a>

Save it.
Now your pagination should work. On a next post I'll show how to do the sorting.
After I ran the code i got a message as if the route repairrequest did not exist (and it did). I fixed it editing the routing.yml (but I'm not really sure why I had to, just hacked it). I added the following code at the top of the routing.yml:
repairrequest:
class: sfDoctrineRouteCollection
options:
model: RepairRequest
module: repairrequest
prefix_path: /repairrequest
column: id
with_wildcard_routes: true

1 comentario: