AbstractQuery.php 15.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
/*
 *  $Id: Abstract.php 1393 2008-03-06 17:49:16Z guilhermeblanco $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
20 21
 */

22 23
namespace Doctrine\ORM;

24 25
use Doctrine\ORM\Query\QueryException;

26
/**
27
 * Base class for Query and NativeQuery.
28 29
 *
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30
 * @link        www.doctrine-project.com
31 32 33 34
 * @since       1.0
 * @version     $Revision: 1393 $
 * @author      Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
35
 * @author      Roman Borschel <roman@code-factory.org>
36
 */
37
abstract class AbstractQuery
38
{
39
    /* Hydration mode constants */
40
    /**
41
     * Hydrates an object graph. This is the default behavior.
42
     */
43
    const HYDRATE_OBJECT = 1;
44
    /**
45
     * Hydrates an array graph.
46
     */
47
    const HYDRATE_ARRAY = 2;
48
    /**
49
     * Hydrates a flat, rectangular result set with scalar values.
50
     */
51
    const HYDRATE_SCALAR = 3;
52
    /**
53
     * Hydrates a single scalar value.
54
     */
55
    const HYDRATE_SINGLE_SCALAR = 4;
56
    /**
57
     * Hydrates nothing.
58
     */
59
    const HYDRATE_NONE = 5;
60 61 62 63 64 65 66

    /**
     * @var array $params Parameters of this query.
     */
    protected $_params = array();

    /**
67 68 69
     * The user-specified ResultSetMapping to use.
     *
     * @var ResultSetMapping
70
     */
71
    protected $_resultSetMapping;
72 73

    /**
74
     * @var Doctrine\ORM\EntityManager The entity manager used by this query object.
75
     */
76
    protected $_em;
77 78

    /**
79 80 81
     * A set of query hints.
     *
     * @var array
82
     */
83
    protected $_hints = array();
84 85

    /**
86
     * @var integer The hydration mode.
87
     */
88
    protected $_hydrationMode = self::HYDRATE_OBJECT;
89 90

    /**
91
     * The locally set cache driver used for caching result sets of this query.
92
     *
93
     * @var CacheDriver
94
     */
95 96 97 98 99 100 101 102
    protected $_resultCacheDriver;

    /**
     * Boolean flag for whether or not to cache the result sets of this query.
     *
     * @var boolean
     */
    protected $_useResultCache;
103

104 105 106 107 108 109 110
    /**
     * The id to store the result cache entry under.
     *
     * @var string
     */
    protected $_resultCacheId;

111
    /**
112
     * @var boolean Boolean value that indicates whether or not expire the result cache.
113
     */
114
    protected $_expireResultCache = false;
115

116 117 118 119
    /**
     * @var int Result Cache lifetime.
     */
    protected $_resultCacheTTL;
120 121

    /**
122 123 124
     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
     *
     * @param Doctrine\ORM\EntityManager $entityManager
125
     */
126
    public function __construct(EntityManager $entityManager)
127
    {
128
        $this->_em = $entityManager;
129 130 131
    }

    /**
132 133 134
     * Retrieves the associated EntityManager of this Query instance.
     *
     * @return Doctrine\ORM\EntityManager
135
     */
136
    public function getEntityManager()
137
    {
138
        return $this->_em;
139 140 141
    }

    /**
142
     * Frees the resources used by the query object.
143
     */
144
    public function free()
145
    {
146
        $this->_params = array();
147 148 149
    }

    /**
150
     * Get all defined parameters
151
     *
152
     * @return array Defined parameters
153
     */
romanb's avatar
romanb committed
154
    public function getParameters($params = array())
155
    {
romanb's avatar
romanb committed
156 157 158 159
        if ($params) {
            return array_merge($this->_params, $params);
        }
        return $this->_params;
160
    }
romanb's avatar
romanb committed
161
    
162
    /**
romanb's avatar
romanb committed
163 164 165 166
     * Gets a query parameter.
     * 
     * @param mixed $key The key (index or name) of the bound parameter.
     * @return mixed The value of the bound parameter.
167
     */
romanb's avatar
romanb committed
168
    public function getParameter($key)
169
    {
romanb's avatar
romanb committed
170
        return isset($this->_params[$key]) ? $this->_params[$key] : null;
171 172 173
    }

    /**
174 175 176
     * Gets the SQL query that corresponds to this query object.
     * The returned SQL syntax depends on the connection driver that is used
     * by this query object at the time of this method call.
177
     *
178
     * @return string SQL query
179
     */
180 181
    abstract public function getSql();
    
182
    /**
183
     * Sets a query parameter.
184
     *
185 186
     * @param string|integer $key The parameter position or name.
     * @param mixed $value The parameter value.
187
     * @return Doctrine\ORM\AbstractQuery
188
     */
189
    public function setParameter($key, $value)
190
    {
191
        $this->_params[$key] = $value;
192
        return $this;
193
    }
194
    
195
    /**
196
     * Sets a collection of query parameters.
197
     *
198
     * @param array $params
199
     * @return Doctrine\ORM\AbstractQuery
200
     */
201
    public function setParameters(array $params)
202
    {
203 204
        foreach ($params as $key => $value) {
            $this->setParameter($key, $value);
205
        }
206
        return $this;
207 208 209
    }

    /**
210
     * Sets the ResultSetMapping that should be used for hydration.
211
     *
212
     * @param ResultSetMapping $rsm
213
     * @return Doctrine\ORM\AbstractQuery
214
     */
215
    public function setResultSetMapping($rsm)
216
    {
217
        $this->_resultSetMapping = $rsm;
218
        return $this;
219 220 221
    }

    /**
222
     * Defines a cache driver to be used for caching result sets.
223
     *
224
     * @param Doctrine\Common\Cache\Cache $driver Cache driver
225
     * @return Doctrine\ORM\AbstractQuery
226
     */
227
    public function setResultCacheDriver($resultCacheDriver = null)
228
    {
229 230 231 232 233 234
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
            throw DoctrineException::invalidResultCacheObject($resultCacheDriver);
        }
        $this->_resultCacheDriver = $resultCacheDriver;
        if ($resultCacheDriver) {
            $this->_useResultCache = true;
235
        }
236
        return $this;
237 238 239
    }

    /**
240
     * Returns the cache driver used for caching result sets.
241
     *
242
     * @return Doctrine\Common\Cache\Cache Cache driver
243
     */
