# Coverage for Doctrine_Hydrate

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: Hydrate.php 3032 2007-10-29 19:50:16Z meus $
4 | *
5 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | *
17 | * This software consists of voluntary contributions made by many individuals
18 | * and is licensed under the LGPL. For more information, see
19 | * <http://www.phpdoctrine.org>.
20 | */
21 |
22 | /**
23 | * Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query.
24 | * Its purpose is to populate object graphs.
25 | *
26 | *
27 | * @package Doctrine
28 | * @subpackage Hydrate
29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
30 | * @link www.phpdoctrine.org
31 | * @since 1.0
32 | * @version $Revision: 3032 $
33 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
34 | */
35 | class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializable
36 | {
37 | /**
38 | * QUERY TYPE CONSTANTS
39 | */
40 |
41 | /**
42 | * constant for SELECT queries
43 | */
44 | const SELECT = 0;
45 |
46 | /**
47 | * constant for DELETE queries
48 | */
49 | const DELETE = 1;
50 |
51 | /**
52 | * constant for UPDATE queries
53 | */
54 | const UPDATE = 2;
55 |
56 | /**
57 | * constant for INSERT queries
58 | */
59 | const INSERT = 3;
60 |
61 | /**
62 | * constant for CREATE queries
63 | */
64 | const CREATE = 4;
65 |
66 | /**
67 | * @var array $params query input parameters
68 | */
69 | protected $_params = array('where' => array(),
70 | 'set' => array(),
71 | 'having' => array());
72 |
73 | /**
74 | * @var Doctrine_Connection $conn Doctrine_Connection object
75 | */
76 | protected $_conn;
77 |
78 | /**
79 | * @var Doctrine_View $_view Doctrine_View object, when set this object will use the
80 | * the query given by the view object for object population
81 | */
82 | protected $_view;
83 |
84 | /**
85 | * @var array $_aliasMap two dimensional array containing the map for query aliases
86 | * Main keys are component aliases
87 | *
88 | * table table object associated with given alias
89 | *
90 | * relation the relation object owned by the parent
91 | *
92 | * parent the alias of the parent
93 | *
94 | * agg the aggregates of this component
95 | *
96 | * map the name of the column / aggregate value this
97 | * component is mapped to a collection
98 | */
99 | protected $_aliasMap = array();
100 |
101 | /**
102 | *
103 | */
104 | protected $pendingAggregates = array();
105 |
106 | /**
107 | * @var array $aggregateMap an array containing all aggregate aliases, keys as dql aliases
108 | * and values as sql aliases
109 | */
110 | protected $aggregateMap = array();
111 |
112 | /**
113 | * @var array $_options an array of options
114 | */
115 | protected $_options = array(
116 | 'fetchMode' => Doctrine::FETCH_RECORD,
117 | 'parserCache' => false,
118 | 'resultSetCache' => false,
119 | );
120 |
121 | /**
122 | * @var string $_sql cached SQL query
123 | */
124 | protected $_sql;
125 |
126 | /**
127 | * @var array $parts SQL query string parts
128 | */
129 | protected $parts = array(
130 | 'select' => array(),
131 | 'distinct' => false,
132 | 'forUpdate' => false,
133 | 'from' => array(),
134 | 'set' => array(),
135 | 'join' => array(),
136 | 'where' => array(),
137 | 'groupby' => array(),
138 | 'having' => array(),
139 | 'orderby' => array(),
140 | 'limit' => false,
141 | 'offset' => false,
142 | );
143 |
144 | /**
145 | * @var integer $type the query type
146 | *
147 | * @see Doctrine_Query::* constants
148 | */
149 | protected $type = self::SELECT;
150 |
151 | /**
152 | * @var array
153 | */
154 | protected $_cache;
155 |
156 | /**
157 | * The current hydration mode.
158 | */
159 | protected $_hydrationMode = Doctrine::HYDRATE_RECORD;
160 |
161 | /**
162 | * @var boolean $_expireCache a boolean value that indicates whether or not to force cache expiration
163 | */
164 | protected $_expireCache = false;
165 |
166 | protected $_timeToLive;
167 |
168 | protected $_tableAliases = array();
169 |
170 | /**
171 | * @var array $_tableAliasSeeds A simple array keys representing table aliases and values
172 | * as table alias seeds. The seeds are used for generating short table
173 | * aliases.
174 | */
175 | protected $_tableAliasSeeds = array();
176 |
177 | /**
178 | * constructor
179 | *
180 | * @param Doctrine_Connection|null $connection
181 | */
182 | public function __construct($connection = null)
183 | {
184 | if ( ! ($connection instanceof Doctrine_Connection)) {
185 | $connection = Doctrine_Manager::getInstance()->getCurrentConnection();
186 | }
187 | $this->_conn = $connection;
188 | }
189 |
190 | /**
191 | * getRootAlias
192 | * returns the alias of the the root component
193 | *
194 | * @return array
195 | */
196 | public function getRootAlias()
197 | {
198 | if ( ! $this->_aliasMap) {
199 | $this->getSql();
200 | }
201 |
202 | reset($this->_aliasMap);
203 |
204 | return key($this->_aliasMap);
205 | }
206 |
207 | /**
208 | * getRootDeclaration
209 | * returns the root declaration
210 | *
211 | * @return array
212 | */
213 | public function getRootDeclaration()
214 | {
215 | $map = reset($this->_aliasMap);
216 |
217 | return $map;
218 | }
219 |
220 | /**
221 | * getRoot
222 | * returns the root component for this object
223 | *
224 | * @return Doctrine_Table root components table
225 | */
226 | public function getRoot()
227 | {
228 | $map = reset($this->_aliasMap);
229 |
230 | if ( ! isset($map['table'])) {
231 | throw new Doctrine_Hydrate_Exception('Root component not initialized.');
232 | }
233 |
234 | return $map['table'];
235 | }
236 |
237 | /**
238 | * getSql
239 | * return the sql associated with this object
240 | *
241 | * @return string sql query string
242 | */
243 | public function getSql()
244 | {
245 | return $this->getQuery();
246 | }
247 |
248 | /**
249 | * useCache
250 | *
251 | * @param Doctrine_Cache_Interface|bool $driver cache driver
252 | * @param integer $timeToLive how long the cache entry is valid
253 | * @return Doctrine_Hydrate this object
254 | */
255 | public function useCache($driver = true, $timeToLive = null)
256 | {
257 | if ($driver !== null) {
258 | if ($driver !== true) {
259 | if ( ! ($driver instanceof Doctrine_Cache_Interface)) {
260 | $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.';
261 |
262 | throw new Doctrine_Hydrate_Exception($msg);
263 | }
264 | }
265 | }
266 | $this->_cache = $driver;
267 |
268 | return $this->setCacheLifeSpan($timeToLive);
269 | }
270 |
271 | /**
272 | * expireCache
273 | *
274 | * @param boolean $expire whether or not to force cache expiration
275 | * @return Doctrine_Hydrate this object
276 | */
277 | public function expireCache($expire = true)
278 | {
279 | $this->_expireCache = true;
280 |
281 | return $this;
282 | }
283 |
284 | /**
285 | * setCacheLifeSpan
286 | *
287 | * @param integer $timeToLive how long the cache entry is valid
288 | * @return Doctrine_Hydrate this object
289 | */
290 | public function setCacheLifeSpan($timeToLive)
291 | {
292 | if ($timeToLive !== null) {
293 | $timeToLive = (int) $timeToLive;
294 | }
295 | $this->_timeToLive = $timeToLive;
296 |
297 | return $this;
298 | }
299 |
300 | /**
301 | * getCacheDriver
302 | * returns the cache driver associated with this object
303 | *
304 | * @return Doctrine_Cache_Interface|boolean|null cache driver
305 | */
306 | public function getCacheDriver()
307 | {
308 | if ($this->_cache instanceof Doctrine_Cache_Interface) {
309 | return $this->_cache;
310 | } else {
311 | return $this->_conn->getCacheDriver();
312 | }
313 | }
314 |
315 | /**
316 | * Sets the fetchmode.
317 | *
318 | * @param integer $fetchmode One of the Doctrine::HYDRATE_* constants.
319 | */
320 | public function setHydrationMode($hydrationMode)
321 | {
322 | $this->_hydrationMode = $hydrationMode;
323 | return $this;
324 | }
325 |
326 | /**
327 | * serialize
328 | * this method is automatically called when this Doctrine_Hydrate is serialized
329 | *
330 | * @return array an array of serialized properties
331 | */
332 | public function serialize()
333 | {
334 | $vars = get_object_vars($this);
335 |
336 | }
337 |
338 | /**
339 | * unseralize
340 | * this method is automatically called everytime a Doctrine_Hydrate object is unserialized
341 | *
342 | * @param string $serialized Doctrine_Record as serialized string
343 | * @return void
344 | */
345 | public function unserialize($serialized)
346 | {
347 |
348 | }
349 |
350 | /**
351 | * generateNewTableAlias
352 | * generates a new alias from given table alias
353 | *
354 | * @param string $tableAlias table alias from which to generate the new alias from
355 | * @return string the created table alias
356 | */
357 | public function generateNewTableAlias($tableAlias)
358 | {
359 | if (isset($this->_tableAliases[$tableAlias])) {
360 | // generate a new alias
361 | $name = substr($tableAlias, 0, 1);
362 | $i = ((int) substr($tableAlias, 1));
363 |
364 | if ($i == 0) {
365 | $i = 1;
366 | }
367 |
368 | $newIndex = ($this->_tableAliasSeeds[$name] + $i);
369 |
370 | return $name . $newIndex;
371 | }
372 |
373 | return $tableAlias;
374 | }
375 |
376 | /**
377 | * hasTableAlias
378 | * whether or not this object has given tableAlias
379 | *
380 | * @param string $tableAlias the table alias to be checked
381 | * @return boolean true if this object has given alias, otherwise false
382 | */
383 | public function hasTableAlias($tableAlias)
384 | {
385 | return (isset($this->_tableAliases[$tableAlias]));
386 | }
387 |
388 | /**
389 | * getComponentAlias
390 | * get component alias associated with given table alias
391 | *
392 | * @param string $tableAlias the table alias that identifies the component alias
393 | * @return string component alias
394 | */
395 | public function getComponentAlias($tableAlias)
396 | {
397 | if ( ! isset($this->_tableAliases[$tableAlias])) {
398 | throw new Doctrine_Hydrate_Exception('Unknown table alias ' . $tableAlias);
399 | }
400 | return $this->_tableAliases[$tableAlias];
401 | }
402 |
403 | /**
404 | * getTableAliasSeed
405 | * returns the alias seed for given table alias
406 | *
407 | * @param string $tableAlias table alias that identifies the alias seed
408 | * @return integer table alias seed
409 | */
410 | public function getTableAliasSeed($tableAlias)
411 | {
412 | if ( ! isset($this->_tableAliasSeeds[$tableAlias])) {
413 | return 0;
414 | }
415 | return $this->_tableAliasSeeds[$tableAlias];
416 | }
417 |
418 | /**
419 | * generateTableAlias
420 | * generates a table alias from given table name and associates
421 | * it with given component alias
422 | *
423 | * @param string $componentAlias the component alias to be associated with generated table alias
424 | * @param string $tableName the table name from which to generate the table alias
425 | * @return string the generated table alias
426 | */
427 | public function generateTableAlias($componentAlias, $tableName)
428 | {
429 | $char = strtolower(substr($tableName, 0, 1));
430 |
431 | $alias = $char;
432 |
433 | if ( ! isset($this->_tableAliasSeeds[$alias])) {
434 | $this->_tableAliasSeeds[$alias] = 1;
435 | }
436 |
437 | while (isset($this->_tableAliases[$alias])) {
438 | if ( ! isset($this->_tableAliasSeeds[$alias])) {
439 | $this->_tableAliasSeeds[$alias] = 1;
440 | }
441 | $alias = $char . ++$this->_tableAliasSeeds[$alias];
442 | }
443 |
444 | $this->_tableAliases[$alias] = $componentAlias;
445 |
446 | return $alias;
447 | }
448 |
449 | /**
450 | * getTableAliases
451 | * returns all table aliases
452 | *
453 | * @return array table aliases as an array
454 | */
455 | public function getTableAliases()
456 | {
457 | return $this->_tableAliases;
458 | }
459 |
460 | /**
461 | * addTableAlias
462 | * adds an alias for table and associates it with given component alias
463 | *
464 | * @param string $componentAlias the alias for the query component associated with given tableAlias
465 | * @param string $tableAlias the table alias to be added
466 | * @return Doctrine_Hydrate
467 | */
468 | public function addTableAlias($tableAlias, $componentAlias)
469 | {
470 | $this->_tableAliases[$tableAlias] = $componentAlias;
471 |
472 | return $this;
473 | }
474 |
475 | /**
476 | * getTableAlias
477 | * some database such as Oracle need the identifier lengths to be < ~30 chars
478 | * hence Doctrine creates as short identifier aliases as possible
479 | *
480 | * this method is used for the creation of short table aliases, its also
481 | * smart enough to check if an alias already exists for given component (componentAlias)
482 | *
483 | * @param string $componentAlias the alias for the query component to search table alias for
484 | * @param string $tableName the table name from which the table alias is being created
485 | * @return string the generated / fetched short alias
486 | */
487 | public function getTableAlias($componentAlias, $tableName = null)
488 | {
489 | $alias = array_search($componentAlias, $this->_tableAliases);
490 |
491 | if ($alias !== false) {
492 | return $alias;
493 | }
494 |
495 | if ($tableName === null) {
496 | throw new Doctrine_Hydrate_Exception("Couldn't get short alias for " . $componentAlias);
497 | }
498 |
499 | return $this->generateTableAlias($componentAlias, $tableName);
500 | }
501 |
502 | /**
503 | * addQueryPart
504 | * adds a query part in the query part array
505 | *
506 | * @param string $name the name of the query part to be added
507 | * @param string $part query part string
508 | * @throws Doctrine_Hydrate_Exception if trying to add unknown query part
509 | * @return Doctrine_Hydrate this object
510 | */
511 | public function addQueryPart($name, $part)
512 | {
513 | if ( ! isset($this->parts[$name])) {
514 | throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
515 | }
516 | if (is_array($part)) {
517 | $this->parts[$name] = array_merge($this->parts[$name], $part);
518 | } else {
519 | $this->parts[$name][] = $part;
520 | }
521 | return $this;
522 | }
523 |
524 | /**
525 | * setQueryPart
526 | * sets a query part in the query part array
527 | *
528 | * @param string $name the name of the query part to be set
529 | * @param string $part query part string
530 | * @throws Doctrine_Hydrate_Exception if trying to set unknown query part
531 | * @return Doctrine_Hydrate this object
532 | */
533 | public function getQueryPart($part)
534 | {
535 | if ( ! isset($this->parts[$part])) {
536 | throw new Doctrine_Hydrate_Exception('Unknown query part ' . $part);
537 | }
538 |
539 | return $this->parts[$part];
540 | }
541 |
542 | /**
543 | * removeQueryPart
544 | * removes a query part from the query part array
545 | *
546 | * @param string $name the name of the query part to be removed
547 | * @throws Doctrine_Hydrate_Exception if trying to remove unknown query part
548 | * @return Doctrine_Hydrate this object
549 | */
550 | public function removeQueryPart($name)
551 | {
552 | if (isset($this->parts[$name])) {
553 | if ($name == 'limit' || $name == 'offset') {
554 | $this->parts[$name] = false;
555 | } else {
556 | $this->parts[$name] = array();
557 | }
558 | } else {
559 | throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
560 | }
561 | return $this;
562 | }
563 |
564 | /**
565 | * setQueryPart
566 | * sets a query part in the query part array
567 | *
568 | * @param string $name the name of the query part to be set
569 | * @param string $part query part string
570 | * @throws Doctrine_Hydrate_Exception if trying to set unknown query part
571 | * @return Doctrine_Hydrate this object
572 | */
573 | public function setQueryPart($name, $part)
574 | {
575 | if ( ! isset($this->parts[$name])) {
576 | throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
577 | }
578 |
579 | if ($name !== 'limit' && $name !== 'offset') {
580 | if (is_array($part)) {
581 | $this->parts[$name] = $part;
582 | } else {
583 | $this->parts[$name] = array($part);
584 | }
585 | } else {
586 | $this->parts[$name] = $part;
587 | }
588 |
589 | return $this;
590 | }
591 |
592 | /**
593 | * hasAliasDeclaration
594 | * whether or not this object has a declaration for given component alias
595 | *
596 | * @param string $componentAlias the component alias the retrieve the declaration from
597 | * @return boolean
598 | */
599 | public function hasAliasDeclaration($componentAlias)
600 | {
601 | return isset($this->_aliasMap[$componentAlias]);
602 | }
603 |
604 | /**
605 | * getAliasDeclaration
606 | * get the declaration for given component alias
607 | *
608 | * @param string $componentAlias the component alias the retrieve the declaration from
609 | * @return array the alias declaration
610 | */
611 | public function getAliasDeclaration($componentAlias)
612 | {
613 | if ( ! isset($this->_aliasMap[$componentAlias])) {
614 | throw new Doctrine_Hydrate_Exception('Unknown component alias ' . $componentAlias);
615 | }
616 |
617 | return $this->_aliasMap[$componentAlias];
618 | }
619 |
620 | /**
621 | * copyAliases
622 | * copy aliases from another Hydrate object
623 | *
624 | * this method is needed by DQL subqueries which need the aliases
625 | * of the parent query
626 | *
627 | * @param Doctrine_Hydrate $query the query object from which the
628 | * aliases are copied from
629 | * @return Doctrine_Hydrate this object
630 | */
631 | public function copyAliases(Doctrine_Hydrate $query)
632 | {
633 | $this->_tableAliases = $query->_tableAliases;
634 | $this->_aliasMap = $query->_aliasMap;
635 | $this->_tableAliasSeeds = $query->_tableAliasSeeds;
636 | return $this;
637 | }
638 |
639 | /**
640 | * createSubquery
641 | * creates a subquery
642 | *
643 | * @return Doctrine_Hydrate
644 | */
645 | public function createSubquery()
646 | {
647 | $class = get_class($this);
648 | $obj = new $class();
649 |
650 | // copy the aliases to the subquery
651 | $obj->copyAliases($this);
652 |
653 | // this prevents the 'id' being selected, re ticket #307
654 | $obj->isSubquery(true);
655 |
656 | return $obj;
657 | }
658 |
659 | /**
660 | * limitSubqueryUsed
661 | * whether or not limit subquery was used
662 | *
663 | * @return boolean
664 | */
665 | public function isLimitSubqueryUsed()
666 | {
667 | return false;
668 | }
669 |
670 | /**
671 | * clear
672 | * resets all the variables
673 | *
674 | * @return void
675 | */
676 | protected function clear()
677 | {
678 | $this->parts = array(
679 | 'select' => array(),
680 | 'distinct' => false,
681 | 'forUpdate' => false,
682 | 'from' => array(),
683 | 'set' => array(),
684 | 'join' => array(),
685 | 'where' => array(),
686 | 'groupby' => array(),
687 | 'having' => array(),
688 | 'orderby' => array(),
689 | 'limit' => false,
690 | 'offset' => false,
691 | );
692 | $this->inheritanceApplied = false;
693 | }
694 |
695 | /**
696 | * getConnection
697 | *
698 | * @return Doctrine_Connection
699 | */
700 | public function getConnection()
701 | {
702 | return $this->_conn;
703 | }
704 |
705 | /**
706 | * setView
707 | * sets a database view this query object uses
708 | * this method should only be called internally by doctrine
709 | *
710 | * @param Doctrine_View $view database view
711 | * @return void
712 | */
713 | public function setView(Doctrine_View $view)
714 | {
715 | $this->_view = $view;
716 | }
717 |
718 | /**
719 | * getView
720 | * returns the view associated with this query object (if any)
721 | *
722 | * @return Doctrine_View the view associated with this query object
723 | */
724 | public function getView()
725 | {
726 | return $this->_view;
727 | }
728 |
729 | /**
730 | * getParams
731 | *
732 | * @return array
733 | */
734 | public function getParams()
735 | {
736 | return array_merge($this->_params['set'], $this->_params['where'], $this->_params['having']);
737 | }
738 |
739 | /**
740 | * setParams
741 | *
742 | * @param array $params
743 | */
744 | public function setParams(array $params = array()) {
745 | $this->_params = $params;
746 | }
747 | public function convertEnums($params)
748 | {
749 | return $params;
750 | }
751 |
752 | /**
753 | * setAliasMap
754 | * sets the whole component alias map
755 | *
756 | * @param array $map alias map
757 | * @return Doctrine_Hydrate this object
758 | */
759 | public function setAliasMap(array $map)
760 | {
761 | $this->_aliasMap = $map;
762 |
763 | return $this;
764 | }
765 |
766 | /**
767 | * getAliasMap
768 | * returns the component alias map
769 | *
770 | * @return array component alias map
771 | */
772 | public function getAliasMap()
773 | {
774 | return $this->_aliasMap;
775 | }
776 |
777 | /**
778 | * getCachedForm
779 | * returns the cached form of this query for given resultSet
780 | *
781 | * @param array $resultSet
782 | * @return string serialized string representation of this query
783 | */
784 | public function getCachedForm(array $resultSet)
785 | {
786 | $map = '';
787 |
788 | foreach ($this->getAliasMap() as $k => $v) {
789 | if ( ! isset($v['parent'])) {
790 | $map[$k][] = $v['table']->getComponentName();
791 | } else {
792 | $map[$k][] = $v['parent'] . '.' . $v['relation']->getAlias();
793 | }
794 | if (isset($v['agg'])) {
795 | $map[$k][] = $v['agg'];
796 | }
797 | }
798 |
799 | return serialize(array($resultSet, $map, $this->getTableAliases()));
800 | }
801 | public function _execute($params)
802 | {
803 | $params = $this->_conn->convertBooleans($params);
804 |
805 | if ( ! $this->_view) {
806 | $query = $this->getQuery($params);
807 | } else {
808 | $query = $this->_view->getSelectSql();
809 | }
810 |
811 | $params = $this->convertEnums($params);
812 |
813 | if ($this->isLimitSubqueryUsed() &&
814 | $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME) !== 'mysql') {
815 |
816 | $params = array_merge($params, $params);
817 | }
818 |
819 | if ($this->type !== self::SELECT) {
820 | return $this->_conn->exec($query, $params);
821 | }
822 |
823 | $stmt = $this->_conn->execute($query, $params);
824 | return $stmt;
825 | }
826 |
827 | /**
828 | * execute
829 | * executes the query and populates the data set
830 | *
831 | * @param string $params
832 | * @return Doctrine_Collection the root collection
833 | */
834 | public function execute($params = array(), $hydrationMode = null)
835 | {
836 | $params = array_merge($this->_params['set'],
837 | $this->_params['where'],
838 | $this->_params['having'],
839 | $params);
840 | if ($this->_cache) {
841 | $cacheDriver = $this->getCacheDriver();
842 |
843 | $dql = $this->getDql();
844 | // calculate hash for dql query
845 | $hash = md5($dql . var_export($params, true));
846 |
847 | $cached = ($this->_expireCache) ? false : $cacheDriver->fetch($hash);
848 |
849 |
850 | if ($cached === false) {
851 | // cache miss
852 | $stmt = $this->_execute($params);
853 | $array = $this->parseData2($stmt, Doctrine::HYDRATE_ARRAY);
854 |
855 | $cached = $this->getCachedForm($array);
856 |
857 | $cacheDriver->save($hash, $cached, $this->_timeToLive);
858 | } else {
859 | $cached = unserialize($cached);
860 | $this->_tableAliases = $cached[2];
861 | $array = $cached[0];
862 |
863 | $map = array();
864 | foreach ($cached[1] as $k => $v) {
865 | $e = explode('.', $v[0]);
866 | if (count($e) === 1) {
867 | $map[$k]['table'] = $this->_conn->getTable($e[0]);
868 | } else {
869 | $map[$k]['parent'] = $e[0];
870 | $map[$k]['relation'] = $map[$e[0]]['table']->getRelation($e[1]);
871 | $map[$k]['table'] = $map[$k]['relation']->getTable();
872 | }
873 | if (isset($v[1])) {
874 | $map[$k]['agg'] = $v[1];
875 | }
876 | }
877 | $this->_aliasMap = $map;
878 | }
879 | } else {
880 | $stmt = $this->_execute($params);
881 |
882 | if (is_integer($stmt)) {
883 | return $stmt;
884 | }
885 |
886 | $array = $this->parseData2($stmt, $hydrationMode);
887 | }
888 | return $array;
889 | }
890 |
891 | /**
892 | * getType
893 | *
894 | * returns the type of this query object
895 | * by default the type is Doctrine_Hydrate::SELECT but if update() or delete()
896 | * are being called the type is Doctrine_Hydrate::UPDATE and Doctrine_Hydrate::DELETE,
897 | * respectively
898 | *
899 | * @see Doctrine_Hydrate::SELECT
900 | * @see Doctrine_Hydrate::UPDATE
901 | * @see Doctrine_Hydrate::DELETE
902 | *
903 | * @return integer return the query type
904 | */
905 | public function getType()
906 | {
907 | return $this->type;
908 | }
909 |
910 | /**
911 | * applyInheritance
912 | * applies column aggregation inheritance to DQL / SQL query
913 | *
914 | * @return string
915 | */
916 | public function applyInheritance()
917 | {
918 | // get the inheritance maps
919 | $array = array();
920 |
921 | foreach ($this->_aliasMap as $componentAlias => $data) {
922 | $tableAlias = $this->getTableAlias($componentAlias);
923 | $array[$tableAlias][] = $data['table']->inheritanceMap;
924 | }
925 |
926 | // apply inheritance maps
927 | $str = '';
928 | $c = array();
929 |
930 | $index = 0;
931 | foreach ($array as $tableAlias => $maps) {
932 | $a = array();
933 |
934 | // don't use table aliases if the query isn't a select query
935 | if ($this->type !== Doctrine_Query::SELECT) {
936 | $tableAlias = '';
937 | } else {
938 | $tableAlias .= '.';
939 | }
940 |
941 | foreach ($maps as $map) {
942 | $b = array();
943 | foreach ($map as $field => $value) {
944 | $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
945 |
946 | if ($index > 0) {
947 | $b[] = '(' . $identifier . ' = ' . $this->_conn->quote($value)
948 | . ' OR ' . $identifier . ' IS NULL)';
949 | } else {
950 | $b[] = $identifier . ' = ' . $this->_conn->quote($value);
951 | }
952 | }
953 |
954 | if ( ! empty($b)) {
955 | $a[] = implode(' AND ', $b);
956 | }
957 | }
958 |
959 | if ( ! empty($a)) {
960 | $c[] = implode(' AND ', $a);
961 | }
962 | $index++;
963 | }
964 |
965 | $str .= implode(' AND ', $c);
966 |
967 | return $str;
968 | }
969 |
970 | /**
971 | * fetchArray
972 | * Convenience method to execute using array fetching as hydration mode.
973 | *
974 | * @param string $params
975 | * @return array
976 | */
977 | public function fetchArray($params = array()) {
978 | return $this->execute($params, Doctrine::HYDRATE_ARRAY);
979 | }
980 |
981 | /**
982 | * fetchOne
983 | * Convenience method to execute the query and return the first item
984 | * of the collection.
985 | *
986 | * @param string $params Parameters
987 | * @param int $hydrationMode Hydration mode
988 | * @return mixed Array or Doctrine_Collection or false if no result.
989 | */
990 | public function fetchOne($params = array(), $hydrationMode = null)
991 | {
992 | if (is_null($hydrationMode)) {
993 | $hydrationMode = $this->_hydrationMode;
994 | }
995 |
996 | $collection = $this->execute($params, $hydrationMode);
997 |
998 | if (count($collection) === 0) {
999 | return false;
1000 | }
1001 |
1002 | switch ($hydrationMode) {
1003 | case Doctrine::HYDRATE_RECORD:
1004 | return $collection->getFirst();
1005 | case Doctrine::HYDRATE_ARRAY:
1006 | return array_shift($collection);
1007 | }
1008 |
1009 | return false;
1010 | }
1011 |
1012 | /**
1013 | * parseData
1014 | * parses the data returned by statement object
1015 | *
1016 | * This is method defines the core of Doctrine object population algorithm
1017 | * hence this method strives to be as fast as possible
1018 | *
1019 | * The key idea is the loop over the rowset only once doing all the needed operations
1020 | * within this massive loop.
1021 | *
1022 | * @param mixed $stmt
1023 | * @return array
1024 | */
1025 | public function parseData2($stmt, $hydrationMode)
1026 | {
1027 | if ($hydrationMode == Doctrine::HYDRATE_NONE) {
1028 | return $stmt->fetchAll(PDO::FETCH_NUM);
1029 | }
1030 |
1031 | $cache = array();
1032 | $rootMap = reset($this->_aliasMap);
1033 | $rootAlias = key($this->_aliasMap);
1034 | $componentName = $rootMap['table']->getComponentName();
1035 | $isSimpleQuery = count($this->_aliasMap) <= 1;
1036 |
1037 | if ($hydrationMode === null) {
1038 | $hydrationMode = $this->_hydrationMode;
1039 | }
1040 |
1041 | if ($hydrationMode === Doctrine::HYDRATE_ARRAY) {
1042 | $driver = new Doctrine_Hydrate_Array();
1043 | } else {
1044 | $driver = new Doctrine_Hydrate_Record();
1045 | }
1046 |
1047 | $array = $driver->getElementCollection($componentName);
1048 |
1049 | if ($stmt === false || $stmt === 0) {
1050 | return $array;
1051 | }
1052 |
1053 | $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null);
1054 |
1055 | // for every getRecordListener() there is a little bit
1056 | // logic behind it, hence calling it multiple times on
1057 | // large result sets can be quite expensive.
1058 | // So for efficiency we use little listener caching here
1059 | foreach ($this->_aliasMap as $alias => $data) {
1060 | $componentName = $data['table']->getComponentName();
1061 | $listeners[$componentName] = $data['table']->getRecordListener();
1062 | $identifierMap[$alias] = array();
1063 | $currData[$alias] = array();
1064 | $prev[$alias] = array();
1065 | $id[$alias] = '';
1066 | }
1067 |
1068 | while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) {
1069 | $currData = array();
1070 | $identifiable = array();
1071 |
1072 | foreach ($data as $key => $value) {
1073 |
1074 | // The following little cache solution ensures that field aliases are
1075 | // parsed only once. This increases speed on large result sets by an order
1076 | // of magnitude.
1077 | if ( ! isset($cache[$key])) {
1078 | $e = explode('__', $key);
1079 | $cache[$key]['field'] = $field = strtolower(array_pop($e));
1080 | $cache[$key]['alias'] = $this->_tableAliases[strtolower(implode('__', $e))];
1081 | }
1082 |
1083 | $map = $this->_aliasMap[$cache[$key]['alias']];
1084 | $table = $map['table'];
1085 | $alias = $cache[$key]['alias'];
1086 | $field = $cache[$key]['field'];
1087 |
1088 | if (isset($this->_aliasMap[$alias]['agg'][$field])) {
1089 | $field = $this->_aliasMap[$alias]['agg'][$field];
1090 | }
1091 |
1092 |
1093 | if ($table->isIdentifier($field)) {
1094 | $id[$alias] .= '|' . $value;
1095 | }
1096 |
1097 | $currData[$alias][$field] = $table->prepareValue($field, $value);
1098 |
1099 | if ($value !== null) {
1100 | $identifiable[$alias] = true;
1101 | }
1102 | }
1103 |
1104 | // dealing with root component
1105 | $table = $this->_aliasMap[$rootAlias]['table'];
1106 | $componentName = $table->getComponentName();
1107 | $event->set('data', $currData[$rootAlias]);
1108 | $listeners[$componentName]->preHydrate($event);
1109 | $element = $driver->getElement($currData[$rootAlias], $componentName);
1110 |
1111 | $oneToOne = false;
1112 |
1113 | if ($isSimpleQuery) {
1114 | $index = false;
1115 | } else {
1116 | $index = isset($identifierMap[$rootAlias][$id[$rootAlias]]) ?
1117 | $identifierMap[$rootAlias][$id[$rootAlias]] : false;
1118 | }
1119 |
1120 | if ($index === false) {
1121 | $event->set('data', $element);
1122 | $listeners[$componentName]->postHydrate($event);
1123 |
1124 | if (isset($this->_aliasMap[$rootAlias]['map'])) {
1125 | $key = $this->_aliasMap[$rootAlias]['map'];
1126 |
1127 | if (isset($array[$key])) {
1128 | throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping.");
1129 | }
1130 |
1131 | if ( ! isset($element[$key])) {
1132 | throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key.");
1133 | }
1134 |
1135 | $array[$element[$key]] = $element;
1136 | } else {
1137 | $array[] = $element;
1138 | }
1139 |
1140 | $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($array);
1141 | }
1142 |
1143 | $this->_setLastElement($prev, $array, $index, $rootAlias, $oneToOne);
1144 | unset($currData[$rootAlias]);
1145 |
1146 | foreach ($currData as $alias => $data) {
1147 | $index = false;
1148 | $map = $this->_aliasMap[$alias];
1149 | $table = $this->_aliasMap[$alias]['table'];
1150 | $componentName = $table->getComponentName();
1151 | $event->set('data', $data);
1152 | $listeners[$componentName]->preHydrate($event);
1153 |
1154 | $element = $driver->getElement($data, $componentName);
1155 |
1156 | $parent = $map['parent'];
1157 | $relation = $map['relation'];
1158 | $componentAlias = $map['relation']->getAlias();
1159 |
1160 | $path = $parent . '.' . $alias;
1161 |
1162 | if ( ! isset($prev[$parent])) {
1163 | break;
1164 | }
1165 |
1166 | // check the type of the relation
1167 | if ( ! $relation->isOneToOne()) {
1168 | // initialize the collection
1169 |
1170 | if ($driver->initRelated($prev[$parent], $componentAlias)) {
1171 |
1172 | // append element
1173 | if (isset($identifiable[$alias])) {
1174 | if ($isSimpleQuery) {
1175 | $index = false;
1176 | } else {
1177 | $index = isset($identifierMap[$path][$id[$parent]][$id[$alias]]) ?
1178 | $identifierMap[$path][$id[$parent]][$id[$alias]] : false;
1179 | }
1180 |
1181 | if ($index === false) {
1182 | $event->set('data', $element);
1183 | $listeners[$componentName]->postHydrate($event);
1184 |
1185 | if (isset($map['map'])) {
1186 | $key = $map['map'];
1187 | if (isset($prev[$parent][$componentAlias][$key])) {
1188 | throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping.");
1189 | }
1190 | if ( ! isset($element[$key])) {
1191 | throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key.");
1192 | }
1193 | $prev[$parent][$componentAlias][$element[$key]] = $element;
1194 | } else {
1195 | $prev[$parent][$componentAlias][] = $element;
1196 | }
1197 |
1198 | $identifierMap[$path][$id[$parent]][$id[$alias]] = $driver->getLastKey($prev[$parent][$componentAlias]);
1199 | }
1200 | }
1201 | // register collection for later snapshots
1202 | $driver->registerCollection($prev[$parent][$componentAlias]);
1203 | }
1204 | } else {
1205 | if ( ! isset($identifiable[$alias])) {
1206 | $prev[$parent][$componentAlias] = $driver->getNullPointer();
1207 | } else {
1208 | $prev[$parent][$componentAlias] = $element;
1209 | }
1210 | $oneToOne = true;
1211 | }
1212 | $coll =& $prev[$parent][$componentAlias];
1213 | $this->_setLastElement($prev, $coll, $index, $alias, $oneToOne);
1214 | $id[$alias] = '';
1215 | }
1216 | $id[$rootAlias] = '';
1217 | }
1218 |
1219 | $driver->flush();
1220 |
1221 | $stmt->closeCursor();
1222 | return $array;
1223 | }
1224 |
1225 | /**
1226 | * _setLastElement
1227 | *
1228 | * sets the last element of given data array / collection
1229 | * as previous element
1230 | *
1231 | * @param boolean|integer $index
1232 | * @return void
1233 | */
1234 | public function _setLastElement(&$prev, &$coll, $index, $alias, $oneToOne)
1235 | {
1236 | if ($coll === self::$_null) {
1237 | return false;
1238 | }
1239 | if ($index !== false) {
1240 | $prev[$alias] =& $coll[$index];
1241 | } else {
1242 | // first check the count (we do not want to get the last element
1243 | // of an empty collection/array)
1244 | if (count($coll) > 0) {
1245 | if (is_array($coll)) {
1246 | if ($oneToOne) {
1247 | $prev[$alias] =& $coll;
1248 | } else {
1249 | end($coll);
1250 | $prev[$alias] =& $coll[key($coll)];
1251 | }
1252 | } else {
1253 | $prev[$alias] = $coll->getLast();
1254 | }
1255 | } else {
1256 | if (isset($prev[$alias])) {
1257 | unset($prev[$alias]);
1258 | }
1259 | }
1260 | }
1261 | }
1262 |
1263 | /**
1264 | * @return string returns a string representation of this object
1265 | */
1266 | public function __toString()
1267 | {
1268 | return Doctrine_Lib::formatSql($this->getQuery());
1269 | }
1270 |
1271 | /**
1272 | * Return the parts
1273 | *
1274 | * @return array The parts
1275 | */
1276 | public function getParts()
1277 | {
1278 | return $this->parts;
1279 | }
1280 | }