Snippets

Create an account or login to be able to add, comment and rate snippets.

Navigation

Snippets by user Nick Winfield Snippets by user Nick Winfield

sfAdvancedPropelPager

I'm sure you've been there; you want to use Symfony's native sfPropelPager addon but you have to seed it with a complicated query that can't be reproduced via Criteria. In order to tackle this issue, I created a subclass of sfPropelPager called sfAdvancedPropelPager (it could probably benefit from a better name).

PLEASE NOTE: You will have to change all private methods and properties in sfPropelPager from 'private' to 'protected'. This won't break paging elsewhere in your projects, I promise you! What it will do is allow this subclass to gain access to it's parent's data and functionality. It should be noted that this addon was developed with the current stable version (0.6.3) of Symfony in mind.

I've cited the "search engine" example within the Askeet project as a suitable candidate for demonstration, albeit with some tweaks. See http://www.symfony-project.com/askeet/21 for details!!

<?php
...
public static function search($phrase, $exact = false, $offset = 0, $max = 10)
{
  $words    = array_values(myTools::stemPhrase($phrase));
  $nb_words = count($words);
 
  if (!$words)
  {
    return array();
  }
 
  $con = sfContext::getInstance()->getDatabaseConnection('propel');
 
  // define the base query
  $query = '
      SELECT DISTINCT '.SearchIndexPeer::QUESTION_ID.', COUNT(*) AS nb, SUM('.SearchIndexPeer::WEIGHT.') AS total_weight
      FROM '.SearchIndexPeer::TABLE_NAME;
 
  if (sfConfig::get('app_permanent_tag'))
  {
    $query .= '
      WHERE ';
  }
  else
  {
    $query .= '
      LEFT JOIN '.QuestionTagPeer::TABLE_NAME.' ON '.QuestionTagPeer::QUESTION_ID.' = '.SearchIndexPeer::QUESTION_ID.'
      WHERE '.QuestionTagPeer::NORMALIZED_TAG.' = ? AND ';
  }
 
  $query .= '
      ('.implode(' OR ', array_fill(0, $nb_words, SearchIndexPeer::WORD.' = ?')).')
      GROUP BY '.SearchIndexPeer::QUESTION_ID;
 
  // AND query?
  if ($exact)
  {
    $query .= '
      HAVING nb = '.$nb_words;
  }
 
  $query .= '
      ORDER BY nb DESC, total_weight DESC';
 
  // prepare the statement
  $stmt = $con->prepareStatement($query);
 
  $placeholder_offset = 1;
  if (sfConfig::get('app_permanent_tag'))
  {
    $stmt->setString(1, sfConfig::get('app_permanent_tag'));
    $placeholder_offset = 2;
  }
  for ($i = 0; $i < $nb_words; $i++)
  {
    $stmt->setString($i + $placeholder_offset, $words[$i]);
  }
 
  // time to call our shiny new pager!
  $pager = new sfAdvancedPropelPager('Question', $max);
  $pager->setStatement($stmt);
  $pager->setPage(ceil($offset / $max));
  $pager->init();
  return $pager;
}
...
?>

You would then assign the $pager to a suitable property in your action, and then handle your pagination in your template in the usual fashion as illustrated on <http://www.symfony-project.com/book/trunk/pager> - simple as that!

<?php
 
class sfAdvancedPropelPager extends sfPropelPager
{
  protected
    $statement = null;
 
  public function __construct($class, $defaultMaxPerPage = 10)
  {
    $this->setClass($class);
    $this->setMaxPerPage($defaultMaxPerPage);
    $this->setPage(1);
    $this->parameter_holder = new sfParameterHolder();
    $this->setPeerMethod('retrieveByPk');
  }  
 
  public function init()
  {
    require_once(sfConfig::get('sf_model_lib_dir').'/'.$this->getClassPeer().'.php');
 
    $rs = clone $this->getStatement()->executeQuery(ResultSet::FETCHMODE_NUM);
 
    $this->setNbResults($rs->getRecordCount());
 
    if (($this->getPage() == 0 || $this->getMaxPerPage() == 0))
    {
      $this->setLastPage(0);
    }
    else
    {
      $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage()));
      $this->statement->setOffset(($this->getPage() - 1) * $this->getMaxPerPage());
      $this->statement->setLimit($this->getMaxPerPage());
    }
  }
 
  public function getResults()
  {
    $rs = $this->getStatement()->executeQuery(ResultSet::FETCHMODE_NUM);
 
    $objects = array();
 
    while ($rs->next())
    {
      $objects[] = call_user_func(array($this->getClassPeer(), $this->getPeerMethod()), $rs->getInt(1));
    }
 
    return $objects;
  }
 
  protected function retrieveObject($offset)
  {
    $statement = clone $this->getStatement();
    $statement->setOffset($offset - 1);
    $statement->setLimit(1);
 
    $rs = $statement->executeQuery(ResultSet::FETCHMODE_NUM);
 
    $object = null;
    while ($rs->next())
    {
      $object = call_user_func(array($this->getClassPeer(), $this->getPeerMethod()), $rs->getInt(1));
    }
 
    return $object;
  }
 
  public function getStatement()
  {
    return $this->statement;
  }
 
  public function setStatement($stmt)
  {
    $this->statement = $stmt;
  }
}
 
?>
by Nick Winfield on 2006-12-01, tagged pager  pagination  propel 
(1 comment)