AbstractQuery.php 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the LGPL. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ORM;
  20. use Doctrine\DBAL\Types\Type,
  21. Doctrine\ORM\Query\QueryException;
  22. /**
  23. * Base contract for ORM queries. Base class for Query and NativeQuery.
  24. *
  25. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  26. * @link www.doctrine-project.org
  27. * @since 2.0
  28. * @version $Revision$
  29. * @author Benjamin Eberlei <kontakt@beberlei.de>
  30. * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
  31. * @author Jonathan Wage <jonwage@gmail.com>
  32. * @author Roman Borschel <roman@code-factory.org>
  33. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
  34. */
  35. abstract class AbstractQuery
  36. {
  37. /* Hydration mode constants */
  38. /**
  39. * Hydrates an object graph. This is the default behavior.
  40. */
  41. const HYDRATE_OBJECT = 1;
  42. /**
  43. * Hydrates an array graph.
  44. */
  45. const HYDRATE_ARRAY = 2;
  46. /**
  47. * Hydrates a flat, rectangular result set with scalar values.
  48. */
  49. const HYDRATE_SCALAR = 3;
  50. /**
  51. * Hydrates a single scalar value.
  52. */
  53. const HYDRATE_SINGLE_SCALAR = 4;
  54. /**
  55. * Very simple object hydrator (optimized for performance).
  56. */
  57. const HYDRATE_SIMPLEOBJECT = 5;
  58. /**
  59. * @var array The parameter map of this query.
  60. */
  61. protected $_params = array();
  62. /**
  63. * @var array The parameter type map of this query.
  64. */
  65. protected $_paramTypes = array();
  66. /**
  67. * @var ResultSetMapping The user-specified ResultSetMapping to use.
  68. */
  69. protected $_resultSetMapping;
  70. /**
  71. * @var Doctrine\ORM\EntityManager The entity manager used by this query object.
  72. */
  73. protected $_em;
  74. /**
  75. * @var array The map of query hints.
  76. */
  77. protected $_hints = array();
  78. /**
  79. * @var integer The hydration mode.
  80. */
  81. protected $_hydrationMode = self::HYDRATE_OBJECT;
  82. /**
  83. * The locally set cache driver used for caching result sets of this query.
  84. *
  85. * @var CacheDriver
  86. */
  87. protected $_resultCacheDriver;
  88. /**
  89. * Boolean flag for whether or not to cache the results of this query.
  90. *
  91. * @var boolean
  92. */
  93. protected $_useResultCache;
  94. /**
  95. * @var string The id to store the result cache entry under.
  96. */
  97. protected $_resultCacheId;
  98. /**
  99. * @var boolean Boolean value that indicates whether or not expire the result cache.
  100. */
  101. protected $_expireResultCache = false;
  102. /**
  103. * @var int Result Cache lifetime.
  104. */
  105. protected $_resultCacheTTL;
  106. /**
  107. * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
  108. *
  109. * @param Doctrine\ORM\EntityManager $entityManager
  110. */
  111. public function __construct(EntityManager $em)
  112. {
  113. $this->_em = $em;
  114. }
  115. /**
  116. * Retrieves the associated EntityManager of this Query instance.
  117. *
  118. * @return Doctrine\ORM\EntityManager
  119. */
  120. public function getEntityManager()
  121. {
  122. return $this->_em;
  123. }
  124. /**
  125. * Frees the resources used by the query object.
  126. *
  127. * Resets Parameters, Parameter Types and Query Hints.
  128. *
  129. * @return void
  130. */
  131. public function free()
  132. {
  133. $this->_params = array();
  134. $this->_paramTypes = array();
  135. $this->_hints = array();
  136. }
  137. /**
  138. * Get all defined parameters.
  139. *
  140. * @return array The defined query parameters.
  141. */
  142. public function getParameters()
  143. {
  144. return $this->_params;
  145. }
  146. /**
  147. * Gets a query parameter.
  148. *
  149. * @param mixed $key The key (index or name) of the bound parameter.
  150. * @return mixed The value of the bound parameter.
  151. */
  152. public function getParameter($key)
  153. {
  154. return isset($this->_params[$key]) ? $this->_params[$key] : null;
  155. }
  156. /**
  157. * Gets the SQL query that corresponds to this query object.
  158. * The returned SQL syntax depends on the connection driver that is used
  159. * by this query object at the time of this method call.
  160. *
  161. * @return string SQL query
  162. */
  163. abstract public function getSQL();
  164. /**
  165. * Sets a query parameter.
  166. *
  167. * @param string|integer $key The parameter position or name.
  168. * @param mixed $value The parameter value.
  169. * @param string $type The parameter type. If specified, the given value will be run through
  170. * the type conversion of this type. This is usually not needed for
  171. * strings and numeric types.
  172. * @return Doctrine\ORM\AbstractQuery This query instance.
  173. */
  174. public function setParameter($key, $value, $type = null)
  175. {
  176. if ($type === null) {
  177. $type = Query\ParameterTypeInferer::inferType($value);
  178. }
  179. $this->_paramTypes[$key] = $type;
  180. $this->_params[$key] = $value;
  181. return $this;
  182. }
  183. /**
  184. * Sets a collection of query parameters.
  185. *
  186. * @param array $params
  187. * @param array $types
  188. * @return Doctrine\ORM\AbstractQuery This query instance.
  189. */
  190. public function setParameters(array $params, array $types = array())
  191. {
  192. foreach ($params as $key => $value) {
  193. if (isset($types[$key])) {
  194. $this->setParameter($key, $value, $types[$key]);
  195. } else {
  196. $this->setParameter($key, $value);
  197. }
  198. }
  199. return $this;
  200. }
  201. /**
  202. * Sets the ResultSetMapping that should be used for hydration.
  203. *
  204. * @param ResultSetMapping $rsm
  205. * @return Doctrine\ORM\AbstractQuery
  206. */
  207. public function setResultSetMapping(Query\ResultSetMapping $rsm)
  208. {
  209. $this->_resultSetMapping = $rsm;
  210. return $this;
  211. }
  212. /**
  213. * Defines a cache driver to be used for caching result sets.
  214. *
  215. * @param Doctrine\Common\Cache\Cache $driver Cache driver
  216. * @return Doctrine\ORM\AbstractQuery
  217. */
  218. public function setResultCacheDriver($resultCacheDriver = null)
  219. {
  220. if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
  221. throw ORMException::invalidResultCacheDriver();
  222. }
  223. $this->_resultCacheDriver = $resultCacheDriver;
  224. if ($resultCacheDriver) {
  225. $this->_useResultCache = true;
  226. }
  227. return $this;
  228. }
  229. /**
  230. * Returns the cache driver used for caching result sets.
  231. *
  232. * @return Doctrine\Common\Cache\Cache Cache driver
  233. */
  234. public function getResultCacheDriver()
  235. {
  236. if ($this->_resultCacheDriver) {
  237. return $this->_resultCacheDriver;
  238. } else {
  239. return $this->_em->getConfiguration()->getResultCacheImpl();
  240. }
  241. }
  242. /**
  243. * Set whether or not to cache the results of this query and if so, for
  244. * how long and which ID to use for the cache entry.
  245. *
  246. * @param boolean $bool
  247. * @param integer $timeToLive
  248. * @param string $resultCacheId
  249. * @return Doctrine\ORM\AbstractQuery This query instance.
  250. */
  251. public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
  252. {
  253. $this->_useResultCache = $bool;
  254. if ($timeToLive) {
  255. $this->setResultCacheLifetime($timeToLive);
  256. }
  257. if ($resultCacheId) {
  258. $this->_resultCacheId = $resultCacheId;
  259. }
  260. return $this;
  261. }
  262. /**
  263. * Defines how long the result cache will be active before expire.
  264. *
  265. * @param integer $timeToLive How long the cache entry is valid.
  266. * @return Doctrine\ORM\AbstractQuery This query instance.
  267. */
  268. public function setResultCacheLifetime($timeToLive)
  269. {
  270. if ($timeToLive !== null) {
  271. $timeToLive = (int) $timeToLive;
  272. }
  273. $this->_resultCacheTTL = $timeToLive;
  274. return $this;
  275. }
  276. /**
  277. * Retrieves the lifetime of resultset cache.
  278. *
  279. * @return integer
  280. */
  281. public function getResultCacheLifetime()
  282. {
  283. return $this->_resultCacheTTL;
  284. }
  285. /**
  286. * Defines if the result cache is active or not.
  287. *
  288. * @param boolean $expire Whether or not to force resultset cache expiration.
  289. * @return Doctrine\ORM\AbstractQuery This query instance.
  290. */
  291. public function expireResultCache($expire = true)
  292. {
  293. $this->_expireResultCache = $expire;
  294. return $this;
  295. }
  296. /**
  297. * Retrieves if the resultset cache is active or not.
  298. *
  299. * @return boolean
  300. */
  301. public function getExpireResultCache()
  302. {
  303. return $this->_expireResultCache;
  304. }
  305. /**
  306. * Change the default fetch mode of an association for this query.
  307. *
  308. * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
  309. *
  310. * @param string $class
  311. * @param string $assocName
  312. * @param int $fetchMode
  313. * @return AbstractQuery
  314. */
  315. public function setFetchMode($class, $assocName, $fetchMode)
  316. {
  317. if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
  318. $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
  319. }
  320. $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
  321. return $this;
  322. }
  323. /**
  324. * Defines the processing mode to be used during hydration / result set transformation.
  325. *
  326. * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
  327. * One of the Query::HYDRATE_* constants.
  328. * @return Doctrine\ORM\AbstractQuery This query instance.
  329. */
  330. public function setHydrationMode($hydrationMode)
  331. {
  332. $this->_hydrationMode = $hydrationMode;
  333. return $this;
  334. }
  335. /**
  336. * Gets the hydration mode currently used by the query.
  337. *
  338. * @return integer
  339. */
  340. public function getHydrationMode()
  341. {
  342. return $this->_hydrationMode;
  343. }
  344. /**
  345. * Gets the list of results for the query.
  346. *
  347. * Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT).
  348. *
  349. * @return array
  350. */
  351. public function getResult($hydrationMode = self::HYDRATE_OBJECT)
  352. {
  353. return $this->execute(array(), $hydrationMode);
  354. }
  355. /**
  356. * Gets the array of results for the query.
  357. *
  358. * Alias for execute(array(), HYDRATE_ARRAY).
  359. *
  360. * @return array
  361. */
  362. public function getArrayResult()
  363. {
  364. return $this->execute(array(), self::HYDRATE_ARRAY);
  365. }
  366. /**
  367. * Gets the scalar results for the query.
  368. *
  369. * Alias for execute(array(), HYDRATE_SCALAR).
  370. *
  371. * @return array
  372. */
  373. public function getScalarResult()
  374. {
  375. return $this->execute(array(), self::HYDRATE_SCALAR);
  376. }
  377. /**
  378. * Get exactly one result or null.
  379. *
  380. * @throws NonUniqueResultException
  381. * @param int $hydrationMode
  382. * @return mixed
  383. */
  384. public function getOneOrNullResult($hydrationMode = null)
  385. {
  386. $result = $this->execute(array(), $hydrationMode);
  387. if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  388. return null;
  389. }
  390. if (is_array($result)) {
  391. if (count($result) > 1) {
  392. throw new NonUniqueResultException;
  393. }
  394. return array_shift($result);
  395. }
  396. return $result;
  397. }
  398. /**
  399. * Gets the single result of the query.
  400. *
  401. * Enforces the presence as well as the uniqueness of the result.
  402. *
  403. * If the result is not unique, a NonUniqueResultException is thrown.
  404. * If there is no result, a NoResultException is thrown.
  405. *
  406. * @param integer $hydrationMode
  407. * @return mixed
  408. * @throws NonUniqueResultException If the query result is not unique.
  409. * @throws NoResultException If the query returned no result.
  410. */
  411. public function getSingleResult($hydrationMode = null)
  412. {
  413. $result = $this->execute(array(), $hydrationMode);
  414. if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  415. throw new NoResultException;
  416. }
  417. if (is_array($result)) {
  418. if (count($result) > 1) {
  419. throw new NonUniqueResultException;
  420. }
  421. return array_shift($result);
  422. }
  423. return $result;
  424. }
  425. /**
  426. * Gets the single scalar result of the query.
  427. *
  428. * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
  429. *
  430. * @return mixed
  431. * @throws QueryException If the query result is not unique.
  432. */
  433. public function getSingleScalarResult()
  434. {
  435. return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
  436. }
  437. /**
  438. * Sets a query hint. If the hint name is not recognized, it is silently ignored.
  439. *
  440. * @param string $name The name of the hint.
  441. * @param mixed $value The value of the hint.
  442. * @return Doctrine\ORM\AbstractQuery
  443. */
  444. public function setHint($name, $value)
  445. {
  446. $this->_hints[$name] = $value;
  447. return $this;
  448. }
  449. /**
  450. * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
  451. *
  452. * @param string $name The name of the hint.
  453. * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
  454. */
  455. public function getHint($name)
  456. {
  457. return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
  458. }
  459. /**
  460. * Return the key value map of query hints that are currently set.
  461. *
  462. * @return array
  463. */
  464. public function getHints()
  465. {
  466. return $this->_hints;
  467. }
  468. /**
  469. * Executes the query and returns an IterableResult that can be used to incrementally
  470. * iterate over the result.
  471. *
  472. * @param array $params The query parameters.
  473. * @param integer $hydrationMode The hydration mode to use.
  474. * @return IterableResult
  475. */
  476. public function iterate(array $params = array(), $hydrationMode = null)
  477. {
  478. if ($hydrationMode !== null) {
  479. $this->setHydrationMode($hydrationMode);
  480. }
  481. if ($params) {
  482. $this->setParameters($params);
  483. }
  484. $stmt = $this->_doExecute();
  485. return $this->_em->newHydrator($this->_hydrationMode)->iterate(
  486. $stmt, $this->_resultSetMapping, $this->_hints
  487. );
  488. }
  489. /**
  490. * Executes the query.
  491. *
  492. * @param array $params Any additional query parameters.
  493. * @param integer $hydrationMode Processing mode to be used during the hydration process.
  494. * @return mixed
  495. */
  496. public function execute($params = array(), $hydrationMode = null)
  497. {
  498. if ($hydrationMode !== null) {
  499. $this->setHydrationMode($hydrationMode);
  500. }
  501. if ($params) {
  502. $this->setParameters($params);
  503. }
  504. // Check result cache
  505. if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
  506. list($key, $hash) = $this->getResultCacheId();
  507. $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash);
  508. if ($cached === false || !isset($cached[$key])) {
  509. // Cache miss.
  510. $stmt = $this->_doExecute();
  511. $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
  512. $stmt, $this->_resultSetMapping, $this->_hints
  513. );
  514. $cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL);
  515. return $result;
  516. } else {
  517. // Cache hit.
  518. return $cached[$key];
  519. }
  520. }
  521. $stmt = $this->_doExecute();
  522. if (is_numeric($stmt)) {
  523. return $stmt;
  524. }
  525. return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
  526. $stmt, $this->_resultSetMapping, $this->_hints
  527. );
  528. }
  529. /**
  530. * Set the result cache id to use to store the result set cache entry.
  531. * If this is not explicitely set by the developer then a hash is automatically
  532. * generated for you.
  533. *
  534. * @param string $id
  535. * @return Doctrine\ORM\AbstractQuery This query instance.
  536. */
  537. public function setResultCacheId($id)
  538. {
  539. $this->_resultCacheId = $id;
  540. return $this;
  541. }
  542. /**
  543. * Get the result cache id to use to store the result set cache entry.
  544. * Will return the configured id if it exists otherwise a hash will be
  545. * automatically generated for you.
  546. *
  547. * @return array ($key, $hash)
  548. */
  549. protected function getResultCacheId()
  550. {
  551. if ($this->_resultCacheId) {
  552. return array($this->_resultCacheId, $this->_resultCacheId);
  553. } else {
  554. $params = $this->_params;
  555. foreach ($params AS $key => $value) {
  556. if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
  557. if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
  558. $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
  559. } else {
  560. $class = $this->_em->getClassMetadata(get_class($value));
  561. $idValues = $class->getIdentifierValues($value);
  562. }
  563. $params[$key] = $idValues;
  564. } else {
  565. $params[$key] = $value;
  566. }
  567. }
  568. $sql = $this->getSql();
  569. ksort($this->_hints);
  570. $key = implode(";", (array)$sql) . var_export($params, true) .
  571. var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode;
  572. return array($key, md5($key));
  573. }
  574. }
  575. /**
  576. * Executes the query and returns a the resulting Statement object.
  577. *
  578. * @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
  579. */
  580. abstract protected function _doExecute();
  581. /**
  582. * Cleanup Query resource when clone is called.
  583. *
  584. * @return void
  585. */
  586. public function __clone()
  587. {
  588. $this->_params = array();
  589. $this->_paramTypes = array();
  590. $this->_hints = array();
  591. }
  592. }