244
    public function getResultCacheDriver()
245
    {
246 247
        if ($this->_resultCacheDriver) {
            return $this->_resultCacheDriver;
248
        } else {
249
            return $this->_em->getConfiguration()->getResultCacheImpl();
250 251 252
        }
    }

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
    /**
     * Set whether or not to cache the result sets for this query
     *
     * @param boolean $bool
     */
    public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
    {
        $this->_useResultCache = $bool;
        if ($timeToLive) {
            $this->setResultCacheLifetime($timeToLive);
        }
        if ($resultCacheId) {
            $this->_resultCacheId = $resultCacheId;
        }
    }

269
    /**
270
     * Defines how long the result cache will be active before expire.
271
     *
272
     * @param integer $timeToLive How long the cache entry is valid
273
     * @return Doctrine\ORM\AbstractQuery
274
     */
275
    public function setResultCacheLifetime($timeToLive)
276
    {
277 278
        if ($timeToLive !== null) {
            $timeToLive = (int) $timeToLive;
279 280
        }

281
        $this->_resultCacheTTL = $timeToLive;
282
        return $this;
283 284 285
    }

    /**
286
     * Retrieves the lifetime of resultset cache.
287
     *
288
     * @return int
289
     */
290
    public function getResultCacheLifetime()
291
    {
292
        return $this->_resultCacheTTL;
293 294 295
    }

    /**
296
     * Defines if the result cache is active or not.
297
     *
298
     * @param boolean $expire Whether or not to force resultset cache expiration.
299
     * @return Doctrine\ORM\AbstractQuery
300
     */
301
    public function expireResultCache($expire = true)
302
    {
303
        $this->_expireResultCache = $expire;
304
        return $this;
305 306 307
    }

    /**
308
     * Retrieves if the resultset cache is active or not.
309
     *
310
     * @return bool
311
     */
312
    public function getExpireResultCache()
313
    {
314
        return $this->_expireResultCache;
315 316 317
    }

    /**
318
     * Defines the processing mode to be used during hydration.
319
     *
320 321
     * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
     *                               One of the Query::HYDRATE_* constants.
322
     * @return Doctrine\ORM\AbstractQuery
323
     */
324
    public function setHydrationMode($hydrationMode)
325
    {
326
        $this->_hydrationMode = $hydrationMode;
327
        return $this;
328 329 330
    }

    /**
331
     * Gets the hydration mode currently used by the query.
332
     *
333
     * @return integer
334
     */
335
    public function getHydrationMode()
336
    {
337
        return $this->_hydrationMode;
338 339 340
    }

    /**
341
     * Gets the list of results for the query.
342
     *
343
     * Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT).
344
     *
345
     * @return array
346
     */
347
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
348
    {
349
        return $this->execute(array(), $hydrationMode);
350 351 352
    }

    /**
353
     * Gets the array of results for the query.
354
     *
355
     * Alias for execute(array(), HYDRATE_ARRAY).
356
     *
357
     * @return array
358
     */
359
    public function getArrayResult()
360
    {
361
        return $this->execute(array(), self::HYDRATE_ARRAY);
362 363 364
    }

    /**
365
     * Gets the scalar results for the query.
366
     *
367
     * Alias for execute(array(), HYDRATE_SCALAR).
368
     *
369
     * @return array
370
     */
371
    public function getScalarResult()
372
    {
373
        return $this->execute(array(), self::HYDRATE_SCALAR);
374 375 376
    }

    /**
377 378 379
     * Gets the single result of the query.
     * Enforces the uniqueness of the result. If the result is not unique,
     * a QueryException is thrown.
380
     *
381 382 383
     * @param integer $hydrationMode
     * @return mixed
     * @throws QueryException If the query result is not unique.
384
     */
385
    public function getSingleResult($hydrationMode = null)
