Development

Changeset 10166

You must first sign up to be able to contribute.

Changeset 10166

Show
Ignore:
Timestamp:
07/07/08 15:19:42 (5 months ago)
Author:
francois
Message:

sfPropelFinderPlugin Added sfPropelFinder::combine() method to handle complex queries with And and Or

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • plugins/sfPropelFinderPlugin/README

    r10123 r10166  
    123123}}} 
    124124 
    125 === Combining methods === 
     125=== Chaining methods === 
    126126 
    127127The methods of the `sfPropelFinder` object return the current finder object, so you can chain them together in a single call, and finish by any of the `find()` methods to launch the query. 
     
    267267}}} 
    268268 
     269=== Complex logic === 
     270 
     271{{{ 
     272#!php 
     273<?php 
     274// _and() and _or() only allow simple logical operations on a single condition 
     275// For more complex logic, you have to use combine() 
     276// It expects an array of named conditions to be combined, and an operator 
     277// Use the fourth argument of where() to name a condition 
     278$article = sfPropelFinder::from('Article')-> 
     279  where('Title', '=', 'Foo', 'cond1')->     // creates a condition named 'cond1' 
     280  where('Title', '=', 'Bar', 'cond2')->     // creates a condition named 'cond2' 
     281  combine(array('cond1', 'cond2'), 'or')->  // combine 'cond1' and 'cond2' with a logical OR 
     282  findOne(); 
     283// SELECT article.* FROM article WHERE (article.TITLE = 'foo' OR article.TITLE = 'bar'); 
     284 
     285// combine accepts more than two conditions at a time 
     286$articles = sfPropelFinder::from('Article')-> 
     287  where('Title', '=', 'Foo', 'cond1')-> 
     288  where('Title', '=', 'Bar', 'cond2')-> 
     289  where('Title', '=', 'FooBar', 'cond3')-> 
     290  combine(array('cond1', 'cond2', 'cond3'), 'or')-> 
     291  find(); 
     292// SELECT article.* FROM article WHERE (article.TITLE = 'foo' OR article.TITLE = 'bar') OR article.TITLE = 'FooBar'; 
     293 
     294// combine() itself can return a named condition to be combined later 
     295// So it allows for any level of logical complexity 
     296$articles = sfPropelFinder::from('Article')-> 
     297  where('Title', '=', 'Foo', 'cond1')-> 
     298  where('Title', '=', 'Bar', 'cond2')-> 
     299  combine(array('cond1', 'cond2'), 'or', 'TitleFooBar')-> 
     300  where('PublishedAt', '<=', $end, 'cond3')-> 
     301  where('PublishedAt', '>=', $begin, 'cond4')-> 
     302  combine(array('cond2', 'cond3'), 'and', 'PublishedInBounds')-> 
     303  combine(array('TitleFooBar', 'PublishedInBounds'), 'or')-> 
     304  find(); 
     305// SELECT article.* FROM article WHERE ( 
     306//  (article.TITLE = 'foo' OR article.TITLE = 'bar') 
     307//  OR 
     308//  (article.PUBLISHED_AT <= $end AND article.PUBLISHED_AT >= $begin) 
     309// ); 
     310}}} 
     311 
    269312=== Minimizing queries === 
    270313 
     
    535578== TODO / Ideas == 
    536579 
     580 * Allow i18n hydration of related objects (#3897) 
     581 * Handle self-referencing relationships (e.g. parent_id), especially in with() 
     582 * Handle multiple references to the same table (c.f. getFooRelatedByBarId()) 
    537583 * Put as a parent class in the PeerBuilder so that every Peer class can be a finder 
    538584 * Merge with sfPropelImpersonatorPlugin! 
    539  * Handle complex queries with And and Or 
    540585 * Add a `__toString()` method which returns a var_export() of the results, or a description of the conditions if not yet executed 
    541586 * Implement iterator interface? That way, the query is only executed upon a foreach or an array access... And the finder can be seen as a collection 
     
    544589== Changelog == 
    545590 
    546 === 2008-07-04 | Trunk === 
    547  
     591=== 2008-07-07 | Trunk === 
     592 
     593 * francois: Added `sfPropelFinder::combine()` method to handle complex queries with And and Or 
    548594 * francois: Added support for `with()` in `findPk()` (and documented the method) 
    549595 * francois: Added the ability to do left, right, and inner joins in a simple way 
  • plugins/sfPropelFinderPlugin/lib/sfPropelFinder.php

    r10132 r10166  
    712712   *   $articleFinder->where('Title', 'like', '%foo') 
    713713   *    => $c->add(ArticlePeer::TITLE, '%foo', Criteria::LIKE) 
     714   *   $articleFinder->where('Title', 'like', '%foo', 'FooTitle') 
     715   *    => $FooTitle = $c->getNewCriterion(ArticlePeer::TITLE, '%foo', Criteria::LIKE) 
    714716   * 
    715717   * @param      string  $columnName PHPName of the column bearing the condition 
    716    * @param      array   $arguments  Optional array of arguments 
     718   * @param      string  $valueOrOperator  Value if 2 arguments, operator otherwise 
     719   * @param      string  $value  Value if 3 arguments (optional) 
     720   * @param      string  $namedCondition  If condition is to be stored for later combination (see combine()) 
    717721   * 
    718722   * @return     sfPropelFinder the current finder object 
    719723   */ 
    720   public function where($columnName, $arguments = array()) 
    721   { 
     724  public function where() 
     725  { 
     726    $arguments = func_get_args(); 
     727    $columnName = array_shift($arguments); 
    722728    $column = $this->getColName($columnName); 
    723     if(!is_array($arguments)) 
    724     { 
    725       $arguments = func_get_args(); 
    726       array_shift($arguments); 
    727     }  
     729    if(isset($arguments[2])) 
     730    { 
     731      $namedCondition = $arguments[2]; 
     732      unset($arguments[2]); 
     733    } 
     734    else 
     735    { 
     736      $namedCondition = null; 
     737    } 
    728738    list($value, $comparison) = sfPropelFinderUtils::getValueAndComparisonFromArguments($arguments); 
    729  
    730     $this->addCondition('and', $column, $value, $comparison ); 
     739    $this->addCondition('And', $column, $value, $comparison, $namedCondition); 
    731740     
    732741    return $this; 
     
    736745   * Finder Fluid Interface for Criteria::addAnd() 
    737746   * Infers $column, $value, $comparison from $columnName and some optional arguments 
    738    * Examples: 
    739    *   $articleFinder->_and('CommentId', 3) 
    740    *    => $c->addAnd(ArticlePeer::COMMENT_ID, 3) 
    741    * 
    742    * @param      string  $columnName PHPName of the column bearing the condition 
    743    * @param      array   $arguments  Optional array of arguments 
    744    * 
    745    * @return     sfPropelFinder the current finder object 
    746    */ 
    747   public function _and($columnName, $arguments = array()) 
    748   { 
     747   * 
     748   * @see     where() 
     749   */ 
     750  public function _and() 
     751  { 
     752    $arguments = func_get_args(); 
     753    $columnName = array_shift($arguments); 
    749754    $column = $this->getColName($columnName); 
    750     if(!is_array($arguments)) 
    751     { 
    752       $arguments = func_get_args(); 
    753       array_shift($arguments); 
    754     }  
    755755    list($value, $comparison) = sfPropelFinderUtils::getValueAndComparisonFromArguments($arguments); 
    756  
    757     $this->addCondition('and', $column, $value, $comparison); 
     756    $this->addCondition('And', $column, $value, $comparison); 
    758757     
    759758    return $this; 
     
    766765   *   $articleFinder->_or('CommentId', 3) 
    767766   *    => $c->addOr(ArticlePeer::COMMENT_ID, 3) 
     767   *   $articleFinder->_or('Title', 'like', '%foo') 
     768   *    => $c->addOr(ArticlePeer::TITLE, '%foo', Criteria::LIKE) 
    768769   * 
    769770   * @param      string  $columnName PHPName of the column bearing the condition 
    770    * @param      array   $arguments  Optional array of arguments 
     771   * @param      string  $valueOrOperator  Value if 2 arguments, operator otherwise 
     772   * @param      string  $value  Value if 3 arguments (optional) 
    771773   * 
    772774   * @return     sfPropelFinder the current finder object 
    773775   */ 
    774   public function _or($columnName, $arguments = array()) 
    775   { 
     776  public function _or() 
     777  { 
     778    $arguments = func_get_args(); 
     779    $columnName = array_shift($arguments); 
    776780    $column = $this->getColName($columnName); 
    777     if(!is_array($arguments)) 
    778     { 
    779       $arguments = func_get_args(); 
    780       array_shift($arguments); 
    781     }  
    782781    list($value, $comparison) = sfPropelFinderUtils::getValueAndComparisonFromArguments($arguments); 
    783     $this->addCondition('or', $column, $value, $comparison); 
    784      
    785     return $this; 
    786   } 
    787  
    788   /** 
    789    * Conditions have to be stocked before being really used 
    790    * cf. sfPropelFinder::buildCriteria() 
    791    */ 
    792   protected function addCondition($cond, $column, $value, $comparison) 
     782    $this->addCondition('Or', $column, $value, $comparison); 
     783     
     784    return $this; 
     785  } 
     786 
     787  /** 
     788   * Conditions have to be stored before being really used 
     789   * 
     790   * @see sfPropelFinder::buildCriteria() 
     791   */ 
     792  protected function addCondition($cond, $column, $value, $comparison, $namedCondition = null) 
    793793  { 
    794794    $criterion = $this->criteria->getNewCriterion($column, $value, $comparison); 
    795     $criterion->func = "add".$cond; 
    796     $this->criterions []= $criterion; 
    797   } 
    798  
     795    if($namedCondition) 
     796    { 
     797      $this->namedCriterions[$namedCondition] = $criterion; 
     798    } 
     799    else 
     800    { 
     801      $criterion->func = "add".$cond; 
     802      $this->criterions []= $criterion; 
     803    } 
     804  } 
     805 
     806  /** 
     807   * Combine named conditions into the main criteria or into a new named condition 
     808   * Named conditions are to be defined in where() 
     809   * 
     810   * @param Array $conditions list of named conditions already set by way of where() 
     811   * @param string $operator Combine operator ('and' or 'or') 
     812   * @param string $namedCondition  If combined condition is to be stored for later combination (see combine()) 
     813   *  
     814   * @see where() 
     815   */ 
     816  public function combine($conditions, $operator = 'and', $namedCondition = null) 
     817  { 
     818    $addMethod = 'add'.ucfirst(strtolower(trim($operator))); 
     819    if(!is_Array($conditions)) 
     820    { 
     821      $conditions = array($conditions); 
     822    } 
     823    foreach($conditions as $condition) 
     824    { 
     825      if(!isset($criterion)) 
     826      { 
     827        $criterion = $this->namedCriterions[$condition]; 
     828      } 
     829      else 
     830      { 
     831        $criterion->$addMethod($this->namedCriterions[$condition]); 
     832      } 
     833    } 
     834    if($namedCondition) 
     835    { 
     836      $this->namedCriterions[$namedCondition] = $criterion; 
     837    } 
     838    else 
     839    { 
     840      $criterion->func = "addAnd"; 
     841      $this->criterions []= $criterion; 
     842    } 
     843     
     844    return $this; 
     845  } 
     846   
    799847  /** 
    800848   * We want that the Finder fuild Interface works like: 
     
    10931141    if(strpos($name, 'where') === 0) 
    10941142    { 
    1095       return $this->where(substr($name, 5), $arguments); 
     1143      array_unshift($arguments, substr($name, 5)); 
     1144      return call_user_func_array(array($this, 'where'), $arguments); 
    10961145    } 
    10971146    if(strpos($name, 'orderBy') === 0) 
     
    11111160    if(strpos($name, '_and') === 0) 
    11121161    { 
    1113       return $this->_and(substr($name, 4), $arguments); 
     1162      array_unshift($arguments, substr($name, 4)); 
     1163      return call_user_func_array(array($this, '_and'), $arguments); 
    11141164    } 
    11151165    if(strpos($name, 'and') === 0) 
    11161166    { 
    1117       return $this->_and(substr($name, 3), $arguments); 
     1167      array_unshift($arguments, substr($name, 3)); 
     1168      return call_user_func_array(array($this, '_and'), $arguments); 
     1169 
    11181170    } 
    11191171    if(strpos($name, '_or') === 0) 
    11201172    { 
    1121       return $this->_or(substr($name, 3), $arguments); 
     1173      array_unshift($arguments, substr($name, 3)); 
     1174      return call_user_func_array(array($this, '_or'), $arguments); 
     1175 
    11221176    } 
    11231177    if(strpos($name, 'or') === 0) 
    11241178    { 
    1125       return $this->_or(substr($name, 2), $arguments); 
     1179      array_unshift($arguments, substr($name, 2)); 
     1180      return call_user_func_array(array($this, '_or'), $arguments); 
    11261181    } 
    11271182    if(strpos($name, 'findBy') === 0) 
  • plugins/sfPropelFinderPlugin/test/unit/sfPropelFinderTest.php

    r10123 r10166  
    6565ArticlePeer::doDeleteAll(); 
    6666 
    67 $t = new lime_test(105, new lime_output_color()); 
     67$t = new lime_test(113, new lime_output_color()); 
    6868 
    6969$t->diag('find()'); 
     
    439439try 
    440440{ 
    441   $article = sfPropelFinder::from('Article')->whereTitle('abc', 'def', 'ghi'); 
    442   $t->fail('whereXXX() throws an exception when called with more than two parameters'); 
     441  $article = sfPropelFinder::from('Article')->whereTitle('abc', 'def', 'ghi', 'jkl'); 
     442  $t->fail('whereXXX() throws an exception when called with more than three parameters'); 
    443443} 
    444444catch (Exception $e) 
    445445{ 
    446   $t->pass('whereXXX() throws an exception when called with more than two parameters'); 
     446  $t->pass('whereXXX() throws an exception when called with more than three parameters'); 
    447447} 
    448448 
     
    461461$baseSelect = "SELECT $columns FROM article WHERE "; 
    462462 
    463 $finder = sfPropelFinder::from('Article')->whereTitle('foo')->_orTitle('bar'); 
     463$finder = sfPropelFinder::from('Article')-> 
     464  where('Title', 'foo')-> 
     465  _or('Title', 'bar'); 
    464466$finder->find(); 
    465467$t->is( 
     
    469471); 
    470472 
    471 $finder = sfPropelFinder::from('Article')->whereTitle('foo')->_orCategoryId(1); 
     473$finder = sfPropelFinder::from('Article')-> 
     474  where('Title', 'foo')-> 
     475  _or('CategoryId', 1); 
    472476$finder->find(); 
    473477$t->is( 
     
    477481); 
    478482 
    479 $finder = sfPropelFinder::from('Article')->whereTitle('foo')->_andTitle('bar'); 
     483$finder = sfPropelFinder::from('Article')-> 
     484  where('Title', 'foo')-> 
     485  _and('Title', 'bar'); 
    480486$finder->find(); 
    481487$t->is( 
     
    485491); 
    486492 
    487 $finder = sfPropelFinder::from('Article')->whereTitle('foo')->_andCategoryId(1); 
     493$finder = sfPropelFinder::from('Article')-> 
     494  where('Title', 'foo')-> 
     495  _and('CategoryId', 1); 
    488496$finder->find(); 
    489497$t->is( 
     
    493501); 
    494502 
    495 $finder = sfPropelFinder::from('Article')->whereCategoryId(1)->_andTitle('foo')->_orTitle('bar'); 
     503$finder = sfPropelFinder::from('Article')-> 
     504  where('CategoryId', 1)-> 
     505  _and('Title', 'foo')-> 
     506  _or('Title', 'bar'); 
    496507$finder->find(); 
    497508$t->is( 
     
    523534  '_or() works on a multiple jointure' 
    524535); 
     536 
     537$t->diag('combine()'); 
     538 
     539$columns    = "article.ID, article.VERSION, article.TITLE, article.CATEGORY_ID"; 
     540$baseSelect = "SELECT $columns FROM article WHERE "; 
     541 
     542$finder = sfPropelFinder::from('Article')-> 
     543  where('Title', '=', 'foo', 'cond1')-> 
     544  where('Title', '=', 'bar'); 
     545$finder->find(); 
     546$t->is( 
     547  $finder->getLatestQuery(), 
     548  $baseSelect . "article.TITLE='bar'", 
     549  'where() called with a named condition does not affect the SQL until it is combined' 
     550); 
     551 
     552$finder = sfPropelFinder::from('Article')-> 
     553  where('Title', '=', 'foo', 'cond1')-> 
     554  where('Title', '=', 'bar', 'cond2')-> 
     555  combine(array('cond1', 'cond2'), 'or'); 
     556$finder->find(); 
     557$t->is( 
     558  $finder->getLatestQuery(), 
     559  $baseSelect . "(article.TITLE='foo' OR article.TITLE='bar')", 
     560  'combine() combines conditions into the main criteria' 
     561); 
     562 
     563$finder = sfPropelFinder::from('Article')-> 
     564  where('Title', '=', 'foo', 'cond1')-> 
     565  where('Title', '=', 'bar', 'cond2')-> 
     566  combine(array('cond1', 'cond2'), 'and'); 
     567$finder->find(); 
     568$t->is( 
     569  $finder->getLatestQuery(), 
     570  $baseSelect . "(article.TITLE='foo' AND article.TITLE='bar')", 
     571  'combine() combines conditions into the main criteria' 
     572); 
     573 
     574$finder = sfPropelFinder::from('Article')-> 
     575  where('Title', '=', 'foo', 'cond1')-> 
     576  where('Title', '=', 'bar', 'cond2')-> 
     577  where('Title', '=', 'foobar')-> 
     578  combine(array('cond1', 'cond2'), 'or'); 
     579$finder->find(); 
     580$t->is( 
     581  $finder->getLatestQuery(), 
     582  $baseSelect . "(article.TITLE='foobar' AND (article.TITLE='foo' OR article.TITLE='bar'))", 
     583  'combine() clauses live well with the usual conditions' 
     584); 
     585 
     586$finder = sfPropelFinder::from('Article')-> 
     587  where('Title', '=', 'foo', 'cond1')-> 
     588  where('Title', '=', 'bar', 'cond2')-> 
     589  combine(array('cond1', 'cond2'), 'or')-> 
     590  where('Title', '=', 'foobar'); 
     591$finder->find(); 
     592$t->is( 
     593  $finder->getLatestQuery(), 
     594  $baseSelect . "((article.TITLE='foo' OR article.TITLE='bar') AND article.TITLE='foobar')", 
     595  'combine() clauses live well with the usual conditions and appear ordered as they were called' 
     596); 
     597 
     598$finder = sfPropelFinder::from('Article')-> 
     599  where('Title', '=', 'foo', 'cond1')-> 
     600  where('Title', '=', 'bar', 'cond2')-> 
     601  where('Title', '=', 'foobar', 'cond3')-> 
     602  combine(array('cond1', 'cond2'), 'or', 'cond4')-> 
     603  combine(array('cond4', 'cond3'), 'and'); 
     604$finder->find(); 
     605$t->is( 
     606  $finder->getLatestQuery(), 
     607  $baseSelect . "((article.TITLE='foo' OR article.TITLE='bar') AND article.TITLE='foobar')", 
     608  'combine() can return a named condition' 
     609); 
     610 
     611$finder = sfPropelFinder::from('Article')-> 
     612  where('Title', '=', 'foo', 'cond1')-> 
     613  where('Title', '=', 'bar', 'cond2')-> 
     614  where('Title', '=', 'foobar', 'cond3')-> 
     615  where('Title', '=', 'boofar', 'cond4')-> 
     616  combine(array('cond1', 'cond2'), 'or', 'cond6')-> 
     617  combine(array('cond3', 'cond4'), 'or', 'cond7')-> 
     618  combine(array('cond6', 'cond7'), 'and'); 
     619$finder->find(); 
     620$t->is( 
     621  $finder->getLatestQuery(), 
     622  $baseSelect . "((article.TITLE='foo' OR article.TITLE='bar') AND (article.TITLE='foobar' OR article.TITLE='boofar'))", 
     623  'combine() allows for imbricated conditions' 
     624); 
     625 
     626$finder = sfPropelFinder::from('Article')-> 
     627  where('Title', '=', 'foo', 'cond1')-> 
     628  where('Title', '=', 'bar', 'cond2')-> 
     629  where('Title', '=', 'foobar', 'cond3')-> 
     630  combine(array('cond1', 'cond2', 'cond3'), 'or'); 
     631$finder->find(); 
     632$t->is( 
     633  $finder->getLatestQuery(), 
     634  $baseSelect . "((article.TITLE='foo' OR article.TITLE='bar') OR article.TITLE='foobar')", 
     635  'combine() can combine more than two conditions' 
     636); 
     637 
    525638 
    526639$t->diag('relatedTo()');