1<?php 2/* 3 * $Id: Query.php 1393 2007-05-19 17:49:16Z zYne $ 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.doctrine-project.org>. 20 */ 21 22/** 23 * Doctrine_Query_Abstract 24 * 25 * @package Doctrine 26 * @subpackage Query 27 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 * @link www.doctrine-project.org 29 * @since 1.0 30 * @version $Revision: 1393 $ 31 * @author Konsta Vesterinen <kvesteri@cc.hut.fi> 32 * @todo See {@link Doctrine_Query} 33 */ 34abstract class Doctrine_Query_Abstract 35{ 36 /** 37 * QUERY TYPE CONSTANTS 38 */ 39 40 /** 41 * constant for SELECT queries 42 */ 43 const SELECT = 0; 44 45 /** 46 * constant for DELETE queries 47 */ 48 const DELETE = 1; 49 50 /** 51 * constant for UPDATE queries 52 */ 53 const UPDATE = 2; 54 55 /** 56 * constant for INSERT queries 57 */ 58 const INSERT = 3; 59 60 /** 61 * constant for CREATE queries 62 */ 63 const CREATE = 4; 64 65 /** @todo document the query states (and the transitions between them). */ 66 /** 67 * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. 68 */ 69 const STATE_CLEAN = 1; 70 71 /** 72 * A query object is in state DIRTY when it has DQL parts that have not yet been 73 * parsed/processed. 74 */ 75 const STATE_DIRTY = 2; 76 77 /** 78 * A query is in DIRECT state when ... ? 79 */ 80 const STATE_DIRECT = 3; 81 82 /** 83 * A query object is on LOCKED state when ... ? 84 */ 85 const STATE_LOCKED = 4; 86 87 /** 88 * @var array Table alias map. Keys are SQL aliases and values DQL aliases. 89 */ 90 protected $_tableAliasMap = array(); 91 92 /** 93 * @var Doctrine_View The view object used by this query, if any. 94 */ 95 protected $_view; 96 97 /** 98 * @var integer $_state The current state of this query. 99 */ 100 protected $_state = self::STATE_CLEAN; 101 102 /** 103 * @var array $_params The parameters of this query. 104 */ 105 protected $_params = array('exec' => array(), 106 'join' => array(), 107 'where' => array(), 108 'set' => array(), 109 'having' => array()); 110 111 /** 112 * @var array $_execParams The parameters passed to connection statement 113 */ 114 protected $_execParams = array(); 115 116 /* Caching properties */ 117 /** 118 * @var Doctrine_Cache_Interface The cache driver used for caching result sets. 119 */ 120 protected $_resultCache; 121 122 /** 123 * @var string Key to use for result cache entry in the cache driver 124 */ 125 protected $_resultCacheHash; 126 127 /** 128 * @var boolean $_expireResultCache A boolean value that indicates whether or not 129 * expire the result cache. 130 */ 131 protected $_expireResultCache = false; 132 protected $_resultCacheTTL; 133 134 /** 135 * @var Doctrine_Cache_Interface The cache driver used for caching queries. 136 */ 137 protected $_queryCache; 138 protected $_expireQueryCache = false; 139 protected $_queryCacheTTL; 140 141 /** 142 * @var boolean $_autoFree A boolean value that indicates whether or not use auto free. 143 */ 144 protected $_autoFree = false; 145 146 /** 147 * @var Doctrine_Connection The connection used by this query object. 148 */ 149 protected $_conn; 150 151 /** 152 * @var bool Whether or not a connection was passed to this query object to use 153 */ 154 protected $_passedConn = false; 155 156 /** 157 * @var array $_sqlParts The SQL query string parts. Filled during the DQL parsing process. 158 */ 159 protected $_sqlParts = array( 160 'select' => array(), 161 'distinct' => false, 162 'forUpdate' => false, 163 'from' => array(), 164 'set' => array(), 165 'join' => array(), 166 'where' => array(), 167 'groupby' => array(), 168 'having' => array(), 169 'orderby' => array(), 170 'limit' => false, 171 'offset' => false, 172 ); 173 174 /** 175 * @var array $_dqlParts an array containing all DQL query parts; @see Doctrine_Query::getDqlPart() 176 */ 177 protected $_dqlParts = array( 178 'from' => array(), 179 'select' => array(), 180 'forUpdate' => false, 181 'set' => array(), 182 'join' => array(), 183 'where' => array(), 184 'groupby' => array(), 185 'having' => array(), 186 'orderby' => array(), 187 'limit' => array(), 188 'offset' => array(), 189 ); 190 191 192 /** 193 * @var array $_queryComponents Two dimensional array containing the components of this query, 194 * informations about their relations and other related information. 195 * The components are constructed during query parsing. 196 * 197 * Keys are component aliases and values the following: 198 * 199 * table table object associated with given alias 200 * 201 * relation the relation object owned by the parent 202 * 203 * parent the alias of the parent 204 * 205 * agg the aggregates of this component 206 * 207 * map the name of the column / aggregate value this 208 * component is mapped to a collection 209 */ 210 protected $_queryComponents = array(); 211 212 /** 213 * Stores the root DQL alias 214 * 215 * @var string 216 */ 217 protected $_rootAlias = ''; 218 219 /** 220 * @var integer $type the query type 221 * 222 * @see Doctrine_Query::* constants 223 */ 224 protected $_type = self::SELECT; 225 226 /** 227 * @var Doctrine_Hydrator The hydrator object used to hydrate query results. 228 */ 229 protected $_hydrator; 230 231 /** 232 * @var Doctrine_Query_Tokenizer The tokenizer that is used during the query parsing process. 233 */ 234 protected $_tokenizer; 235 236 /** 237 * @var Doctrine_Query_Parser The parser that is used for query parsing. 238 */ 239 protected $_parser; 240 241 /** 242 * @var array $_tableAliasSeeds A simple array keys representing table aliases and values 243 * table alias seeds. The seeds are used for generating short table 244 * aliases. 245 */ 246 protected $_tableAliasSeeds = array(); 247 248 /** 249 * @var array $_options an array of options 250 */ 251 protected $_options = array( 252 'hydrationMode' => Doctrine_Core::HYDRATE_RECORD 253 ); 254 255 /** 256 * @var boolean 257 */ 258 protected $_isLimitSubqueryUsed = false; 259 260 /** 261 * @var array components used in the DQL statement 262 */ 263 protected $_components; 264 265 /** 266 * @var bool Boolean variable for whether or not the preQuery process has been executed 267 */ 268 protected $_preQueried = false; 269 270 /** 271 * Fix for http://www.doctrine-project.org/jira/browse/DC-701 272 * 273 * @var bool Boolean variable for whether the limitSubquery method of accessing tables via a many relationship should be used. 274 */ 275 protected $disableLimitSubquery = false; 276 277 /** 278 * Constructor. 279 * 280 * @param Doctrine_Connection $connection The connection object the query will use. 281 * @param Doctrine_Hydrator_Abstract $hydrator The hydrator that will be used for generating result sets. 282 * 283 * @throws Doctrine_Connection_Exception 284 */ 285 public function __construct(Doctrine_Connection $connection = null, 286 Doctrine_Hydrator_Abstract $hydrator = null) 287 { 288 if ($connection === null) { 289 $connection = Doctrine_Manager::getInstance()->getCurrentConnection(); 290 } else { 291 $this->_passedConn = true; 292 } 293 if ($hydrator === null) { 294 $hydrator = new Doctrine_Hydrator(); 295 } 296 $this->_conn = $connection; 297 $this->_hydrator = $hydrator; 298 $this->_tokenizer = new Doctrine_Query_Tokenizer(); 299 $this->_resultCacheTTL = $this->_conn->getAttribute(Doctrine_Core::ATTR_RESULT_CACHE_LIFESPAN); 300 $this->_queryCacheTTL = $this->_conn->getAttribute(Doctrine_Core::ATTR_QUERY_CACHE_LIFESPAN); 301 $this->_autoFree = $this->_conn->getAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS); 302 } 303 304 /** 305 * Set the connection this query object should use 306 * 307 * @param Doctrine_Connection $connection 308 * @return void 309 */ 310 public function setConnection(Doctrine_Connection $connection) 311 { 312 $this->_passedConn = true; 313 $this->_conn = $connection; 314 } 315 316 /** 317 * setOption 318 * 319 * @param string $name option name 320 * @param string $value option value 321 * 322 * @throws Doctrine_Query_Exception 323 */ 324 public function setOption($name, $value) 325 { 326 if ( ! isset($this->_options[$name])) { 327 throw new Doctrine_Query_Exception('Unknown option ' . $name); 328 } 329 $this->_options[$name] = $value; 330 } 331 332 /** 333 * setAutoFree 334 * Sets auto free 335 * 336 * @param boolean $value True or false (true by default) 337 * 338 * @return $this 339 */ 340 public function setAutoFree($value = true) 341 { 342 $this->_autoFree = (boolean) $value; 343 344 return $this; 345 } 346 347 /** 348 * hasSqlTableAlias 349 * whether or not this object has given tableAlias 350 * 351 * @param string $sqlTableAlias the table alias to be checked 352 * 353 * @return boolean true if this object has given alias, otherwise false 354 */ 355 public function hasSqlTableAlias($sqlTableAlias) 356 { 357 return (isset($this->_tableAliasMap[$sqlTableAlias])); 358 } 359 360 /** 361 * getTableAliasMap 362 * returns all table aliases 363 * 364 * @return array table aliases as an array 365 */ 366 public function getTableAliasMap() 367 { 368 return $this->_tableAliasMap; 369 } 370 371 /** 372 * getDql 373 * returns the DQL query that is represented by this query object. 374 * 375 * the query is built from $_dqlParts 376 * 377 * @return string the DQL query 378 */ 379 public function getDql() 380 { 381 $q = ''; 382 if ($this->_type == self::SELECT) { 383 $q .= ( ! empty($this->_dqlParts['select'])) ? 'SELECT ' . implode(', ', $this->_dqlParts['select']) : ''; 384 $q .= ( ! empty($this->_dqlParts['from'])) ? ' FROM ' . implode(' ', $this->_dqlParts['from']) : ''; 385 } else if ($this->_type == self::DELETE) { 386 $q .= 'DELETE'; 387 $q .= ( ! empty($this->_dqlParts['from'])) ? ' FROM ' . implode(' ', $this->_dqlParts['from']) : ''; 388 } else if ($this->_type == self::UPDATE) { 389 $q .= 'UPDATE '; 390 $q .= ( ! empty($this->_dqlParts['from'])) ? implode(' ', $this->_dqlParts['from']) : ''; 391 $q .= ( ! empty($this->_dqlParts['set'])) ? ' SET ' . implode(' ', $this->_dqlParts['set']) : ''; 392 } 393 $q .= ( ! empty($this->_dqlParts['where'])) ? ' WHERE ' . implode(' ', $this->_dqlParts['where']) : ''; 394 $q .= ( ! empty($this->_dqlParts['groupby'])) ? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : ''; 395 $q .= ( ! empty($this->_dqlParts['having'])) ? ' HAVING ' . implode(' AND ', $this->_dqlParts['having']) : ''; 396 $q .= ( ! empty($this->_dqlParts['orderby'])) ? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : ''; 397 $q .= ( ! empty($this->_dqlParts['limit'])) ? ' LIMIT ' . implode(' ', $this->_dqlParts['limit']) : ''; 398 $q .= ( ! empty($this->_dqlParts['offset'])) ? ' OFFSET ' . implode(' ', $this->_dqlParts['offset']) : ''; 399 400 return $q; 401 } 402 403 /** 404 * getSqlQueryPart 405 * gets an SQL query part from the SQL query part array 406 * 407 * @param string $part the name of the query part to get 408 * 409 * @return mixed 410 * @throws Doctrine_Query_Exception if trying to get an unknown query part 411 */ 412 public function getSqlQueryPart($part) 413 { 414 if ( ! isset($this->_sqlParts[$part])) { 415 throw new Doctrine_Query_Exception('Unknown SQL query part ' . $part); 416 } 417 return $this->_sqlParts[$part]; 418 } 419 420 /** 421 * setSqlQueryPart 422 * sets an SQL query part in the SQL query part array 423 * 424 * @param string $name the name of the query part to be set 425 * @param string $part query part string 426 * 427 * @return $this this object 428 * @throws Doctrine_Query_Exception if trying to set unknown query part 429 */ 430 public function setSqlQueryPart($name, $part) 431 { 432 if ( ! isset($this->_sqlParts[$name])) { 433 throw new Doctrine_Query_Exception('Unknown query part ' . $name); 434 } 435 436 if ($name !== 'limit' && $name !== 'offset') { 437 if (is_array($part)) { 438 $this->_sqlParts[$name] = $part; 439 } else { 440 $this->_sqlParts[$name] = array($part); 441 } 442 } else { 443 $this->_sqlParts[$name] = $part; 444 } 445 446 return $this; 447 } 448 449 /** 450 * addSqlQueryPart 451 * adds an SQL query part to the SQL query part array 452 * 453 * @param string $name the name of the query part to be added 454 * @param string $part query part string 455 * 456 * @return $this this object 457 * @throws Doctrine_Query_Exception if trying to add unknown query part 458 */ 459 public function addSqlQueryPart($name, $part) 460 { 461 if ( ! isset($this->_sqlParts[$name])) { 462 throw new Doctrine_Query_Exception('Unknown query part ' . $name); 463 } 464 if (is_array($part)) { 465 $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part); 466 } else { 467 $this->_sqlParts[$name][] = $part; 468 } 469 return $this; 470 } 471 472 /** 473 * removeSqlQueryPart 474 * removes a query part from the query part array 475 * 476 * @param string $name the name of the query part to be removed 477 * 478 * @return $this this object 479 * @throws Doctrine_Query_Exception if trying to remove unknown query part 480 */ 481 public function removeSqlQueryPart($name) 482 { 483 if ( ! isset($this->_sqlParts[$name])) { 484 throw new Doctrine_Query_Exception('Unknown query part ' . $name); 485 } 486 487 if ($name == 'limit' || $name == 'offset' || $name == 'forUpdate') { 488 $this->_sqlParts[$name] = false; 489 } else { 490 $this->_sqlParts[$name] = array(); 491 } 492 493 return $this; 494 } 495 496 /** 497 * removeDqlQueryPart 498 * removes a dql query part from the dql query part array 499 * 500 * @param string $name the name of the query part to be removed 501 * 502 * @return $this this object 503 * @throws Doctrine_Query_Exception if trying to remove unknown query part 504 */ 505 public function removeDqlQueryPart($name) 506 { 507 if ( ! isset($this->_dqlParts[$name])) { 508 throw new Doctrine_Query_Exception('Unknown query part ' . $name); 509 } 510 511 if ($name == 'limit' || $name == 'offset') { 512 $this->_dqlParts[$name] = false; 513 } else { 514 $this->_dqlParts[$name] = array(); 515 } 516 517 return $this; 518 } 519 520 /** 521 * Get raw array of parameters for query and all parts. 522 * 523 * @return array $params 524 */ 525 public function getParams() 526 { 527 return $this->_params; 528 } 529 530 /** 531 * Get flattened array of parameters for query. 532 * Used internally and used to pass flat array of params to the database. 533 * 534 * @param array $params 535 * 536 * @return array 537 */ 538 public function getFlattenedParams($params = array()) 539 { 540 return array_merge( 541 (array) $params, (array) $this->_params['exec'], 542 $this->_params['join'], $this->_params['set'], 543 $this->_params['where'], $this->_params['having'] 544 ); 545 } 546 547 /** 548 * getInternalParams 549 * 550 * @param array $params 551 * 552 * @return array 553 */ 554 public function getInternalParams($params = array()) 555 { 556 return array_merge($params, $this->_execParams); 557 } 558 559 /** 560 * setParams 561 * 562 * @param array $params 563 */ 564 public function setParams(array $params = array()) 565 { 566 $this->_params = $params; 567 } 568 569 /** 570 * getCountQueryParams 571 * Retrieves the parameters for count query 572 * 573 * @param array $params 574 * 575 * @return array Parameters array 576 */ 577 public function getCountQueryParams($params = array()) 578 { 579 if ( ! is_array($params)) { 580 $params = array($params); 581 } 582 583 $this->_params['exec'] = $params; 584 585 $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $this->_params['exec']); 586 587 $this->fixArrayParameterValues($params); 588 589 return $this->_execParams; 590 } 591 592 /** 593 * @nodoc 594 */ 595 public function fixArrayParameterValues($params = array()) 596 { 597 $i = 0; 598 599 foreach ($params as $param) { 600 if (is_array($param)) { 601 $c = count($param); 602 603 array_splice($params, $i, 1, $param); 604 605 $i += $c; 606 } else { 607 $i++; 608 } 609 } 610 611 $this->_execParams = $params; 612 } 613 614 /** 615 * setView 616 * sets a database view this query object uses 617 * this method should only be called internally by doctrine 618 * 619 * @param Doctrine_View $view database view 620 * @return void 621 */ 622 public function setView(Doctrine_View $view) 623 { 624 $this->_view = $view; 625 } 626 627 /** 628 * getView 629 * returns the view associated with this query object (if any) 630 * 631 * @return Doctrine_View the view associated with this query object 632 */ 633 public function getView() 634 { 635 return $this->_view; 636 } 637 638 /** 639 * limitSubqueryUsed 640 * 641 * @return boolean 642 */ 643 public function isLimitSubqueryUsed() 644 { 645 return $this->_isLimitSubqueryUsed; 646 } 647 648 /** 649 * Returns the inheritance condition for the passed componentAlias 650 * If no component alias is specified it defaults to the root component 651 * 652 * This function is used to append a SQL condition to models which have inheritance mapping 653 * The condition is applied to the FROM component in the WHERE, but the condition is applied to 654 * JOINS in the ON condition and not the WHERE 655 * 656 * @param string $componentAlias 657 * 658 * @return string|null $str SQL condition string 659 * @throws Doctrine_Query_Exception 660 */ 661 public function getInheritanceCondition($componentAlias) 662 { 663 $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap; 664 665 // No inheritance map so lets just return 666 if (empty($map)) { 667 return null; 668 } 669 670 $tableAlias = $this->getSqlTableAlias($componentAlias); 671 672 if ($this->_type !== Doctrine_Query::SELECT) { 673 $tableAlias = ''; 674 } else { 675 $tableAlias .= '.'; 676 } 677 678 // Fix for 2015: loop through whole inheritanceMap to add all 679 // keyFields for inheritance (and not only the first) 680 $retVal = ""; 681 $count = 0; 682 683 foreach ($map as $field => $value) { 684 if ($count++ > 0) { 685 $retVal .= ' AND '; 686 } 687 688 $identifier = $this->_conn->quoteIdentifier($tableAlias . $field); 689 $retVal .= $identifier . ' = ' . $this->_conn->quote($value); 690 } 691 692 return $retVal; 693 } 694 695 /** 696 * getSqlTableAlias 697 * some database such as Oracle need the identifier lengths to be < ~30 chars 698 * hence Doctrine creates as short identifier aliases as possible 699 * 700 * this method is used for the creation of short table aliases, its also 701 * smart enough to check if an alias already exists for given component (componentAlias) 702 * 703 * @param string $componentAlias the alias for the query component to search table alias for 704 * @param string $tableName the table name from which the table alias is being created 705 * 706 * @return string the generated / fetched short alias 707 * @throws Doctrine_Query_Exception 708 */ 709 public function getSqlTableAlias($componentAlias, $tableName = null) 710 { 711 $alias = array_search($componentAlias, $this->_tableAliasMap); 712 713 if ($alias !== false) { 714 return $alias; 715 } 716 717 if ($tableName === null) { 718 throw new Doctrine_Query_Exception("Couldn't get short alias for " . $componentAlias); 719 } 720 721 return $this->generateSqlTableAlias($componentAlias, $tableName); 722 } 723 724 /** 725 * generateNewSqlTableAlias 726 * generates a new alias from given table alias 727 * 728 * @param string $oldAlias table alias from which to generate the new alias from 729 * 730 * @return string the created table alias 731 */ 732 public function generateNewSqlTableAlias($oldAlias) 733 { 734 if (isset($this->_tableAliasMap[$oldAlias])) { 735 // generate a new alias 736 $name = substr($oldAlias, 0, 1); 737 $i = ((int) substr($oldAlias, 1)); 738 739 // Fix #1530: It was reaching unexistent seeds index 740 if ( ! isset($this->_tableAliasSeeds[$name])) { 741 $this->_tableAliasSeeds[$name] = 1; 742 } 743 744 $newIndex = ($this->_tableAliasSeeds[$name] + (($i == 0) ? 1 : $i)); 745 746 return $name . $newIndex; 747 } 748 749 return $oldAlias; 750 } 751 752 /** 753 * getSqlTableAliasSeed 754 * returns the alias seed for given table alias 755 * 756 * @param string $sqlTableAlias table alias that identifies the alias seed 757 * 758 * @return integer table alias seed 759 */ 760 public function getSqlTableAliasSeed($sqlTableAlias) 761 { 762 if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) { 763 return 0; 764 } 765 return $this->_tableAliasSeeds[$sqlTableAlias]; 766 } 767 768 /** 769 * hasAliasDeclaration 770 * whether or not this object has a declaration for given component alias 771 * 772 * @param string $componentAlias the component alias the retrieve the declaration from 773 * @return boolean 774 */ 775 public function hasAliasDeclaration($componentAlias) 776 { 777 return isset($this->_queryComponents[$componentAlias]); 778 } 779 780 /** 781 * getQueryComponent 782 * get the declaration for given component alias 783 * 784 * @param string $componentAlias the component alias the retrieve the declaration from 785 * 786 * @return array the alias declaration 787 * @throws Doctrine_Query_Exception 788 */ 789 public function getQueryComponent($componentAlias) 790 { 791 if ( ! isset($this->_queryComponents[$componentAlias])) { 792 throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias); 793 } 794 795 return $this->_queryComponents[$componentAlias]; 796 } 797 798 /** 799 * copySubqueryInfo 800 * copy aliases from another Hydrate object 801 * 802 * this method is needed by DQL subqueries which need the aliases 803 * of the parent query 804 * 805 * @param Doctrine_Query_Abstract $query the query object from which the 806 * aliases are copied from 807 * 808 * @return $this this object 809 */ 810 public function copySubqueryInfo(Doctrine_Query_Abstract $query) 811 { 812 $this->_params =& $query->_params; 813 $this->_tableAliasMap =& $query->_tableAliasMap; 814 $this->_queryComponents =& $query->_queryComponents; 815 $this->_tableAliasSeeds = $query->_tableAliasSeeds; 816 return $this; 817 } 818 819 /** 820 * getRootAlias 821 * returns the alias of the root component 822 * 823 * @return string 824 */ 825 public function getRootAlias() 826 { 827 if ( ! $this->_queryComponents) { 828 $this->getSqlQuery(array(), false); 829 } 830 831 return $this->_rootAlias; 832 } 833 834 /** 835 * getRootDeclaration 836 * returns the root declaration 837 * 838 * @return array 839 */ 840 public function getRootDeclaration() 841 { 842 $map = $this->_queryComponents[$this->_rootAlias]; 843 return $map; 844 } 845 846 /** 847 * getRoot 848 * returns the root component for this object 849 * 850 * @return Doctrine_Table root components table 851 * @throws Doctrine_Query_Exception 852 */ 853 public function getRoot() 854 { 855 $map = $this->_queryComponents[$this->_rootAlias]; 856 857 if ( ! isset($map['table'])) { 858 throw new Doctrine_Query_Exception('Root component not initialized.'); 859 } 860 861 return $map['table']; 862 } 863 864 /** 865 * generateSqlTableAlias 866 * generates a table alias from given table name and associates 867 * it with given component alias 868 * 869 * @param string $componentAlias the component alias to be associated with generated table alias 870 * @param string $tableName the table name from which to generate the table alias 871 * @return string the generated table alias 872 */ 873 public function generateSqlTableAlias($componentAlias, $tableName) 874 { 875 preg_match('/([^_|\d])/', $tableName, $matches); 876 $char = strtolower($matches[0]); 877 878 $alias = $char; 879 880 if ( ! isset($this->_tableAliasSeeds[$alias])) { 881 $this->_tableAliasSeeds[$alias] = 1; 882 } 883 884 while (isset($this->_tableAliasMap[$alias])) { 885 if ( ! isset($this->_tableAliasSeeds[$alias])) { 886 $this->_tableAliasSeeds[$alias] = 1; 887 } 888 $alias = $char . ++$this->_tableAliasSeeds[$alias]; 889 } 890 891 $this->_tableAliasMap[$alias] = $componentAlias; 892 893 return $alias; 894 } 895 896 /** 897 * getComponentAlias 898 * get component alias associated with given table alias 899 * 900 * @param string $sqlTableAlias the SQL table alias that identifies the component alias 901 * 902 * @return string component alias 903 * @throws Doctrine_Query_Exception 904 */ 905 public function getComponentAlias($sqlTableAlias) 906 { 907 $sqlTableAlias = trim($sqlTableAlias, '[]`"'); 908 if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) { 909 throw new Doctrine_Query_Exception('Unknown table alias ' . $sqlTableAlias); 910 } 911 return $this->_tableAliasMap[$sqlTableAlias]; 912 } 913 914 /** 915 * calculateQueryCacheHash 916 * calculate hash key for query cache 917 * 918 * @return string the hash 919 */ 920 public function calculateQueryCacheHash() 921 { 922 $dql = $this->getDql(); 923 $hash = md5($dql . var_export($this->_pendingJoinConditions, true) . 'DOCTRINE_QUERY_CACHE_SALT'); 924 return $hash; 925 } 926 927 /** 928 * calculateResultCacheHash 929 * calculate hash key for result cache 930 * 931 * @param array $params 932 * @return string the hash 933 */ 934 public function calculateResultCacheHash($params = array()) 935 { 936 $dql = $this->getDql(); 937 $conn = $this->getConnection(); 938 $params = $this->getFlattenedParams($params); 939 $hash = md5($this->_hydrator->getHydrationMode() . $conn->getName() . $conn->getOption('dsn') . $dql . var_export($this->_pendingJoinConditions, true) . var_export($params, true)); 940 return $hash; 941 } 942 943 /** 944 * Get the result cache hash/key. Returns key set with useResultCache() 945 * or generates a unique key from the query automatically. 946 * 947 * @param array $params 948 * @return string $hash 949 */ 950 public function getResultCacheHash($params = array()) 951 { 952 if ($this->_resultCacheHash) { 953 return $this->_resultCacheHash; 954 } else { 955 return $this->calculateResultCacheHash($params); 956 } 957 } 958 959 /** 960 * _execute 961 * 962 * @param array $params 963 * 964 * @return PDOStatement|int The executed PDOStatement or the number of affected rows 965 * @throws Doctrine_Connection_Exception 966 * @throws Doctrine_Exception 967 */ 968 protected function _execute($params) 969 { 970 // Apply boolean conversion in DQL params 971 $params = $this->_conn->convertBooleans($params); 972 973 foreach ($this->_params as $k => $v) { 974 $this->_params[$k] = $this->_conn->convertBooleans($v); 975 } 976 977 $dqlParams = $this->getFlattenedParams($params); 978 979 // Check if we're not using a Doctrine_View 980 if ( ! $this->_view) { 981 if ($this->_queryCache !== false && ($this->_queryCache || $this->_conn->getAttribute(Doctrine_Core::ATTR_QUERY_CACHE))) { 982 $queryCacheDriver = $this->getQueryCacheDriver(); 983 $hash = $this->calculateQueryCacheHash(); 984 $cached = $queryCacheDriver->fetch($hash); 985 986 // If we have a cached query... 987 if ($cached) { 988 // Rebuild query from cache 989 $query = $this->_constructQueryFromCache($cached); 990 991 // Assign building/execution specific params 992 $this->_params['exec'] = $params; 993 994 // Initialize prepared parameters array 995 $this->_execParams = $this->getFlattenedParams(); 996 997 // Fix possible array parameter values in SQL params 998 $this->fixArrayParameterValues($this->getInternalParams()); 999 } else { 1000 // Generate SQL or pick already processed one 1001 $query = $this->getSqlQuery($params); 1002 1003 // Check again because getSqlQuery() above could have flipped the _queryCache flag 1004 // if this query contains the limit sub query algorithm we don't need to cache it 1005 if ($this->_queryCache !== false && ($this->_queryCache || $this->_conn->getAttribute(Doctrine_Core::ATTR_QUERY_CACHE))) { 1006 // Convert query into a serialized form 1007 $serializedQuery = $this->getCachedForm($query); 1008 1009 // Save cached query 1010 $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan()); 1011 } 1012 } 1013 } else { 1014 $query = $this->getSqlQuery($params); 1015 } 1016 } else { 1017 $query = $this->_view->getSelectSql(); 1018 } 1019 1020 // Get prepared SQL params for execution 1021 $params = $this->getInternalParams(); 1022 1023 if ($this->isLimitSubqueryUsed() && 1024 $this->_conn->getAttribute(Doctrine_Core::ATTR_DRIVER_NAME) !== 'mysql') { 1025 $params = array_merge((array) $params, (array) $params); 1026 } 1027 1028 if ($this->_type !== self::SELECT) { 1029 return $this->_conn->exec($query, $params); 1030 } 1031 1032 $stmt = $this->_conn->execute($query, $params); 1033 1034 $this->_params['exec'] = array(); 1035 1036 return $stmt; 1037 } 1038 1039 /** 1040 * execute 1041 * executes the query and populates the data set 1042 * 1043 * @param array $params 1044 * @param null|int|string $hydrationMode one of the Doctrine_Core::HYDRATE_* constants or null 1045 * 1046 * @return Doctrine_Collection|array the root collection (the result type depends on the hydrator) 1047 * @throws Doctrine_Connection_Exception 1048 * @throws Doctrine_Hydrator_Exception 1049 * @throws Doctrine_Query_Exception 1050 * @throws Doctrine_Exception 1051 */ 1052 public function execute($params = array(), $hydrationMode = null) 1053 { 1054 // Clean any possible processed params 1055 $this->_execParams = array(); 1056 1057 if (empty($this->_dqlParts['from']) && empty($this->_sqlParts['from'])) { 1058 throw new Doctrine_Query_Exception('You must have at least one component specified in your from.'); 1059 } 1060 1061 $dqlParams = $this->getFlattenedParams($params); 1062 1063 $this->_preQuery($dqlParams); 1064 1065 if ($hydrationMode !== null) { 1066 $this->_hydrator->setHydrationMode($hydrationMode); 1067 } 1068 1069 $hydrationMode = $this->_hydrator->getHydrationMode(); 1070 1071 if ($this->_resultCache && $this->_type == self::SELECT) { 1072 $cacheDriver = $this->getResultCacheDriver(); 1073 $hash = $this->getResultCacheHash($params); 1074 $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash); 1075 1076 if ($cached === false) { 1077 // cache miss 1078 $stmt = $this->_execute($params); 1079 $this->_hydrator->setQueryComponents($this->_queryComponents); 1080 $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap); 1081 1082 $cached = $this->getCachedForm($result); 1083 $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan()); 1084 } else { 1085 $result = $this->_constructQueryFromCache($cached); 1086 } 1087 } else { 1088 $stmt = $this->_execute($params); 1089 1090 if (is_integer($stmt)) { 1091 $result = $stmt; 1092 } else { 1093 $this->_hydrator->setQueryComponents($this->_queryComponents); 1094 if ($this->_type == self::SELECT && $hydrationMode == Doctrine_Core::HYDRATE_ON_DEMAND) { 1095 $hydrationDriver = $this->_hydrator->getHydratorDriver($hydrationMode, $this->_tableAliasMap); 1096 $result = new Doctrine_Collection_OnDemand($stmt, $hydrationDriver, $this->_tableAliasMap); 1097 } else { 1098 $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap); 1099 } 1100 } 1101 } 1102 if ($this->_autoFree) { 1103 $this->free(); 1104 } 1105 1106 return $result; 1107 } 1108 1109 /** 1110 * Blank template method free(). Override to be used to free query object memory 1111 */ 1112 public function free() 1113 { 1114 } 1115 1116 /** 1117 * Get the dql call back for this query 1118 * 1119 * @return array $callback 1120 */ 1121 protected function _getDqlCallback() 1122 { 1123 $callback = false; 1124 if ( ! empty($this->_dqlParts['from'])) { 1125 switch ($this->_type) { 1126 case self::DELETE: 1127 $callback = array( 1128 'callback' => 'preDqlDelete', 1129 'const' => Doctrine_Event::RECORD_DQL_DELETE 1130 ); 1131 break; 1132 case self::UPDATE: 1133 $callback = array( 1134 'callback' => 'preDqlUpdate', 1135 'const' => Doctrine_Event::RECORD_DQL_UPDATE 1136 ); 1137 break; 1138 case self::SELECT: 1139 $callback = array( 1140 'callback' => 'preDqlSelect', 1141 'const' => Doctrine_Event::RECORD_DQL_SELECT 1142 ); 1143 break; 1144 } 1145 } 1146 1147 return $callback; 1148 } 1149 1150 /** 1151 * Pre query method which invokes the pre*Query() methods on the model instance or any attached 1152 * record listeners 1153 * 1154 * @param array $params 1155 * 1156 * @return void 1157 * @throws Doctrine_Connection_Exception 1158 */ 1159 protected function _preQuery($params = array()) 1160 { 1161 if ( ! $this->_preQueried && $this->getConnection()->getAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS)) { 1162 $this->_preQueried = true; 1163 1164 $callback = $this->_getDqlCallback(); 1165 1166 // if there is no callback for the query type, then we can return early 1167 if ( ! $callback) { 1168 return; 1169 } 1170 1171 foreach ($this->_getDqlCallbackComponents($params) as $alias => $component) { 1172 $table = $component['table']; 1173 $record = $table->getRecordInstance(); 1174 1175 // Trigger preDql*() callback event 1176 $params = array('component' => $component, 'alias' => $alias); 1177 $event = new Doctrine_Event($record, $callback['const'], $this, $params); 1178 1179 $record->{$callback['callback']}($event); 1180 $table->getRecordListener()->{$callback['callback']}($event); 1181 } 1182 } 1183 1184 // Invoke preQuery() hook on Doctrine_Query for child classes which implement this hook 1185 $this->preQuery(); 1186 } 1187 1188 /** 1189 * Returns an array of components to execute the query callbacks for 1190 * 1191 * @param array $params 1192 * @return array $components 1193 */ 1194 protected function _getDqlCallbackComponents($params = array()) 1195 { 1196 $componentsBefore = array(); 1197 if ($this->isSubquery()) { 1198 $componentsBefore = $this->getQueryComponents(); 1199 } 1200 1201 $copy = $this->copy(); 1202 $copy->getSqlQuery($params, false); 1203 $componentsAfter = $copy->getQueryComponents(); 1204 1205 $this->_rootAlias = $copy->getRootAlias(); 1206 1207 $copy->free(); 1208 1209 if ($componentsBefore !== $componentsAfter) { 1210 return Doctrine_Lib::arrayDiffSimple($componentsAfter, $componentsBefore); 1211 } else { 1212 return $componentsAfter; 1213 } 1214 } 1215 1216 /** 1217 * Blank hook methods which can be implemented in Doctrine_Query child classes 1218 * 1219 * @return void 1220 */ 1221 public function preQuery() 1222 { 1223 } 1224 1225 /** 1226 * Constructs the query from the cached form. 1227 * 1228 * @param string The cached query, in a serialized form. 1229 * @return array The custom component that was cached together with the essential 1230 * query data. This can be either a result set (result caching) 1231 * or an SQL query string (query caching). 1232 */ 1233 protected function _constructQueryFromCache($cached) 1234 { 1235 $cached = unserialize($cached); 1236 $this->_tableAliasMap = $cached[2]; 1237 $customComponent = $cached[0]; 1238 1239 $queryComponents = array(); 1240 $cachedComponents = $cached[1]; 1241 foreach ($cachedComponents as $alias => $components) { 1242 $e = explode('.', $components['name']); 1243 if (count($e) === 1) { 1244 $manager = Doctrine_Manager::getInstance(); 1245 if ( ! $this->_passedConn && $manager->hasConnectionForComponent($e[0])) { 1246 $this->_conn = $manager->getConnectionForComponent($e[0]); 1247 } 1248 $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]); 1249 } else { 1250 $queryComponents[$alias]['parent'] = $e[0]; 1251 $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]); 1252 $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable(); 1253 } 1254 if (isset($components['agg'])) { 1255 $queryComponents[$alias]['agg'] = $components['agg']; 1256 } 1257 if (isset($components['map'])) { 1258 $queryComponents[$alias]['map'] = $components['map']; 1259 } 1260 } 1261 $this->_queryComponents = $queryComponents; 1262 1263 return $customComponent; 1264 } 1265 1266 /** 1267 * getCachedForm 1268 * returns the cached form of this query for given resultSet 1269 * 1270 * @param array|null|Doctrine_Collection $customComponent 1271 * 1272 * @return string serialized string representation of this query 1273 */ 1274 public function getCachedForm($customComponent = null) 1275 { 1276 $componentInfo = array(); 1277 1278 foreach ($this->getQueryComponents() as $alias => $components) { 1279 if ( ! isset($components['parent'])) { 1280 $componentInfo[$alias]['name'] = $components['table']->getComponentName(); 1281 } else { 1282 $componentInfo[$alias]['name'] = $components['parent'] . '.' . $components['relation']->getAlias(); 1283 } 1284 if (isset($components['agg'])) { 1285 $componentInfo[$alias]['agg'] = $components['agg']; 1286 } 1287 if (isset($components['map'])) { 1288 $componentInfo[$alias]['map'] = $components['map']; 1289 } 1290 } 1291 1292 if ($customComponent instanceof Doctrine_Collection) { 1293 foreach ($customComponent as $record) { 1294 $record->serializeReferences(true); 1295 } 1296 } 1297 1298 return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap())); 1299 } 1300 1301 /** 1302 * Adds fields or aliased functions. 1303 * 1304 * This method adds fields or dbms functions to the SELECT query part. 1305 * <code> 1306 * $query->addSelect('COUNT(p.id) as num_phonenumbers'); 1307 * </code> 1308 * 1309 * @param string $select Query SELECT part 1310 * 1311 * @return $this 1312 * @throws Doctrine_Query_Exception 1313 */ 1314 public function addSelect($select) 1315 { 1316 return $this->_addDqlQueryPart('select', $select, true); 1317 } 1318 1319 /** 1320 * addSqlTableAlias 1321 * adds an SQL table alias and associates it a component alias 1322 * 1323 * @param string $componentAlias the alias for the query component associated with given tableAlias 1324 * @param string $sqlTableAlias the table alias to be added 1325 * 1326 * @return $this 1327 */ 1328 public function addSqlTableAlias($sqlTableAlias, $componentAlias) 1329 { 1330 $this->_tableAliasMap[$sqlTableAlias] = $componentAlias; 1331 return $this; 1332 } 1333 1334 /** 1335 * addFrom 1336 * adds fields to the FROM part of the query 1337 * 1338 * @param string $from Query FROM part 1339 * 1340 * @return $this 1341 * @throws Doctrine_Query_Exception 1342 */ 1343 public function addFrom($from) 1344 { 1345 return $this->_addDqlQueryPart('from', $from, true); 1346 } 1347 1348 /** 1349 * Alias for @see Doctrine_Query_Abstract::andWhere(). 1350 * 1351 * @return $this this object 1352 * @throws Doctrine_Query_Exception 1353 */ 1354 public function addWhere($where, $params = array()) 1355 { 1356 return $this->andWhere($where, $params); 1357 } 1358 1359 /** 1360 * Adds conditions to the WHERE part of the query. 1361 * <code> 1362 * $q->andWhere('u.birthDate > ?', '1975-01-01'); 1363 * </code> 1364 * 1365 * @param string $where Query WHERE part 1366 * @param mixed $params An array of parameters or a simple scalar 1367 * 1368 * @return $this 1369 * @throws Doctrine_Query_Exception 1370 */ 1371 public function andWhere($where, $params = array()) 1372 { 1373 if (is_array($params)) { 1374 $this->_params['where'] = array_merge($this->_params['where'], $params); 1375 } else { 1376 $this->_params['where'][] = $params; 1377 } 1378 1379 if ($this->_hasDqlQueryPart('where')) { 1380 $this->_addDqlQueryPart('where', 'AND', true); 1381 } 1382 1383 return $this->_addDqlQueryPart('where', $where, true); 1384 } 1385 1386 /** 1387 * Adds conditions to the WHERE part of the query 1388 * <code> 1389 * $q->orWhere('u.role = ?', 'admin'); 1390 * </code> 1391 * 1392 * @param string $where Query WHERE part 1393 * @param mixed $params An array of parameters or a simple scalar 1394 * 1395 * @return $this 1396 * @throws Doctrine_Query_Exception 1397 */ 1398 public function orWhere($where, $params = array()) 1399 { 1400 if (is_array($params)) { 1401 $this->_params['where'] = array_merge($this->_params['where'], $params); 1402 } else { 1403 $this->_params['where'][] = $params; 1404 } 1405 1406 if ($this->_hasDqlQueryPart('where')) { 1407 $this->_addDqlQueryPart('where', 'OR', true); 1408 } 1409 1410 return $this->_addDqlQueryPart('where', $where, true); 1411 } 1412 1413 /** 1414 * Adds IN condition to the query WHERE part. Alias to @see andWhereIn(). 1415 * 1416 * @param string $expr the operand of the IN 1417 * @param mixed $params an array of parameters or a simple scalar 1418 * @param boolean $not whether or not to use NOT in front of IN 1419 * 1420 * @return $this 1421 * @throws Doctrine_Query_Exception 1422 */ 1423 public function whereIn($expr, $params = array(), $not = false) 1424 { 1425 return $this->andWhereIn($expr, $params, $not); 1426 } 1427 1428 /** 1429 * Adds IN condition to the query WHERE part 1430 * <code> 1431 * $q->whereIn('u.id', array(10, 23, 44)); 1432 * </code> 1433 * 1434 * @param string $expr The operand of the IN 1435 * @param mixed $params An array of parameters or a simple scalar 1436 * @param boolean $not Whether or not to use NOT in front of IN. Defaults to false (simple IN clause) 1437 * 1438 * @return $this this object. 1439 * @throws Doctrine_Query_Exception 1440 */ 1441 public function andWhereIn($expr, $params = array(), $not = false) 1442 { 1443 // if there's no params, return (else we'll get a WHERE IN (), invalid SQL) 1444 if (isset($params) and is_array($params) and (count($params) == 0)) { 1445 return $this; 1446 } 1447 1448 if ($this->_hasDqlQueryPart('where')) { 1449 $this->_addDqlQueryPart('where', 'AND', true); 1450 } 1451 1452 return $this->_addDqlQueryPart('where', $this->_processWhereIn($expr, $params, $not), true); 1453 } 1454 1455 /** 1456 * Adds IN condition to the query WHERE part, appending it with an OR operator. 1457 * <code> 1458 * $q->orWhereIn('u.id', array(10, 23)) 1459 * ->orWhereIn('u.id', 44); 1460 * // will select all record with id equal to 10, 23 or 44 1461 * </code> 1462 * 1463 * @param string $expr The operand of the IN 1464 * @param mixed $params An array of parameters or a simple scalar 1465 * @param boolean $not Whether or not to use NOT in front of IN 1466 * 1467 * @return $this 1468 * @throws Doctrine_Query_Exception 1469 */ 1470 public function orWhereIn($expr, $params = array(), $not = false) 1471 { 1472 // if there's no params, return (else we'll get a WHERE IN (), invalid SQL) 1473 if (isset($params) and (count($params) == 0)) { 1474 return $this; 1475 } 1476 1477 if ($this->_hasDqlQueryPart('where')) { 1478 $this->_addDqlQueryPart('where', 'OR', true); 1479 } 1480 1481 return $this->_addDqlQueryPart('where', $this->_processWhereIn($expr, $params, $not), true); 1482 } 1483 1484 /** 1485 * @nodoc 1486 */ 1487 protected function _processWhereIn($expr, $params = array(), $not = false) 1488 { 1489 $params = (array) $params; 1490 1491 // if there's no params, return (else we'll get a WHERE IN (), invalid SQL) 1492 if (count($params) == 0) { 1493 throw new Doctrine_Query_Exception('You must pass at least one parameter when using an IN() condition.'); 1494 } 1495 1496 $a = array(); 1497 foreach ($params as $k => $value) { 1498 if ($value instanceof Doctrine_Expression) { 1499 $value = $value->getSql(); 1500 unset($params[$k]); 1501 } else { 1502 $value = '?'; 1503 } 1504 $a[] = $value; 1505 } 1506 1507 $this->_params['where'] = array_merge($this->_params['where'], $params); 1508 1509 return $expr . ($not === true ? ' NOT' : '') . ' IN (' . implode(', ', $a) . ')'; 1510 } 1511 1512 /** 1513 * Adds NOT IN condition to the query WHERE part. 1514 * <code> 1515 * $q->whereNotIn('u.id', array(10, 20)); 1516 * // will exclude users with id 10 and 20 from the select 1517 * </code> 1518 * 1519 * @param string $expr the operand of the NOT IN 1520 * @param mixed $params an array of parameters or a simple scalar 1521 * 1522 * @return $this this object 1523 * @throws Doctrine_Query_Exception 1524 */ 1525 public function whereNotIn($expr, $params = array()) 1526 { 1527 return $this->whereIn($expr, $params, true); 1528 } 1529 1530 /** 1531 * Adds NOT IN condition to the query WHERE part 1532 * Alias for @see whereNotIn(). 1533 * 1534 * @param string $expr The operand of the NOT IN 1535 * @param mixed $params An array of parameters or a simple scalar 1536 * 1537 * @return $this 1538 * @throws Doctrine_Query_Exception 1539 */ 1540 public function andWhereNotIn($expr, $params = array()) 1541 { 1542 return $this->andWhereIn($expr, $params, true); 1543 } 1544 1545 /** 1546 * Adds NOT IN condition to the query WHERE part 1547 * 1548 * @param string $expr The operand of the NOT IN 1549 * @param mixed $params An array of parameters or a simple scalar 1550 * 1551 * @return $this 1552 * @throws Doctrine_Query_Exception 1553 */ 1554 public function orWhereNotIn($expr, $params = array()) 1555 { 1556 return $this->orWhereIn($expr, $params, true); 1557 } 1558 1559 /** 1560 * Adds fields to the GROUP BY part of the query. 1561 * <code> 1562 * $q->groupBy('u.id'); 1563 * </code> 1564 * 1565 * @param string $groupby Query GROUP BY part 1566 * 1567 * @return $this 1568 * @throws Doctrine_Query_Exception 1569 */ 1570 public function addGroupBy($groupby) 1571 { 1572 return $this->_addDqlQueryPart('groupby', $groupby, true); 1573 } 1574 1575 /** 1576 * Adds conditions to the HAVING part of the query. 1577 * 1578 * This methods add HAVING clauses. These clauses are used to narrow the 1579 * results by operating on aggregated values. 1580 * <code> 1581 * $q->having('num_phonenumbers > ?', 1); 1582 * </code> 1583 * 1584 * @param string $having Query HAVING part 1585 * @param mixed $params an array of parameters or a simple scalar 1586 * 1587 * @return $this 1588 * @throws Doctrine_Query_Exception 1589 */ 1590 public function addHaving($having, $params = array()) 1591 { 1592 if (is_array($params)) { 1593 $this->_params['having'] = array_merge($this->_params['having'], $params); 1594 } else { 1595 $this->_params['having'][] = $params; 1596 } 1597 return $this->_addDqlQueryPart('having', $having, true); 1598 } 1599 1600 /** 1601 * addOrderBy 1602 * adds fields to the ORDER BY part of the query 1603 * 1604 * @param string $orderby Query ORDER BY part 1605 * 1606 * @return $this 1607 * @throws Doctrine_Query_Exception 1608 */ 1609 public function addOrderBy($orderby) 1610 { 1611 return $this->_addDqlQueryPart('orderby', $orderby, true); 1612 } 1613 1614 /** 1615 * select 1616 * sets the SELECT part of the query 1617 * 1618 * @param string $select Query SELECT part 1619 * 1620 * @return $this 1621 * @throws Doctrine_Query_Exception 1622 */ 1623 public function select($select = null) 1624 { 1625 $this->_type = self::SELECT; 1626 if ($select) { 1627 return $this->_addDqlQueryPart('select', $select); 1628 } else { 1629 return $this; 1630 } 1631 } 1632 1633 /** 1634 * distinct 1635 * Makes the query SELECT DISTINCT. 1636 * <code> 1637 * $q->distinct(); 1638 * </code> 1639 * 1640 * @param bool $flag Whether or not the SELECT is DISTINCT (default true). 1641 * 1642 * @return $this 1643 */ 1644 public function distinct($flag = true) 1645 { 1646 $this->_sqlParts['distinct'] = (bool) $flag; 1647 return $this; 1648 } 1649 1650 /** 1651 * forUpdate 1652 * Makes the query SELECT FOR UPDATE. 1653 * 1654 * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true). 1655 * 1656 * @return $this 1657 */ 1658 public function forUpdate($flag = true) 1659 { 1660 $this->_sqlParts['forUpdate'] = (bool) $flag; 1661 return $this; 1662 } 1663 1664 /** 1665 * delete 1666 * sets the query type to DELETE 1667 * 1668 * @param null|string $from 1669 * 1670 * @return $this 1671 * @throws Doctrine_Query_Exception 1672 */ 1673 public function delete($from = null) 1674 { 1675 $this->_type = self::DELETE; 1676 if ($from != null) { 1677 return $this->_addDqlQueryPart('from', $from); 1678 } 1679 return $this; 1680 } 1681 1682 /** 1683 * update 1684 * sets the UPDATE part of the query 1685 * 1686 * @param string|null $from Query UPDATE part 1687 * 1688 * @return $this 1689 * @throws Doctrine_Query_Exception 1690 */ 1691 public function update($from = null) 1692 { 1693 $this->_type = self::UPDATE; 1694 if ($from != null) { 1695 return $this->_addDqlQueryPart('from', $from); 1696 } 1697 return $this; 1698 } 1699 1700 /** 1701 * set 1702 * sets the SET part of the query 1703 * 1704 * NOTE: best way is to use: $query->set(array('key' => 'value', ...)) 1705 * 1706 * @param string|array $key either field name or array(<key> => <value>, ...) 1707 * @param string|null $value will be ignored if $key is an array. placeholder '?' or a concrete value like NULL 1708 * or DATE() 1709 * @param mixed|null $params if $value is '?' this defines the value which replaces ?. 1710 * Attention: null will be ignored. if you want to set something to null use set('key', 1711 * '?', array(null)) or set(array('key' => null)) 1712 * 1713 * @return $this 1714 * @throws Doctrine_Query_Exception 1715 */ 1716 public function set($key, $value = null, $params = null) 1717 { 1718 if (is_array($key)) { 1719 foreach ($key as $k => $v) { 1720 $this->set($k, '?', array($v)); 1721 } 1722 return $this; 1723 } else { 1724 if ($params !== null) { 1725 if (is_array($params)) { 1726 $this->_params['set'] = array_merge($this->_params['set'], $params); 1727 } else { 1728 $this->_params['set'][] = $params; 1729 } 1730 } 1731 1732 return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true); 1733 } 1734 } 1735 1736 /** 1737 * from 1738 * sets the FROM part of the query 1739 * <code> 1740 * $q->from('User u'); 1741 * </code> 1742 * 1743 * @param string $from Query FROM part 1744 * 1745 * @return $this 1746 * @throws Doctrine_Query_Exception 1747 */ 1748 public function from($from) 1749 { 1750 return $this->_addDqlQueryPart('from', $from); 1751 } 1752 1753 /** 1754 * innerJoin 1755 * appends an INNER JOIN to the FROM part of the query 1756 * 1757 * @param string $join Query INNER JOIN 1758 * 1759 * @return $this 1760 * @throws Doctrine_Query_Exception 1761 */ 1762 public function innerJoin($join, $params = array()) 1763 { 1764 if (is_array($params)) { 1765 $this->_params['join'] = array_merge($this->_params['join'], $params); 1766 } else { 1767 $this->_params['join'][] = $params; 1768 } 1769 1770 return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true); 1771 } 1772 1773 /** 1774 * leftJoin 1775 * appends a LEFT JOIN to the FROM part of the query 1776 * 1777 * @param string $join Query LEFT JOIN 1778 * 1779 * @return $this 1780 * @throws Doctrine_Query_Exception 1781 */ 1782 public function leftJoin($join, $params = array()) 1783 { 1784 if (is_array($params)) { 1785 $this->_params['join'] = array_merge($this->_params['join'], $params); 1786 } else { 1787 $this->_params['join'][] = $params; 1788 } 1789 1790 return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true); 1791 } 1792 1793 /** 1794 * groupBy 1795 * sets the GROUP BY part of the query 1796 * 1797 * @param string $groupby Query GROUP BY part 1798 * 1799 * @return $this 1800 * @throws Doctrine_Query_Exception 1801 */ 1802 public function groupBy($groupby) 1803 { 1804 return $this->_addDqlQueryPart('groupby', $groupby); 1805 } 1806 1807 /** 1808 * where 1809 * sets the WHERE part of the query 1810 * 1811 * @param string $where Query WHERE part 1812 * @param mixed $params an array of parameters or a simple scalar 1813 * 1814 * @return $this 1815 * @throws Doctrine_Query_Exception 1816 */ 1817 public function where($where, $params = array()) 1818 { 1819 $this->_params['where'] = array(); 1820 1821 if (is_array($params)) { 1822 $this->_params['where'] = $params; 1823 } else { 1824 $this->_params['where'][] = $params; 1825 } 1826 1827 return $this->_addDqlQueryPart('where', $where); 1828 } 1829 1830 /** 1831 * having 1832 * sets the HAVING part of the query 1833 * 1834 * @param string $having Query HAVING part 1835 * @param mixed $params an array of parameters or a simple scalar 1836 * 1837 * @return $this 1838 * @throws Doctrine_Query_Exception 1839 */ 1840 public function having($having, $params = array()) 1841 { 1842 $this->_params['having'] = array(); 1843 if (is_array($params)) { 1844 $this->_params['having'] = $params; 1845 } else { 1846 $this->_params['having'][] = $params; 1847 } 1848 1849 return $this->_addDqlQueryPart('having', $having); 1850 } 1851 1852 /** 1853 * Sets the ORDER BY part of the query. 1854 * <code> 1855 * $q->orderBy('u.name'); 1856 * $query->orderBy('u.birthDate DESC'); 1857 * </code> 1858 * 1859 * @param string $orderby Query ORDER BY part 1860 * 1861 * @return $this 1862 * @throws Doctrine_Query_Exception 1863 */ 1864 public function orderBy($orderby) 1865 { 1866 return $this->_addDqlQueryPart('orderby', $orderby); 1867 } 1868 1869 /** 1870 * limit 1871 * sets the Query query limit 1872 * 1873 * @param integer $limit limit to be used for limiting the query results 1874 * 1875 * @return $this 1876 * @throws Doctrine_Query_Exception 1877 */ 1878 public function limit($limit) 1879 { 1880 return $this->_addDqlQueryPart('limit', $limit); 1881 } 1882 1883 /** 1884 * offset 1885 * sets the Query query offset 1886 * 1887 * @param integer $offset offset to be used for paginating the query 1888 * 1889 * @return $this 1890 * @throws Doctrine_Query_Exception 1891 */ 1892 public function offset($offset) 1893 { 1894 return $this->_addDqlQueryPart('offset', $offset); 1895 } 1896 1897 /** 1898 * Resets all the sql parts. 1899 * 1900 * @return void 1901 */ 1902 protected function clear() 1903 { 1904 $this->_sqlParts = array( 1905 'select' => array(), 1906 'distinct' => false, 1907 'forUpdate' => false, 1908 'from' => array(), 1909 'set' => array(), 1910 'join' => array(), 1911 'where' => array(), 1912 'groupby' => array(), 1913 'having' => array(), 1914 'orderby' => array(), 1915 'limit' => false, 1916 'offset' => false, 1917 ); 1918 } 1919 1920 public function setHydrationMode($hydrationMode) 1921 { 1922 $this->_hydrator->setHydrationMode($hydrationMode); 1923 return $this; 1924 } 1925 1926 /** 1927 * Gets the components of this query. 1928 */ 1929 public function getQueryComponents() 1930 { 1931 return $this->_queryComponents; 1932 } 1933 1934 /** 1935 * Return the SQL parts. 1936 * 1937 * @return array The parts 1938 */ 1939 public function getSqlParts() 1940 { 1941 return $this->_sqlParts; 1942 } 1943 1944 /** 1945 * getType 1946 * 1947 * returns the type of this query object 1948 * by default the type is Doctrine_Query_Abstract::SELECT but if update() or delete() 1949 * are being called the type is Doctrine_Query_Abstract::UPDATE and Doctrine_Query_Abstract::DELETE, 1950 * respectively 1951 * 1952 * @see Doctrine_Query_Abstract::SELECT 1953 * @see Doctrine_Query_Abstract::UPDATE 1954 * @see Doctrine_Query_Abstract::DELETE 1955 * 1956 * @return integer return the query type 1957 */ 1958 public function getType() 1959 { 1960 return $this->_type; 1961 } 1962 1963 /** 1964 * useResultCache 1965 * 1966 * @throws Doctrine_Query_Exception 1967 * 1968 * @param bool|Doctrine_Cache_Interface|null $driver cache driver (true: use default driver | null: disable caching) 1969 * @param integer $timeToLive (in sec) how long the cache entry is valid 1970 * @param string $resultCacheHash The key to use for storing the queries result cache entry 1971 * 1972 * @return $this this object 1973 */ 1974 public function useResultCache($driver = true, $timeToLive = null, $resultCacheHash = null) 1975 { 1976 if ($driver !== null && $driver !== true && ! ($driver instanceOf Doctrine_Cache_Interface)) { 1977 $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.'; 1978 throw new Doctrine_Query_Exception($msg); 1979 } 1980 $this->_resultCache = $driver; 1981 $this->_resultCacheHash = $resultCacheHash; 1982 1983 if ($timeToLive !== null) { 1984 $this->setResultCacheLifeSpan($timeToLive); 1985 } 1986 return $this; 1987 } 1988 1989 /** 1990 * Set the result cache hash to be used for storing the results in the cache driver 1991 * 1992 * @param string $resultCacheHash 1993 * 1994 * @return $this 1995 */ 1996 public function setResultCacheHash($resultCacheHash) 1997 { 1998 $this->_resultCacheHash = $resultCacheHash; 1999 2000 return $this; 2001 } 2002 2003 /** 2004 * Clear the result cache entry for this query 2005 * 2006 * @return $this 2007 * @throws Doctrine_Exception 2008 */ 2009 public function clearResultCache() 2010 { 2011 $this->getResultCacheDriver() 2012 ->delete($this->getResultCacheHash()); 2013 2014 return $this; 2015 } 2016 2017 /** 2018 * useQueryCache 2019 * 2020 * @throws Doctrine_Query_Exception 2021 * 2022 * @param Doctrine_Cache_Interface|bool $driver cache driver 2023 * @param integer $timeToLive how long the cache entry is valid 2024 * 2025 * @return $this this object 2026 */ 2027 public function useQueryCache($driver = true, $timeToLive = null) 2028 { 2029 if ($driver !== null && $driver !== true && $driver !== false && ! ($driver instanceOf Doctrine_Cache_Interface)) { 2030 $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.'; 2031 throw new Doctrine_Query_Exception($msg); 2032 } 2033 $this->_queryCache = $driver; 2034 2035 if ($timeToLive !== null) { 2036 $this->setQueryCacheLifeSpan($timeToLive); 2037 } 2038 return $this; 2039 } 2040 2041 /** 2042 * expireCache 2043 * 2044 * @param boolean $expire whether or not to force cache expiration 2045 * 2046 * @return $this this object 2047 */ 2048 public function expireResultCache($expire = true) 2049 { 2050 $this->_expireResultCache = $expire; 2051 return $this; 2052 } 2053 2054 /** 2055 * expireQueryCache 2056 * 2057 * @param boolean $expire whether or not to force cache expiration 2058 * 2059 * @return $this this object 2060 */ 2061 public function expireQueryCache($expire = true) 2062 { 2063 $this->_expireQueryCache = $expire; 2064 return $this; 2065 } 2066 2067 /** 2068 * setResultCacheLifeSpan 2069 * 2070 * @param integer $timeToLive how long the cache entry is valid (in seconds) 2071 * 2072 * @return $this this object 2073 */ 2074 public function setResultCacheLifeSpan($timeToLive) 2075 { 2076 if ($timeToLive !== null) { 2077 $timeToLive = (int) $timeToLive; 2078 } 2079 $this->_resultCacheTTL = $timeToLive; 2080 2081 return $this; 2082 } 2083 2084 /** 2085 * Gets the life span of the result cache in seconds. 2086 * 2087 * @return integer 2088 */ 2089 public function getResultCacheLifeSpan() 2090 { 2091 return $this->_resultCacheTTL; 2092 } 2093 2094 /** 2095 * setQueryCacheLifeSpan 2096 * 2097 * @param integer $timeToLive how long the cache entry is valid 2098 * 2099 * @return $this this object 2100 */ 2101 public function setQueryCacheLifeSpan($timeToLive) 2102 { 2103 if ($timeToLive !== null) { 2104 $timeToLive = (int) $timeToLive; 2105 } 2106 $this->_queryCacheTTL = $timeToLive; 2107 2108 return $this; 2109 } 2110 2111 /** 2112 * Gets the life span of the query cache the Query object is using. 2113 * 2114 * @return integer The life span in seconds. 2115 */ 2116 public function getQueryCacheLifeSpan() 2117 { 2118 return $this->_queryCacheTTL; 2119 } 2120 2121 /** 2122 * getResultCacheDriver 2123 * returns the cache driver used for caching result sets 2124 * 2125 * @return Doctrine_Cache_Interface|boolean|null cache driver 2126 * @throws Doctrine_Exception 2127 */ 2128 public function getResultCacheDriver() 2129 { 2130 if ($this->_resultCache instanceof Doctrine_Cache_Interface) { 2131 return $this->_resultCache; 2132 } else { 2133 return $this->_conn->getResultCacheDriver(); 2134 } 2135 } 2136 2137 /** 2138 * getQueryCacheDriver 2139 * returns the cache driver used for caching queries 2140 * 2141 * @return Doctrine_Cache_Interface|boolean|null cache driver 2142 * @throws Doctrine_Exception 2143 */ 2144 public function getQueryCacheDriver() 2145 { 2146 if ($this->_queryCache instanceof Doctrine_Cache_Interface) { 2147 return $this->_queryCache; 2148 } else { 2149 return $this->_conn->getQueryCacheDriver(); 2150 } 2151 } 2152 2153 /** 2154 * getConnection 2155 * 2156 * @return Doctrine_Connection 2157 */ 2158 public function getConnection() 2159 { 2160 return $this->_conn; 2161 } 2162 2163 /** 2164 * Checks if there's at least one DQL part defined to the internal parts collection. 2165 * 2166 * @param string $queryPartName The name of the query part. 2167 * @return boolean 2168 */ 2169 protected function _hasDqlQueryPart($queryPartName) 2170 { 2171 return count($this->_dqlParts[$queryPartName]) > 0; 2172 } 2173 2174 /** 2175 * Adds a DQL part to the internal parts collection. 2176 * 2177 * This method add the part specified to the array named by $queryPartName. 2178 * Most part names support multiple parts addition. 2179 * 2180 * @see $_dqlParts; 2181 * @see Doctrine_Query::getDqlPart() 2182 * 2183 * @param string $queryPartName The name of the query part. 2184 * @param string $queryPart The actual query part to add. 2185 * @param boolean $append Whether to append $queryPart to already existing 2186 * parts under the same $queryPartName. Defaults to FALSE 2187 * (previously added parts with the same name get overridden). 2188 * 2189 * @return $this 2190 * @throws Doctrine_Query_Exception 2191 */ 2192 protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false) 2193 { 2194 // We should prevent nullable query parts 2195 if ($queryPart === null) { 2196 throw new Doctrine_Query_Exception('Cannot define NULL as part of query when defining \'' . $queryPartName . '\'.'); 2197 } 2198 2199 if ($append) { 2200 $this->_dqlParts[$queryPartName][] = $queryPart; 2201 } else { 2202 $this->_dqlParts[$queryPartName] = array($queryPart); 2203 } 2204 2205 $this->_state = Doctrine_Query::STATE_DIRTY; 2206 return $this; 2207 } 2208 2209 /** 2210 * _processDqlQueryPart 2211 * parses given query part 2212 * 2213 * @param string $queryPartName the name of the query part 2214 * @param array $queryParts an array containing the query part data 2215 * 2216 * @todo Better description. "parses given query part" ??? Then wheres the difference 2217 * between process/parseQueryPart? I suppose this does something different. 2218 * @throws Doctrine_Query_Exception 2219 */ 2220 protected function _processDqlQueryPart($queryPartName, $queryParts) 2221 { 2222 $this->removeSqlQueryPart($queryPartName); 2223 2224 if (is_array($queryParts) && ! empty($queryParts)) { 2225 foreach ($queryParts as $queryPart) { 2226 $parser = $this->_getParser($queryPartName); 2227 $sql = $parser->parse($queryPart); 2228 if (isset($sql)) { 2229 if ($queryPartName == 'limit' || $queryPartName == 'offset') { 2230 $this->setSqlQueryPart($queryPartName, $sql); 2231 } else { 2232 $this->addSqlQueryPart($queryPartName, $sql); 2233 } 2234 } 2235 } 2236 } 2237 } 2238 2239 /** 2240 * _getParser 2241 * parser lazy-loader 2242 * 2243 * @throws Doctrine_Query_Exception if unknown parser name given 2244 * @return Doctrine_Query_Part 2245 * @todo Doc/Description: What is the parameter for? Which parsers are available? 2246 */ 2247 protected function _getParser($name) 2248 { 2249 if ( ! isset($this->_parsers[$name])) { 2250 $class = 'Doctrine_Query_' . ucwords(strtolower($name)); 2251 2252 Doctrine_Core::autoload($class); 2253 2254 if ( ! class_exists($class)) { 2255 throw new Doctrine_Query_Exception('Unknown parser ' . $name); 2256 } 2257 2258 $this->_parsers[$name] = new $class($this, $this->_tokenizer); 2259 } 2260 2261 return $this->_parsers[$name]; 2262 } 2263 2264 /** 2265 * Gets the SQL query that corresponds to this query object. 2266 * The returned SQL syntax depends on the connection driver that is used 2267 * by this query object at the time of this method call. 2268 * 2269 * @param array $params 2270 * 2271 * @return string 2272 */ 2273 abstract public function getSqlQuery($params = array()); 2274 2275 /** 2276 * parseDqlQuery 2277 * parses a dql query 2278 * 2279 * @param string $query query to be parsed 2280 * @return $this this object 2281 */ 2282 abstract public function parseDqlQuery($query); 2283 2284 /** 2285 * toString magic call 2286 * this method is automatically called when Doctrine_Query object is trying to be used as a string 2287 * So, it it converted into its DQL correspondant 2288 * 2289 * @return string DQL string 2290 */ 2291 public function __toString() 2292 { 2293 return $this->getDql(); 2294 } 2295 2296 /** 2297 * Gets the disableLimitSubquery property. 2298 * 2299 * @return boolean 2300 */ 2301 public function getDisableLimitSubquery() 2302 { 2303 return $this->disableLimitSubquery; 2304 } 2305 2306 /** 2307 * Allows you to set the disableLimitSubquery property -- setting this to true will 2308 * restrict the query object from using the limit sub query method of tranversing many relationships. 2309 * 2310 * @param boolean $disableLimitSubquery 2311 */ 2312 public function setDisableLimitSubquery($disableLimitSubquery) 2313 { 2314 $this->disableLimitSubquery = $disableLimitSubquery; 2315 } 2316} 2317