Archive

Posts Tagged ‘symfony sorting pagination sourcecode’

Sorting and pagination with symfony

November 1, 2009 Leave a comment

It took me a couple of days to figure out how to display sorted, paginated results in PHP using symfony. While both pagination and sorting are easy, combining them (along with using routing framework of symfony) does produce some difficult-to-find bugs. Here is what worked for me. Note: the code needs some refactoring.

Lets say, I have a table named t_job.

project/config/schema.yml

t_job:
  id: ~
  name: { type: VARCHAR, size: ’45’, required: true }
  description: { type: VARCHAR, size: ‘255’ }
  popularity: { type: INTEGER }
  starts_at: { type: TIMESTAMP }
  expires_at: { type: TIMESTAMP }
  is_active: { type: BOOLEAN, default: 0 }
  created_at: ~
  updated_at: ~

Build sql, insert sql, build model, create module called job with symfony. My model classes are TJob and TJobPeer. At this step, you should be able to add, edit, delete a job. Remember to __toString methods to each of your model classes for default print outputs.

I am using all default routing for the sake of simplicity. You may add/modify those later to show pretty urls.

Open TJobPeer and add these static methods to create criteria objects. I have used separate methods for each criteria so that I can mix-and-match in different modules.

project/lib/model/TJobPeer.php

class TJobPeer extends BaseTJobPeer
{
static public function getActiveJobs(Criteria $criteria = null)
{
if (is_null($criteria))
{
$criteria = new Criteria();
}
$max=sfConfig::get(‘app_max_jobs_on_page’);   //need to add this constant in app.yml, required to determine the number of jobs to be displayed per page for pagination
$criteria->setLimit($max);
return self::doSelect($criteria);
}

static public function countActiveJobs(Criteria $criteria = null)
{
return self::doCount($criteria);
}

static public function addActiveJobsCriteria(Criteria $criteria = null)
{
if (is_null($criteria))
{
$criteria = new Criteria();
}
$criteria->addDescendingOrderByColumn(self::EXPIRES_AT);
$criteria->add(self::IS_ACTIVE, true);
return $criteria;
}

static public function addSortAscendingCriteria(Criteria $criteria = null)
{
if (is_null($criteria))
{
$criteria = new Criteria();
}
$criteria->addAscendingOrderByColumn(self::NAME);
return $criteria;
}

static public function addSortPopularityCriteria(Criteria $criteria = null)
{
if (is_null($criteria))
{
$criteria = new Criteria();
}
$criteria->addAscendingOrderByColumn(self::POPULARITY);
return $criteria;
}

static public function addSortNewestCriteria(Criteria $criteria = null)
{
if (is_null($criteria))
{
$criteria = new Criteria();
}
$criteria->addDescendingOrderByColumn(self::STARTS_AT);
return $criteria;
}
}

Modify the executeIndex function of actions.class.php as below:

project/apps/frontend/modules/job/actions.class.php

public function executeIndex(sfWebRequest $request)
{
$criteria = new Criteria();
$criteria = TJobPeer::addActiveJobsCriteria();
$this->setVar(‘sortoption’, ”, true);
if($request->getParameter(‘sortoption’) == ‘normal’){
$criteria = TJobPeer::addSortAscendingCriteria();
$this->setVar(‘sortoption’, ‘normal’, true);
}
if($request->getParameter(‘sortoption’) == ‘popularity’){
$criteria = TJobPeer::addSortPercentCriteria();
$this->setVar(‘sortoption’, ‘popularity’, true);
}
if($request->getParameter(‘sortoption’) == ‘newest’){
$criteria = TJobPeer::addSortNewestCriteria();
$this->setVar(‘sortoption’, ‘newest’, true);
}

$this->t_job_list = TJobPeer::getActiveJobs($criteria);

//These lines take care of pagination
$this->pager = new sfPropelPager(‘TJob’, sfConfig::get(‘app_max_jobs_on_page’));
$this->pager->setPage($request->getParameter(‘page’, 1));
$this->pager->setCriteria($criteria);
$this->pager->init();
}

Now the view. I have used a partial to reuse it elsewhere and I have removed my decoration in the following view file to make it understandable.

project/apps/frontend/modules/job/templates/indexSuccess.php

<a href=”<?php echo url_for(‘job/index?sortoption=normal’) ?>”>All</a>
<a href=”<?php echo url_for(‘job/index?sortoption=popularity’) ?>”>Popularity</a>
<a href=”<?php echo url_for(‘job/index?sortoption=newest’) ?>”>Latest</a>

<?php include_partial(‘job/list’, array(‘job_list’ => $pager->getResults())) ?>

<?php include_partial(‘job/paginate’, array(‘item’ => $t_job_list, ‘pager’ => $pager, ‘module’ => ‘job’, ‘sortpath’=> $sortoption )) ?>

And the partials:

project/apps/frontend/modules/job/templates/_list.php

<?php foreach ($job_list as $t_job): ?>

<div class=”job_listing”>
<h1><a href=”<?php echo url_for(‘show_job’, $t_job) ?>”><?php echo $t_job->getName() ?></a></h1>
</div>
<h1>up to <?php echo $t_job->getPopularity() ?>%</h1>
<div class=”clear”></div>

<div class=”job_listing”>
<p><?php echo $t_job->getDescription() ?></p>
</div>

<?php endforeach;?>

project/apps/frontend/modules/job/templates/_paginate.php

<?php if ($pager->haveToPaginate()): ?>
<div class=”pagination”>
<?php if(is_null($sortpath) or $sortpath==”): ?>
<a href=”<?php echo url_for($module, $item) ?>page=1″>First</a>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $pager->getPreviousPage() ?>”>Previous</a>
<?php foreach ($pager->getLinks() as $page): ?>
<?php if ($page == $pager->getPage()): ?>
<span class=”disabled”><?php echo $page ?></span>
<?php else: ?>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $page ?>”><?php echo $page ?></a>
<?php endif; ?>
<?php endforeach; ?>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $pager->getNextPage() ?>”>Next</a>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $pager->getLastPage() ?>”>Last</a>
<?php else: ?>
<a href=”<?php echo url_for($module, $item) ?>page=1&sortoption=<?php echo $sortpath ?>”>First</a>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $pager->getPreviousPage() ?>&sortoption=<?php echo $sortpath ?>”>Previous</a>
<?php foreach ($pager->getLinks() as $page): ?>
<?php if ($page == $pager->getPage()): ?>
<span class=”disabled”><?php echo $page ?></span>
<?php else: ?>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $page ?>&sortoption=<?php echo $sortpath ?>”><?php echo $page ?></a>
<?php endif; ?>
<?php endforeach; ?>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $pager->getNextPage() ?>&sortoption=<?php echo $sortpath ?>”>Next</a>
<a href=”<?php echo url_for($module, $item) ?>page=<?php echo $pager->getLastPage() ?>&sortoption=<?php echo $sortpath ?>”>Last</a>
<?php endif;?>
</div>
<?php endif; ?>

Thats it. Done! Enter test values in the database and point your browser to the module home path. Warning: I have modified table, column, model and other names and may have introduced a typo.

t_category:
id: ~
name: { type: VARCHAR, size: ’45’, required: true }
description: { type: VARCHAR, size: ’45’ }
slug: { type: varchar(255), required: true, index: unique }
parent_category_id: { type: INTEGER, foreignTable: t_category, foreignReference: id, onDelete: RESTRICT, onUpdate: RESTRICT }
created_at: ~
updated_at: ~