Development

#3883: sfPropelFinder.php

You must first sign up to be able to contribute.

Ticket #3883: sfPropelFinder.php

File sfPropelFinder.php, 25.9 kB (added by Richtermeister, 6 months ago)

sfPropelFinder class that supports phpName attribute on tables

Line 
1 <?php
2
3 /*
4  * This file is part of the sfPropelFinder package.
5  *
6  * (c) 2007 François Zaninotto <francois.zaninotto@symfony-project.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11  
12 class sfPropelFinder
13 {
14   protected $peerClass = null;
15   protected $databaseMap = null;
16   protected $criteria = null;
17   protected $relations = null;
18   protected $latestQuery = '';
19   protected $criterions = array();
20   protected $withClasses = array();
21   protected $withColumns = array();
22
23   public function __construct($class = '')
24   {
25     $this->criteria = new Criteria();
26     if($class)
27     {
28       $this->setPeerClass($class.'Peer');
29     }
30     if($this->peerClass)
31     {
32       $this->initialize();
33     }
34   }
35  
36   public function initialize()
37   {
38     $mapBuilder = call_user_func(array($this->peerClass, 'getMapBuilder'));
39     $mapBuilder->doBuild();
40     $this->databaseMap = $mapBuilder->getDatabaseMap();
41   }
42  
43   public function getPeerClass()
44   {
45     return $this->peerClass;
46   }
47
48   public function setPeerClass($peerClass)
49   {
50     $this->relations[]= $peerClass;
51     $this->peerClass = $peerClass;
52     
53     return $this;
54   }
55  
56   public function getCriteria()
57   {
58     return $this->buildCriteria();
59   }
60  
61   public function setCriteria($criteria)
62   {
63     $this->criteria = $criteria;
64     $this->criterions = array();
65     
66     return $this;
67   }
68
69   public function reinitCriteria()
70   {
71     return $this->setCriteria(new Criteria());
72   }
73  
74   public function getLatestQuery()
75   {
76     $con = Propel::getConnection();
77     if(method_exists($con, 'getLastExecutedQuery'))
78     {
79       return $this->latestQuery;
80     }
81     else
82     {
83       throw new Exception('getLatestQuery() only works when debug mode is enabled');
84     }
85   }
86  
87   public function updateLatestQuery()
88   {
89     $con = Propel::getConnection();
90     if(method_exists($con, 'getLastExecutedQuery'))
91     {
92       $this->latestQuery = $con->getLastExecutedQuery();
93     }
94   }
95  
96   public function addWithClass($class)
97   {
98     $this->withClasses []= $class;
99     
100     return $this;
101   }
102  
103   public function getWithClasses()
104   {
105     return $this->withClasses;
106   }
107  
108   public function reinitWithClasses()
109   {
110     $this->withClasses = array();
111     
112     return $this;
113   }
114
115   public function getWithColumns()
116   {
117     return $this->withColumns;
118   }
119  
120   public function reinitWithColumns()
121   {
122     $this->withColumns = array();
123     
124     return $this;
125   }
126  
127   // Finder Initializers
128  
129   /**
130    * Mixed initializer
131    * Accepts either a string (Propel class) or an array of Propel objects
132    *
133    * @param mixed $from The data to initialize the finder with
134    * @return sfPropelFinder a finder object
135    * @throws Exception If the data is neither a classname nor an array
136    */
137   public static function from($from)
138   {
139     if (is_string($from))
140     {
141       return self::fromClass($from);
142     }
143     if (is_array($from))
144     {
145       return self::fromCollection($from);
146     }
147     throw new Exception('from() only accepts a Propel object classname or an array of Propel objects');
148   }
149
150   /**
151    * Class initializer
152    *
153    * @param string $from Propel classname on which the search will be done
154    * @return sfPropelFinder a finder object
155    */
156   public static function fromClass($class)
157   {
158     $me = __CLASS__;
159     $finder = new $me($class);
160     
161     return $finder;
162   }
163  
164   /**
165    * Collection initializer
166    *
167    * @param array $from Array of Propel objects of the same class
168    * @param string $class Optional classname of the desired objects
169    * @param string $class Optional column name of the primary key
170    *
171    * @return sfPropelFinder a finder object
172    * @throws Exception If the array is empty, contains not Propel objects or composite objects
173    */
174   public static function fromCollection($collection, $class = '', $pkName = '')
175   {
176     $pks = array();
177     foreach($collection as $object)
178     {
179       if($class != get_class($object))
180       {
181         if($class)
182         {
183           throw new Exception('A finder can only be initialized from an array of objects of a single class');
184         }
185         if($object instanceof BaseObject)
186         {
187           $class = get_class($object);
188         }
189         else
190         {
191           throw new Exception('A finder can only be initialized from an array of Propel objects');
192         }
193       }
194       $pks []= $object->getPrimaryKey();
195     }
196     if(!$class)
197     {
198       throw new Exception('A finder cannot be initialized with an empty array');
199     }
200     
201     $tempObject = new $class();
202     foreach ($tempObject->getPeer()->getTableMap()->getColumns() as $column)
203     {
204       if($column->isPrimaryKey())
205       {
206         if($pkName)
207         {
208           throw new Exception('A finder cannot be initialized from an array of objects with several foreign keys');
209         }
210         else
211         {
212           $pkName = $column->getFullyQualifiedName();
213         }
214       }
215     }
216     
217     return self::fromArray($pks, $class, $pkName);
218   }
219  
220   /**
221    * Array initializer
222    *
223    * @param array $array Array of Primary keys
224    * @param string $class Propel classname on which the search will be done
225    *
226    * @return sfPropelFinder a finder object
227    */
228   public static function fromArray($array, $class, $pkName)
229   {
230     $finder = self::fromClass($class);
231     $finder->add($pkName, $array, Criteria::IN);
232     
233     return $finder;
234   }
235  
236   // Finder Executers
237  
238   public function count($con = null, $reinitCriteria = true)
239   {
240     $ret = call_user_func(array($this->getPeerClass(), 'doCount'), $this->getCriteria(), $con);
241     $this->updateLatestQuery();
242     if($reinitCriteria)
243     {
244       $this->reinitCriteria();
245     }
246     
247     return $ret;
248   }
249  
250   public function find($limit = null, $con = null, $reinitCriteria = false)
251   {
252     if($limit)
253     {
254       $this->criteria->setLimit($limit);
255     }
256     $ret = $this->doFind($this->getCriteria(), $con);
257     $this->updateLatestQuery();
258     if($reinitCriteria)
259     {
260       $this->reinitCriteria();
261     }
262     
263     return $ret;
264   }
265  
266   public function findOne($con = null, $reinitCriteria = true)
267   {
268     $this->criteria->setLimit(1);
269     $ret = $this->doFind($this->getCriteria(), $con);
270     $this->updateLatestQuery();
271     if($reinitCriteria)
272     {
273       $this->reinitCriteria();
274     }
275     if ($ret)
276     {
277       return $ret[0];
278     }
279     return null;
280   }
281  
282   public function findLast($column = null, $con = null, $reinitCriteria = true)
283   {
284     if($column)
285     {
286       $this->orderBy($column, 'desc');
287     }
288     else
289     {
290       $this->guessOrder('desc');
291     }
292     
293     return $this->findOne($con, $reinitCriteria);
294   }
295  
296   public function findFirst($column = null, $con = null, $reinitCriteria = true)
297   {
298     if($column)
299     {
300       $this->orderBy($column, 'asc');
301     }
302     else
303     {
304       $this->guessOrder('asc');
305     }
306     
307     return $this->findOne($con, $reinitCriteria);
308   }
309
310   protected function guessOrder($direction = 'desc')
311   {
312     $columnNames = array();
313     foreach ($this->getColumnsForPeerClass($this->getPeerClass()) as $c)
314     {
315       $columnNames []= $c->getPhpName();
316     }
317     foreach(sfConfig::get('app_sfPropelFinder_sort_column_guesses', array('UpdatedAt', 'UpdatedOn', 'CreatedAt', 'CreatedOn', 'Id')) as $testColumnName)
318     {
319       if(in_array($testColumnName, $columnNames))
320       {
321         $this->orderBy($testColumnName, $direction);
322         return;
323       }
324     }
325     
326     throw new Exception('Unable to figure out the column to use to order rows');
327   }
328  
329   public function findBy($columnName, $value, $limit = null, $con = null, $reinitCriteria = false)
330   {
331     $column = $this->getColName($columnName);
332     $this->addCondition('and', $column, $value, Criteria::EQUAL);
333     
334     return $this->find($limit, $con, $reinitCriteria);
335   }
336
337   public function findOneBy($columnName, $value, $con = null, $reinitCriteria = false)
338   {
339     $column = $this->getColName($columnName);
340     $this->addCondition('and', $column, $value, Criteria::EQUAL);
341     
342     return $this->findOne($con, $reinitCriteria);
343   }
344  
345   public function findPk($pk, $con = null)
346   {
347     if(is_array($pk))
348     {
349       $ret = call_user_func(array($this->getPeerClass(), 'retrieveByPks'), $pk, $con);
350     }
351     else
352     {
353       $ret = call_user_func(array($this->getPeerClass(), 'retrieveByPk'), $pk, $con);
354     }
355     $this->updateLatestQuery();
356     
357     return $ret;
358   }
359  
360   public function doFind($criteria, $con = null)
361   {
362     if($this->getWithClasses() || $this->getWithColumns())
363     {
364       $c = $this->prepareCompositeCriteria($criteria);
365       $resultSet = call_user_func(array($this->getPeerClass(), 'doSelectRS'), $c, $con);
366       
367       // Hydrate the objects based on the resultset
368       $omClass = call_user_func(array($this->getPeerClass(), 'getOMClass'));
369       $cls = Propel::import($omClass);
370       $objects = array();
371       $withObjs = array();
372       while ($resultSet->next())
373       {
374         // First come the columns of the main class
375         $obj = new $cls();
376         $startCol = $obj->hydrate($resultSet, 1);
377         // Then the related classes added by way of 'with'
378         $objectsInJoin = array($obj);
379         foreach ($this->getWithClasses() as $className)
380         {
381           $withObj = new $className();
382           $startCol = $withObj->hydrate($resultSet, $startCol);
383           
384           // initialize our object directory
385           if (!isset($withObjs[$className]))
386           {
387             $withObjs[$className] = array();
388           }
389           
390           // check if object is not already referenced in allObjects directory
391           $isNewObject = true;
392           foreach ($withObjs[$className] as $otherObject)
393           {
394             if ($otherObject->getPrimaryKey() === $withObj->getPrimaryKey())
395             {
396               $isNewObject = false;
397               $withObj = $otherObject;
398               break;
399             }
400           }
401           $this->relateObjects($withObj, $objectsInJoin, $isNewObject);
402           $objectsInJoin []= $withObj;
403           if ($isNewObject)
404           {
405             $withObjs[$className][] = $withObj;
406           }
407         }
408         // Then the columns added one by one by way of 'withColumn'
409         foreach($this->getWithColumns() as $name => $column)
410         {
411           // Additional columns are stored in the object, in a special 'namespace'
412           // see getColumn() for how to retrieve the value afterwards
413           $asName = 'WithColumn'.$name;
414           // Using the third parameter of withColumn() as a type. defaults to $rs->get() (= $rs->getString())
415           $typedGetter = 'get'.ucfirst($column['type']);
416           $obj->$asName = $resultSet->$typedGetter($startCol);
417           $startCol++;
418         }
419         
420         $objects []= $obj;
421       }
422       
423       // activate custom column getter if asColumns were added
424       if($this->getWithColumns() && !sfMixer::getCallable('Base'.$cls.':getColumn'))
425       {
426         sfMixer::register('Base'.$cls, array($this, 'getColumn'));
427       }
428       
429       return $objects;
430     }
431     else
432     {
433       // No 'with', so we use the native Propel doSelect()
434       return call_user_func(array($this->getPeerClass(), 'doSelect'), $criteria, $con);
435     }
436   }
437  
438   /**
439    * Prepare the select columns and add the missing joins
440    */
441   protected function prepareCompositeCriteria($criteria)
442   {
443     $c = clone $criteria;
444     $c->clearSelectColumns();
445     // First come the columns of the main class
446     call_user_func(array($this->getPeerClass(), 'addSelectColumns'), $c);
447     // Then the related classes added by way of 'with'
448     foreach ($this->getWithClasses() as $className)
449     {
450       $tempClass = new $className();
451       call_user_func(array($tempClass->getPeer(), 'addSelectColumns'), $c);
452       // if join() wasn't called previously on this class, do a simple join
453       if(!in_array($className.'Peer', $this->relations))
454       {
455         list($column1, $column2) = $this->getRelation($className);
456         $c->addJoin($column1, $column2);
457         $this->relations[]= $className.'Peer';
458       }
459     }
460     // Then the columns added one by one by way of 'withColumn'
461     foreach($this->getWithColumns() as $name => $column)
462     {
463       // if the column is on a related object property
464       // and if join() wasn't called previously on this object, do a simple join
465       $peerClass = $column['peerClass'];
466       if($peerClass && !in_array($peerClass, $this->relations))
467       {
468         list($column1, $column2) = $this->getRelation(str_replace('Peer', '', $peerClass));
469         $c->addJoin($column1, $column2);
470         $this->relations[]= $peerClass;
471       }
472       $c->addAsColumn($name, $column['column']);
473     }
474     
475     return $c;
476   }
477  
478   protected function relateObjects($new, $existingObjects, $isNew)
479   {
480     // brute force (to be optimized later)
481     foreach ($existingObjects as $existingObject)
482     {
483       $methodName = 'add'.get_class($existingObject);
484       if(method_exists($new, $methodName))
485       {
486         if($isNew)
487         {
488           call_user_func(array($new, 'init'.get_class($existingObject).'s'));
489         }
490         call_user_func(array($new, $methodName), $existingObject);
491         break;
492       }
493     }
494   }
495  
496   public function delete($con = null, $reinitCriteria = true)
497   {
498     $deleteCriteria = $this->getCriteria();
499     if($deleteCriteria->equals(new Criteria()))
500     {
501       // delete will delete nothing when passed an empty criteria
502       // while it should, in fact, delete all
503       $fieldNames = call_user_func(array($this->getPeerClass(), 'getFieldNames'), BasePeer::TYPE_COLNAME);
504       $firstFieldName = $fieldNames[0];
505       $deleteCriteria->add($firstFieldName, true, Criteria::BINARY_OR);
506     }
507     $ret = call_user_func(array($this->getPeerClass(), 'doDelete'), $deleteCriteria, $con);
508     $this->updateLatestQuery();
509     if($reinitCriteria)
510     {
511       $this->reinitCriteria();
512     }
513     
514     return $ret;
515   }
516  
517   // Hydrating
518  
519   public function with($classes)
520   {
521     if(!is_array($classes))
522     {
523       $classes = func_get_args();
524     }
525     foreach($classes as $class)
526     {
527       $this->addWithClass($class);
528     }
529     
530     return $this;
531   }
532
533   public function withColumn($column, $alias = null, $type = null)
534   {
535     $isCalculationColumn = strpos($column, '(') !== false;
536     if(!$alias)
537     {
538       if($isCalculationColumn)
539       {
540         throw new Exception('Calculated colums added with withColumn() need an alias as second parameter');
541       }
542       else
543       {
544         $alias = $column;
545       }
546     }
547     if($isCalculationColumn)
548     {
549       $peerClass = null;
550     }
551     else
552     {
553       list($peerClass, $columnName) = $this->getColName($column, null, true);
554     }
555     $this->withColumns [$alias]= array(
556       'column'    => $isCalculationColumn ? $column : $columnName,
557       'type'      => $type,
558       'peerClass' => $peerClass
559     );
560     
561     return $this;
562   }
563  
564   // Finder Filters
565  
566   /**
567    * Finder Fluid Interface for Criteria::setDistinct()
568    */
569   public function distinct()
570   {
571     $this->criteria->setDistinct();
572     
573     return $this;
574   }
575  
576   /**
577    * Finder Fluid Interface for Criteria::add()
578    * Infers $column, $value, $comparison from $columnName and some optional arguments
579    * Examples:
580    *   $articleFinder->where('IsPublished')
581  &nbs