Archive

Archive for November, 2009

Pagination in symfony without criteria

November 28, 2009 1 comment

I have created a news aggregator and needed to paginate the results. I had the ‘feed’ object having an array of ‘feeditems’ objects in my actions. So, I could not use sfPropelPager as it needs a criteria object to pull objects from the database. The solution which worked for me is presented here. Disclaimer: This is a complete rip-off from the snippets section on symfony site, modified slightly to work with the latest version of symfony and php. I will add another post to paginate in sfFeed2Plugin when I have time.

Add this class to lib/helper or anywhere in lib and include it in the file you need to use pagination.

myArrayPager.class.php

<?php
class myArrayPager extends sfPager
{
protected $resultsArray = null;

public function __construct($class = null, $maxPerPage = 10)
{
parent::__construct($class, $maxPerPage);
}

public function init()
{
$this->setNbResults(count($this->resultsArray));

if (($this->getPage() == 0 || $this->getMaxPerPage() == 0))
{
$this->setLastPage(0);
} else {
$this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage()));
}
}

public function setResultArray($array)
{
$this->resultsArray = $array;
}

public function getResultArray()
{
return $this->resultsArray;
}

public function retrieveObject($offset) {
return $this->resultsArray[$offset];
}

public function getResults()
{
return array_slice($this->resultsArray, ($this->getPage() – 1) * $this->getMaxPerPage(), $this->maxPerPage);
}
}
?>

In the action, instead of setting an object holding array of items, set the pager.

$empager = new myArrayPager(null, 10); //10 items per page
$empager->setResultArray($this->feed->getItems()); //my object is ‘feed’ which has ‘items’ array
$empager->setPage($request->getParameter(‘page’, 1));
$empager->init();

$this->pager = $empager;

Implementation of view is exactly the same as with sfPropelPager or the equivalent doctrine pager.

Enjoy!

Advertisements

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: ~