386
    {
387 388 389 390 391 392 393 394 395 396
        $result = $this->execute(array(), $hydrationMode);
        if (is_array($result)) {
            if (count($result) > 1) {
                throw QueryException::nonUniqueResult();
            }
            return array_shift($result);
        } else if (is_object($result)) {
            if (count($result) > 1) {
                throw QueryException::nonUniqueResult();
            }
397
            return $result->first();
398
        }
399
        return $result;
400 401 402
    }

    /**
403
     * Gets the single scalar result of the query.
404
     *
405
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
406
     *
407
     * @return mixed
408
     * @throws QueryException If the query result is not unique.
409
     */
410
    public function getSingleScalarResult()
411
    {
412
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
413 414 415
    }

    /**
416 417
     * Sets an implementation-specific hint. If the hint name is not recognized,
     * it is silently ignored.
418
     *
419 420
     * @param string $name The name of the hint.
     * @param mixed $value The value of the hint.
421
     * @return Doctrine\ORM\AbstractQuery
422
     */
423
    public function setHint($name, $value)
424
    {
425
        $this->_hints[$name] = $value;
426
        return $this;
427 428 429
    }

    /**
430 431
     * Gets an implementation-specific hint. If the hint name is not recognized,
     * FALSE is returned.
432
     *
433
     * @param string $name The name of the hint.
434
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
435
     */
436
    public function getHint($name)
437
    {
438
        return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
439 440 441
    }

    /**
442 443
     * Executes the query and returns an IterableResult that can be used to incrementally
     * iterated over the result.
444
     *
445
     * @param array $params The query parameters.
446
     * @param integer $hydrationMode The hydration mode to use.
447
     * @return IterableResult
448
     */
449
    public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
450
    {
451
        return $this->_em->getHydrator($this->_hydrationMode)->iterate(
452
            $this->_doExecute($params, $hydrationMode), $this->_resultSetMapping
453
        );
454 455 456
    }

    /**
457
     * Executes the query.
458
     *
459 460
     * @param string $params Any additional query parameters.
     * @param integer $hydrationMode Processing mode to be used during the hydration process.
461
     * @return mixed
462
     */
463
    public function execute($params = array(), $hydrationMode = null)
464
    {
465 466
        // If there are still pending insertions in the UnitOfWork we need to flush
        // in order to guarantee a correct result.
467 468
        if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
            $this->_em->flush();
469 470
        }

471 472 473
        if ($hydrationMode !== null) {
            $this->_hydrationMode = $hydrationMode;
        }
romanb's avatar
romanb committed
474 475
    
        $params = $this->getParameters($params);
romanb's avatar
romanb committed
476 477 478 479
        
        if (isset($params[0])) {
            throw QueryException::invalidParameterPosition(0);
        }
480

481
        // Check result cache
482
        if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
483
            $id = $this->_getResultCacheId($params);
484
            $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($id);
485

486 487
            if ($cached === false) {
                // Cache miss.
488 489 490 491 492 493
                $stmt = $this->_doExecute($params);
                
                $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
                        $stmt, $this->_resultSetMapping, $this->_hints
                        );
                
494
                $cacheDriver->save($id, $result, $this->_resultCacheTTL);
495

496 497 498
                return $result;
            } else {
                // Cache hit.
499
                return $cached;
500
            }
501 502
        }

503
        $stmt = $this->_doExecute($params);
504

505
        if (is_numeric($stmt)) {
506
            return $stmt;
507 508
        }

509 510 511
        return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
                $stmt, $this->_resultSetMapping, $this->_hints
                );
512 513
    }

514 515 516 517 518
    /**
     * Set the result cache id to use to store the result set cache entry.
     * If this is not explicitely set by the developer then a hash is automatically
     * generated for you.
     *
519 520
     * @param string $id
     * @return Doctrine\ORM\AbstractQuery
521 522 523 524
     */
    public function setResultCacheId($id)
    {
        $this->_resultCacheId = $id;
525
        return $this;
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
    }

    /**
     * Get the result cache id to use to store the result set cache entry.
     * Will return the configured id if it exists otherwise a hash will be
     * automatically generated for you.
     *
     * @param array $params 
     * @return string $id
     */
    protected function _getResultCacheId(array $params)
    {
        if ($this->_resultCacheId) {
            return $this->_resultCacheId;
        } else {
            return md5($this->getDql() . var_export($params, true));
        }
    }

545
    /**
romanb's avatar
romanb committed
546 547
     * Prepares the given parameters for execution in an SQL statement.
     * 
548
     * Note to inheritors: This method must return a numerically, continuously indexed array,
romanb's avatar
romanb committed
549 550 551 552
     * starting with index 0 where the values (the parameter values) are in the order
     * in which the parameters appear in the SQL query.
     * 
     * @return array The SQL parameter array.
553
     */
romanb's avatar
romanb committed
554
    abstract protected function _prepareParams(array $params);
555 556

    /**
557
     * Executes the query and returns a reference to the resulting Statement object.
558
     *
559
     * @param array $params
560
     */
561
    abstract protected function _doExecute(array $params);
562
}