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.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\ORM;
22 use Doctrine\Common\Util\ClassUtils;
23 use Doctrine\Common\Collections\ArrayCollection;
25 use Doctrine\DBAL\Types\Type;
26 use Doctrine\DBAL\Cache\QueryCacheProfile;
28 use Doctrine\ORM\Query\QueryException;
31 * Base contract for ORM queries. Base class for Query and NativeQuery.
33 * @link www.doctrine-project.org
35 * @author Benjamin Eberlei <kontakt@beberlei.de>
36 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
37 * @author Jonathan Wage <jonwage@gmail.com>
38 * @author Roman Borschel <roman@code-factory.org>
39 * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
41 abstract class AbstractQuery
43 /* Hydration mode constants */
45 * Hydrates an object graph. This is the default behavior.
47 const HYDRATE_OBJECT = 1;
49 * Hydrates an array graph.
51 const HYDRATE_ARRAY = 2;
53 * Hydrates a flat, rectangular result set with scalar values.
55 const HYDRATE_SCALAR = 3;
57 * Hydrates a single scalar value.
59 const HYDRATE_SINGLE_SCALAR = 4;
62 * Very simple object hydrator (optimized for performance).
64 const HYDRATE_SIMPLEOBJECT = 5;
67 * @var \Doctrine\Common\Collections\ArrayCollection The parameter map of this query.
69 protected $parameters;
72 * @var ResultSetMapping The user-specified ResultSetMapping to use.
74 protected $_resultSetMapping;
77 * @var \Doctrine\ORM\EntityManager The entity manager used by this query object.
82 * @var array The map of query hints.
84 protected $_hints = array();
87 * @var integer The hydration mode.
89 protected $_hydrationMode = self::HYDRATE_OBJECT;
92 * @param \Doctrine\DBAL\Cache\QueryCacheProfile
94 protected $_queryCacheProfile;
97 * @var boolean Boolean value that indicates whether or not expire the result cache.
99 protected $_expireResultCache = false;
102 * @param \Doctrine\DBAL\Cache\QueryCacheProfile
104 protected $_hydrationCacheProfile;
107 * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
109 * @param \Doctrine\ORM\EntityManager $entityManager
111 public function __construct(EntityManager $em)
114 $this->parameters = new ArrayCollection();
118 * Gets the SQL query that corresponds to this query object.
119 * The returned SQL syntax depends on the connection driver that is used
120 * by this query object at the time of this method call.
122 * @return string SQL query
124 abstract public function getSQL();
127 * Retrieves the associated EntityManager of this Query instance.
129 * @return \Doctrine\ORM\EntityManager
131 public function getEntityManager()
137 * Frees the resources used by the query object.
139 * Resets Parameters, Parameter Types and Query Hints.
143 public function free()
145 $this->parameters = new ArrayCollection();
147 $this->_hints = array();
151 * Get all defined parameters.
153 * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
155 public function getParameters()
157 return $this->parameters;
161 * Gets a query parameter.
163 * @param mixed $key The key (index or name) of the bound parameter.
165 * @return mixed The value of the bound parameter.
167 public function getParameter($key)
169 $filteredParameters = $this->parameters->filter(
170 function ($parameter) use ($key)
172 // Must not be identical because of string to integer conversion
173 return ($key == $parameter->getName());
177 return count($filteredParameters) ? $filteredParameters->first() : null;
181 * Sets a collection of query parameters.
183 * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
185 * @return \Doctrine\ORM\AbstractQuery This query instance.
187 public function setParameters($parameters)
189 // BC compatibility with 2.3-
190 if (is_array($parameters)) {
191 $parameterCollection = new ArrayCollection();
193 foreach ($parameters as $key => $value) {
194 $parameter = new Query\Parameter($key, $value);
196 $parameterCollection->add($parameter);
199 $parameters = $parameterCollection;
202 $this->parameters = $parameters;
208 * Sets a query parameter.
210 * @param string|integer $key The parameter position or name.
211 * @param mixed $value The parameter value.
212 * @param string $type The parameter type. If specified, the given value will be run through
213 * the type conversion of this type. This is usually not needed for
214 * strings and numeric types.
216 * @return \Doctrine\ORM\AbstractQuery This query instance.
218 public function setParameter($key, $value, $type = null)
220 $filteredParameters = $this->parameters->filter(
221 function ($parameter) use ($key)
223 // Must not be identical because of string to integer conversion
224 return ($key == $parameter->getName());
228 if (count($filteredParameters)) {
229 $parameter = $filteredParameters->first();
230 $parameter->setValue($value, $type);
235 $parameter = new Query\Parameter($key, $value, $type);
237 $this->parameters->add($parameter);
243 * Process an individual parameter value
245 * @param mixed $value
248 public function processParameterValue($value)
251 case is_array($value):
252 foreach ($value as $key => $paramValue) {
253 $paramValue = $this->processParameterValue($paramValue);
254 $value[$key] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
259 case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value)):
260 return $this->convertObjectParameterToScalarValue($value);
267 private function convertObjectParameterToScalarValue($value)
269 $class = $this->_em->getClassMetadata(get_class($value));
271 if ($class->isIdentifierComposite) {
272 throw new \InvalidArgumentException(
273 "Binding an entity with a composite primary key to a query is not supported. " .
274 "You should split the parameter into the explicit fields and bind them seperately."
278 $values = ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED)
279 ? $this->_em->getUnitOfWork()->getEntityIdentifier($value)
280 : $class->getIdentifierValues($value);
282 $value = $values[$class->getSingleIdentifierFieldName()];
285 throw new \InvalidArgumentException(
286 "Binding entities to query parameters only allowed for entities that have an identifier."
294 * Sets the ResultSetMapping that should be used for hydration.
296 * @param ResultSetMapping $rsm
297 * @return \Doctrine\ORM\AbstractQuery
299 public function setResultSetMapping(Query\ResultSetMapping $rsm)
301 $this->_resultSetMapping = $rsm;
307 * Set a cache profile for hydration caching.
309 * If no result cache driver is set in the QueryCacheProfile, the default
310 * result cache driver is used from the configuration.
312 * Important: Hydration caching does NOT register entities in the
313 * UnitOfWork when retrieved from the cache. Never use result cached
314 * entities for requests that also flush the EntityManager. If you want
315 * some form of caching with UnitOfWork registration you should use
316 * {@see AbstractQuery::setResultCacheProfile()}.
320 * $resultKey = "abc";
321 * $query->setHydrationCacheProfile(new QueryCacheProfile());
322 * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
324 * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
325 * @return \Doctrine\ORM\AbstractQuery
327 public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
329 if ( ! $profile->getResultCacheDriver()) {
330 $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
331 $profile = $profile->setResultCacheDriver($resultCacheDriver);
334 $this->_hydrationCacheProfile = $profile;
340 * @return \Doctrine\DBAL\Cache\QueryCacheProfile
342 public function getHydrationCacheProfile()
344 return $this->_hydrationCacheProfile;
348 * Set a cache profile for the result cache.
350 * If no result cache driver is set in the QueryCacheProfile, the default
351 * result cache driver is used from the configuration.
353 * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
354 * @return \Doctrine\ORM\AbstractQuery
356 public function setResultCacheProfile(QueryCacheProfile $profile = null)
358 if ( ! $profile->getResultCacheDriver()) {
359 $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
360 $profile = $profile->setResultCacheDriver($resultCacheDriver);
363 $this->_queryCacheProfile = $profile;
369 * Defines a cache driver to be used for caching result sets and implictly enables caching.
371 * @param \Doctrine\Common\Cache\Cache $driver Cache driver
372 * @return \Doctrine\ORM\AbstractQuery
374 public function setResultCacheDriver($resultCacheDriver = null)
376 if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
377 throw ORMException::invalidResultCacheDriver();
380 $this->_queryCacheProfile = $this->_queryCacheProfile
381 ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
382 : new QueryCacheProfile(0, null, $resultCacheDriver);
388 * Returns the cache driver used for caching result sets.
391 * @return \Doctrine\Common\Cache\Cache Cache driver
393 public function getResultCacheDriver()
395 if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
396 return $this->_queryCacheProfile->getResultCacheDriver();
399 return $this->_em->getConfiguration()->getResultCacheImpl();
403 * Set whether or not to cache the results of this query and if so, for
404 * how long and which ID to use for the cache entry.
406 * @param boolean $bool
407 * @param integer $lifetime
408 * @param string $resultCacheId
409 * @return \Doctrine\ORM\AbstractQuery This query instance.
411 public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
414 $this->setResultCacheLifetime($lifetime);
415 $this->setResultCacheId($resultCacheId);
420 $this->_queryCacheProfile = null;
426 * Defines how long the result cache will be active before expire.
428 * @param integer $lifetime How long the cache entry is valid.
429 * @return \Doctrine\ORM\AbstractQuery This query instance.
431 public function setResultCacheLifetime($lifetime)
433 $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
435 $this->_queryCacheProfile = $this->_queryCacheProfile
436 ? $this->_queryCacheProfile->setLifetime($lifetime)
437 : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
443 * Retrieves the lifetime of resultset cache.
448 public function getResultCacheLifetime()
450 return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
454 * Defines if the result cache is active or not.
456 * @param boolean $expire Whether or not to force resultset cache expiration.
457 * @return \Doctrine\ORM\AbstractQuery This query instance.
459 public function expireResultCache($expire = true)
461 $this->_expireResultCache = $expire;
467 * Retrieves if the resultset cache is active or not.
471 public function getExpireResultCache()
473 return $this->_expireResultCache;
477 * @return QueryCacheProfile
479 public function getQueryCacheProfile()
481 return $this->_queryCacheProfile;
485 * Change the default fetch mode of an association for this query.
487 * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
489 * @param string $class
490 * @param string $assocName
491 * @param int $fetchMode
492 * @return AbstractQuery
494 public function setFetchMode($class, $assocName, $fetchMode)
496 if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
497 $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
500 $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
506 * Defines the processing mode to be used during hydration / result set transformation.
508 * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
509 * One of the Query::HYDRATE_* constants.
510 * @return \Doctrine\ORM\AbstractQuery This query instance.
512 public function setHydrationMode($hydrationMode)
514 $this->_hydrationMode = $hydrationMode;
520 * Gets the hydration mode currently used by the query.
524 public function getHydrationMode()
526 return $this->_hydrationMode;
530 * Gets the list of results for the query.
532 * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
536 public function getResult($hydrationMode = self::HYDRATE_OBJECT)
538 return $this->execute(null, $hydrationMode);
542 * Gets the array of results for the query.
544 * Alias for execute(null, HYDRATE_ARRAY).
548 public function getArrayResult()
550 return $this->execute(null, self::HYDRATE_ARRAY);
554 * Gets the scalar results for the query.
556 * Alias for execute(null, HYDRATE_SCALAR).
560 public function getScalarResult()
562 return $this->execute(null, self::HYDRATE_SCALAR);
566 * Get exactly one result or null.
568 * @throws NonUniqueResultException
569 * @param int $hydrationMode
572 public function getOneOrNullResult($hydrationMode = null)
574 $result = $this->execute(null, $hydrationMode);
576 if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
580 if ( ! is_array($result)) {
584 if (count($result) > 1) {
585 throw new NonUniqueResultException;
588 return array_shift($result);
592 * Gets the single result of the query.
594 * Enforces the presence as well as the uniqueness of the result.
596 * If the result is not unique, a NonUniqueResultException is thrown.
597 * If there is no result, a NoResultException is thrown.
599 * @param integer $hydrationMode
601 * @throws NonUniqueResultException If the query result is not unique.
602 * @throws NoResultException If the query returned no result.
604 public function getSingleResult($hydrationMode = null)
606 $result = $this->execute(null, $hydrationMode);
608 if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
609 throw new NoResultException;
612 if ( ! is_array($result)) {
616 if (count($result) > 1) {
617 throw new NonUniqueResultException;
620 return array_shift($result);
624 * Gets the single scalar result of the query.
626 * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
629 * @throws QueryException If the query result is not unique.
631 public function getSingleScalarResult()
633 return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
637 * Sets a query hint. If the hint name is not recognized, it is silently ignored.
639 * @param string $name The name of the hint.
640 * @param mixed $value The value of the hint.
641 * @return \Doctrine\ORM\AbstractQuery
643 public function setHint($name, $value)
645 $this->_hints[$name] = $value;
651 * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
653 * @param string $name The name of the hint.
654 * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
656 public function getHint($name)
658 return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
662 * Return the key value map of query hints that are currently set.
666 public function getHints()
668 return $this->_hints;
672 * Executes the query and returns an IterableResult that can be used to incrementally
673 * iterate over the result.
675 * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters.
676 * @param integer $hydrationMode The hydration mode to use.
677 * @return \Doctrine\ORM\Internal\Hydration\IterableResult
679 public function iterate($parameters = null, $hydrationMode = null)
681 if ($hydrationMode !== null) {
682 $this->setHydrationMode($hydrationMode);
685 if ( ! empty($parameters)) {
686 $this->setParameters($parameters);
689 $stmt = $this->_doExecute();
691 return $this->_em->newHydrator($this->_hydrationMode)->iterate(
692 $stmt, $this->_resultSetMapping, $this->_hints
697 * Executes the query.
699 * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters Query parameters.
700 * @param integer $hydrationMode Processing mode to be used during the hydration process.
703 public function execute($parameters = null, $hydrationMode = null)
705 if ($hydrationMode !== null) {
706 $this->setHydrationMode($hydrationMode);
709 if ( ! empty($parameters)) {
710 $this->setParameters($parameters);
713 $setCacheEntry = function() {};
715 if ($this->_hydrationCacheProfile !== null) {
716 list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
718 $queryCacheProfile = $this->getHydrationCacheProfile();
719 $cache = $queryCacheProfile->getResultCacheDriver();
720 $result = $cache->fetch($cacheKey);
722 if (isset($result[$realCacheKey])) {
723 return $result[$realCacheKey];
730 $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
731 $result[$realCacheKey] = $data;
733 $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
737 $stmt = $this->_doExecute();
739 if (is_numeric($stmt)) {
740 $setCacheEntry($stmt);
745 $data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
746 $stmt, $this->_resultSetMapping, $this->_hints
749 $setCacheEntry($data);
755 * Get the result cache id to use to store the result set cache entry.
756 * Will return the configured id if it exists otherwise a hash will be
757 * automatically generated for you.
759 * @return array ($key, $hash)
761 protected function getHydrationCacheId()
763 $parameters = array();
765 foreach ($this->getParameters() as $parameter) {
766 $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
769 $sql = $this->getSQL();
770 $queryCacheProfile = $this->getHydrationCacheProfile();
771 $hints = $this->getHints();
772 $hints['hydrationMode'] = $this->getHydrationMode();
776 return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
780 * Set the result cache id to use to store the result set cache entry.
781 * If this is not explicitly set by the developer then a hash is automatically
785 * @return \Doctrine\ORM\AbstractQuery This query instance.
787 public function setResultCacheId($id)
789 $this->_queryCacheProfile = $this->_queryCacheProfile
790 ? $this->_queryCacheProfile->setCacheKey($id)
791 : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
797 * Get the result cache id to use to store the result set cache entry if set.
802 public function getResultCacheId()
804 return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
808 * Executes the query and returns a the resulting Statement object.
810 * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
812 abstract protected function _doExecute();
815 * Cleanup Query resource when clone is called.
819 public function __clone()
821 $this->parameters = new ArrayCollection();
823 $this->_hints = array();