Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Query.php
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 MIT license. For more information, see
17  * <http://www.doctrine-project.org>.
18  */
19
20 namespace Doctrine\ORM;
21
22 use Doctrine\Common\Collections\ArrayCollection;
23
24 use Doctrine\DBAL\LockMode;
25
26 use Doctrine\ORM\Query\Parser;
27 use Doctrine\ORM\Query\ParserResult;
28 use Doctrine\ORM\Query\QueryException;
29
30 /**
31  * A Query object represents a DQL query.
32  *
33  * @since   1.0
34  * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
35  * @author  Konsta Vesterinen <kvesteri@cc.hut.fi>
36  * @author  Roman Borschel <roman@code-factory.org>
37  */
38 final class Query extends AbstractQuery
39 {
40     /**
41      * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
42      */
43     const STATE_CLEAN  = 1;
44     /**
45      * A query object is in state DIRTY when it has DQL parts that have not yet been
46      * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
47      * is called.
48      */
49     const STATE_DIRTY = 2;
50
51     /* Query HINTS */
52     /**
53      * The refresh hint turns any query into a refresh query with the result that
54      * any local changes in entities are overridden with the fetched values.
55      *
56      * @var string
57      */
58     const HINT_REFRESH = 'doctrine.refresh';
59
60
61     /**
62      * Internal hint: is set to the proxy entity that is currently triggered for loading
63      *
64      * @var string
65      */
66     const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
67
68     /**
69      * The forcePartialLoad query hint forces a particular query to return
70      * partial objects.
71      *
72      * @var string
73      * @todo Rename: HINT_OPTIMIZE
74      */
75     const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
76     /**
77      * The includeMetaColumns query hint causes meta columns like foreign keys and
78      * discriminator columns to be selected and returned as part of the query result.
79      *
80      * This hint does only apply to non-object queries.
81      *
82      * @var string
83      */
84     const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
85
86     /**
87      * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
88      * are iterated and executed after the DQL has been parsed into an AST.
89      *
90      * @var string
91      */
92     const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
93
94     /**
95      * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
96      * and is used for generating the target SQL from any DQL AST tree.
97      *
98      * @var string
99      */
100     const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
101
102     //const HINT_READ_ONLY = 'doctrine.readOnly';
103
104     /**
105      * @var string
106      */
107     const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
108
109     /**
110      * @var string
111      */
112     const HINT_LOCK_MODE = 'doctrine.lockMode';
113
114
115     /**
116      * @var integer $_state   The current state of this query.
117      */
118     private $_state = self::STATE_CLEAN;
119
120     /**
121      * @var string $_dql Cached DQL query.
122      */
123     private $_dql = null;
124
125     /**
126      * @var \Doctrine\ORM\Query\ParserResult  The parser result that holds DQL => SQL information.
127      */
128     private $_parserResult;
129
130     /**
131      * @var integer The first result to return (the "offset").
132      */
133     private $_firstResult = null;
134
135     /**
136      * @var integer The maximum number of results to return (the "limit").
137      */
138     private $_maxResults = null;
139
140     /**
141      * @var CacheDriver The cache driver used for caching queries.
142      */
143     private $_queryCache;
144
145     /**
146      * @var boolean Boolean value that indicates whether or not expire the query cache.
147      */
148     private $_expireQueryCache = false;
149
150     /**
151      * @var int Query Cache lifetime.
152      */
153     private $_queryCacheTTL;
154
155     /**
156      * @var boolean Whether to use a query cache, if available. Defaults to TRUE.
157      */
158     private $_useQueryCache = true;
159
160     /**
161      * Initializes a new Query instance.
162      *
163      * @param \Doctrine\ORM\EntityManager $entityManager
164      */
165     /*public function __construct(EntityManager $entityManager)
166     {
167         parent::__construct($entityManager);
168     }*/
169
170     /**
171      * Gets the SQL query/queries that correspond to this DQL query.
172      *
173      * @return mixed The built sql query or an array of all sql queries.
174      * @override
175      */
176     public function getSQL()
177     {
178         return $this->_parse()->getSQLExecutor()->getSQLStatements();
179     }
180
181     /**
182      * Returns the corresponding AST for this DQL query.
183      *
184      * @return \Doctrine\ORM\Query\AST\SelectStatement |
185      *         \Doctrine\ORM\Query\AST\UpdateStatement |
186      *         \Doctrine\ORM\Query\AST\DeleteStatement
187      */
188     public function getAST()
189     {
190         $parser = new Parser($this);
191
192         return $parser->getAST();
193     }
194
195     /**
196      * Parses the DQL query, if necessary, and stores the parser result.
197      *
198      * Note: Populates $this->_parserResult as a side-effect.
199      *
200      * @return \Doctrine\ORM\Query\ParserResult
201      */
202     private function _parse()
203     {
204         // Return previous parser result if the query and the filter collection are both clean
205         if ($this->_state === self::STATE_CLEAN && $this->_em->isFiltersStateClean()) {
206             return $this->_parserResult;
207         }
208
209         $this->_state = self::STATE_CLEAN;
210
211         // Check query cache.
212         if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) {
213             $parser = new Parser($this);
214
215             $this->_parserResult = $parser->parse();
216
217             return $this->_parserResult;
218         }
219
220         $hash   = $this->_getQueryCacheId();
221         $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
222
223         if ($cached instanceof ParserResult) {
224             // Cache hit.
225             $this->_parserResult = $cached;
226
227             return $this->_parserResult;
228         }
229
230         // Cache miss.
231         $parser = new Parser($this);
232
233         $this->_parserResult = $parser->parse();
234
235         $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
236
237         return $this->_parserResult;
238     }
239
240     /**
241      * {@inheritdoc}
242      */
243     protected function _doExecute()
244     {
245         $executor = $this->_parse()->getSqlExecutor();
246
247         if ($this->_queryCacheProfile) {
248             $executor->setQueryCacheProfile($this->_queryCacheProfile);
249         }
250
251         // Prepare parameters
252         $paramMappings = $this->_parserResult->getParameterMappings();
253
254         if (count($paramMappings) != count($this->parameters)) {
255             throw QueryException::invalidParameterNumber();
256         }
257
258         list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
259
260         if ($this->_resultSetMapping === null) {
261             $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
262         }
263
264         return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
265     }
266
267     /**
268      * Processes query parameter mappings
269      *
270      * @param array $paramMappings
271      * @return array
272      */
273     private function processParameterMappings($paramMappings)
274     {
275         $sqlParams = array();
276         $types     = array();
277
278         foreach ($this->parameters as $parameter) {
279             $key = $parameter->getName();
280
281             if ( ! isset($paramMappings[$key])) {
282                 throw QueryException::unknownParameter($key);
283             }
284
285             $value = $this->processParameterValue($parameter->getValue());
286             $type  = ($parameter->getValue() === $value)
287                 ? $parameter->getType()
288                 : Query\ParameterTypeInferer::inferType($value);
289
290             foreach ($paramMappings[$key] as $position) {
291                 $types[$position] = $type;
292             }
293
294             $sqlPositions = $paramMappings[$key];
295
296             // optimized multi value sql positions away for now,
297             // they are not allowed in DQL anyways.
298             $value = array($value);
299             $countValue = count($value);
300
301             for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
302                 $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
303             }
304         }
305
306         if (count($sqlParams) != count($types)) {
307             throw QueryException::parameterTypeMissmatch();
308         }
309
310         if ($sqlParams) {
311             ksort($sqlParams);
312             $sqlParams = array_values($sqlParams);
313
314             ksort($types);
315             $types = array_values($types);
316         }
317
318         return array($sqlParams, $types);
319     }
320
321     /**
322      * Defines a cache driver to be used for caching queries.
323      *
324      * @param Doctrine_Cache_Interface|null $driver Cache driver
325      * @return Query This query instance.
326      */
327     public function setQueryCacheDriver($queryCache)
328     {
329         $this->_queryCache = $queryCache;
330
331         return $this;
332     }
333
334     /**
335      * Defines whether the query should make use of a query cache, if available.
336      *
337      * @param boolean $bool
338      * @return @return Query This query instance.
339      */
340     public function useQueryCache($bool)
341     {
342         $this->_useQueryCache = $bool;
343
344         return $this;
345     }
346
347     /**
348      * Returns the cache driver used for query caching.
349      *
350      * @return CacheDriver The cache driver used for query caching or NULL, if
351      *                     this Query does not use query caching.
352      */
353     public function getQueryCacheDriver()
354     {
355         if ($this->_queryCache) {
356             return $this->_queryCache;
357         }
358
359         return $this->_em->getConfiguration()->getQueryCacheImpl();
360     }
361
362     /**
363      * Defines how long the query cache will be active before expire.
364      *
365      * @param integer $timeToLive How long the cache entry is valid
366      * @return Query This query instance.
367      */
368     public function setQueryCacheLifetime($timeToLive)
369     {
370         if ($timeToLive !== null) {
371             $timeToLive = (int) $timeToLive;
372         }
373
374         $this->_queryCacheTTL = $timeToLive;
375
376         return $this;
377     }
378
379     /**
380      * Retrieves the lifetime of resultset cache.
381      *
382      * @return int
383      */
384     public function getQueryCacheLifetime()
385     {
386         return $this->_queryCacheTTL;
387     }
388
389     /**
390      * Defines if the query cache is active or not.
391      *
392      * @param boolean $expire Whether or not to force query cache expiration.
393      * @return Query This query instance.
394      */
395     public function expireQueryCache($expire = true)
396     {
397         $this->_expireQueryCache = $expire;
398
399         return $this;
400     }
401
402     /**
403      * Retrieves if the query cache is active or not.
404      *
405      * @return bool
406      */
407     public function getExpireQueryCache()
408     {
409         return $this->_expireQueryCache;
410     }
411
412     /**
413      * @override
414      */
415     public function free()
416     {
417         parent::free();
418
419         $this->_dql = null;
420         $this->_state = self::STATE_CLEAN;
421     }
422
423     /**
424      * Sets a DQL query string.
425      *
426      * @param string $dqlQuery DQL Query
427      * @return \Doctrine\ORM\AbstractQuery
428      */
429     public function setDQL($dqlQuery)
430     {
431         if ($dqlQuery !== null) {
432             $this->_dql = $dqlQuery;
433             $this->_state = self::STATE_DIRTY;
434         }
435
436         return $this;
437     }
438
439     /**
440      * Returns the DQL query that is represented by this query object.
441      *
442      * @return string DQL query
443      */
444     public function getDQL()
445     {
446         return $this->_dql;
447     }
448
449     /**
450      * Returns the state of this query object
451      * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
452      * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
453      *
454      * @see AbstractQuery::STATE_CLEAN
455      * @see AbstractQuery::STATE_DIRTY
456      *
457      * @return integer Return the query state
458      */
459     public function getState()
460     {
461         return $this->_state;
462     }
463
464     /**
465      * Method to check if an arbitrary piece of DQL exists
466      *
467      * @param string $dql Arbitrary piece of DQL to check for
468      * @return boolean
469      */
470     public function contains($dql)
471     {
472         return stripos($this->getDQL(), $dql) === false ? false : true;
473     }
474
475     /**
476      * Sets the position of the first result to retrieve (the "offset").
477      *
478      * @param integer $firstResult The first result to return.
479      * @return Query This query object.
480      */
481     public function setFirstResult($firstResult)
482     {
483         $this->_firstResult = $firstResult;
484         $this->_state       = self::STATE_DIRTY;
485
486         return $this;
487     }
488
489     /**
490      * Gets the position of the first result the query object was set to retrieve (the "offset").
491      * Returns NULL if {@link setFirstResult} was not applied to this query.
492      *
493      * @return integer The position of the first result.
494      */
495     public function getFirstResult()
496     {
497         return $this->_firstResult;
498     }
499
500     /**
501      * Sets the maximum number of results to retrieve (the "limit").
502      *
503      * @param integer $maxResults
504      * @return Query This query object.
505      */
506     public function setMaxResults($maxResults)
507     {
508         $this->_maxResults = $maxResults;
509         $this->_state      = self::STATE_DIRTY;
510
511         return $this;
512     }
513
514     /**
515      * Gets the maximum number of results the query object was set to retrieve (the "limit").
516      * Returns NULL if {@link setMaxResults} was not applied to this query.
517      *
518      * @return integer Maximum number of results.
519      */
520     public function getMaxResults()
521     {
522         return $this->_maxResults;
523     }
524
525     /**
526      * Executes the query and returns an IterableResult that can be used to incrementally
527      * iterated over the result.
528      *
529      * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters.
530      * @param integer $hydrationMode The hydration mode to use.
531      * @return \Doctrine\ORM\Internal\Hydration\IterableResult
532      */
533     public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
534     {
535         $this->setHint(self::HINT_INTERNAL_ITERATION, true);
536
537         return parent::iterate($parameters, $hydrationMode);
538     }
539
540     /**
541      * {@inheritdoc}
542      */
543     public function setHint($name, $value)
544     {
545         $this->_state = self::STATE_DIRTY;
546
547         return parent::setHint($name, $value);
548     }
549
550     /**
551      * {@inheritdoc}
552      */
553     public function setHydrationMode($hydrationMode)
554     {
555         $this->_state = self::STATE_DIRTY;
556
557         return parent::setHydrationMode($hydrationMode);
558     }
559
560     /**
561      * Set the lock mode for this Query.
562      *
563      * @see \Doctrine\DBAL\LockMode
564      * @param  int $lockMode
565      * @return Query
566      */
567     public function setLockMode($lockMode)
568     {
569         if (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) {
570             if ( ! $this->_em->getConnection()->isTransactionActive()) {
571                 throw TransactionRequiredException::transactionRequired();
572             }
573         }
574
575         $this->setHint(self::HINT_LOCK_MODE, $lockMode);
576
577         return $this;
578     }
579
580     /**
581      * Get the current lock mode for this query.
582      *
583      * @return int
584      */
585     public function getLockMode()
586     {
587         $lockMode = $this->getHint(self::HINT_LOCK_MODE);
588
589         if ( ! $lockMode) {
590             return LockMode::NONE;
591         }
592
593         return $lockMode;
594     }
595
596     /**
597      * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
598      *
599      * The query cache
600      *
601      * @return string
602      */
603     protected function _getQueryCacheId()
604     {
605         ksort($this->_hints);
606
607         return md5(
608             $this->getDql() . var_export($this->_hints, true) .
609             ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
610             '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
611             '&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
612         );
613     }
614
615     /**
616      * Cleanup Query resource when clone is called.
617      *
618      * @return void
619      */
620     public function __clone()
621     {
622         parent::__clone();
623
624         $this->_state = self::STATE_DIRTY;
625     }
626 }