# Source for file Hydrate.php

Documentation is available at Hydrate.php

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