Query.php 10.7 KB
Newer Older
1 2
<?php
/*
3
 *  $Id$
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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
use Doctrine\ORM\Query\CacheHandler;
25
use Doctrine\ORM\Query\Parser;
26
use Doctrine\ORM\Query\QueryException;
27

28
/**
29
 * A Query object represents a DQL query.
30 31
 *
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
romanb's avatar
romanb committed
32
 * @link        www.doctrine-project.org
33
 * @since       1.0
34 35
 * @version     $Revision: 3938 $
 * @author      Guilherme Blanco <guilhermeblanco@hotmail.com>
36
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
37
 * @author      Roman Borschel <roman@code-factory.org>
38
 */
39
final class Query extends AbstractQuery
zYne's avatar
zYne committed
40
{
41
    /**
42
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
43
     */
44
    const STATE_CLEAN  = 1;
45

46
    /**
47 48 49
     * A query object is in state DIRTY when it has DQL parts that have not yet been
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
     * is called.
50
     */
romanb's avatar
romanb committed
51
    const STATE_DIRTY = 2;
52

zYne's avatar
zYne committed
53
    /**
54
     * @var integer $_state   The current state of this query.
zYne's avatar
zYne committed
55
     */
56
    private $_state = self::STATE_CLEAN;
zYne's avatar
zYne committed
57

58
    /**
59
     * @var string $_dql Cached DQL query.
60
     */
61
    private $_dql = null;
62

63 64 65
    /**
     * @var Doctrine\ORM\Query\ParserResult  The parser result that holds DQL => SQL information.
     */
66
    private $_parserResult;
romanb's avatar
romanb committed
67 68 69 70 71 72 73 74 75 76
    
    /**
     * @var integer The first result to return (the "offset").
     */
    private $_firstResult = null;
    
    /**
     * @var integer The maximum number of results to return (the "limit").
     */
    private $_maxResults = null;
77 78

    /**
79
     * @var CacheDriver  The cache driver used for caching queries.
80
     */
81
    private $_queryCache;
82

83
    /**
84
     * @var boolean Boolean value that indicates whether or not expire the query cache.
85
     */
86
    private $_expireQueryCache = false;
87

88
    /**
89
     * @var int Query Cache lifetime.
90
     */
91
    private $_queryCacheTTL;
92 93 94

    // End of Caching Stuff

95
    /**
96
     * Initializes a new Query instance.
97
     *
romanb's avatar
romanb committed
98
     * @param Doctrine\ORM\EntityManager $entityManager
99
     */
100
    public function __construct(EntityManager $entityManager)
101
    {
102
        parent::__construct($entityManager);
103
    }
104

105
    /**
106
     * Gets the SQL query/queries that correspond to this DQL query.
107
     *
108 109
     * @return mixed The built sql query or an array of all sql queries.
     * @override
110
     */
111
    public function getSql()
112
    {
romanb's avatar
romanb committed
113
        return $this->_parse()->getSqlExecutor()->getSqlStatements();
114
    }
115

116
    /**
117 118 119
     * Parses the DQL query, if necessary, and stores the parser result.
     * 
     * Note: Populates $this->_parserResult as a side-effect.
120
     *
121
     * @return Doctrine\ORM\Query\ParserResult
122
     */
romanb's avatar
romanb committed
123
    private function _parse()
124
    {
125 126 127 128 129 130
        if ($this->_state === self::STATE_DIRTY) {
            $parser = new Parser($this);
            $this->_parserResult = $parser->parse();
            $this->_state = self::STATE_CLEAN;
        }
        return $this->_parserResult;
131
    }
132

133
    /**
romanb's avatar
romanb committed
134
     * {@inheritdoc}
135
     *
136
     * @param array $params
romanb's avatar
romanb committed
137
     * @return Statement The resulting Statement.
138
     * @override
139
     */
140
    protected function _doExecute(array $params)
141
    {
romanb's avatar
romanb committed
142
        // Check query cache
143
        if ($queryCache = $this->getQueryCacheDriver()) {
144 145 146
            // Calculate hash for dql query.
            $hash = md5($this->getDql() . 'DOCTRINE_QUERY_CACHE_SALT');
            $cached = ($this->_expireQueryCache) ? false : $queryCache->fetch($hash);
147

148 149
            if ($cached === false) {
                // Cache miss.
romanb's avatar
romanb committed
150
                $executor = $this->_parse()->getSqlExecutor();
151
                $queryCache->save($hash, serialize($this->_parserResult), null);
152 153
            } else {
                // Cache hit.
154
                $this->_parserResult = unserialize($cached);
155 156 157
                $executor = $this->_parserResult->getSqlExecutor();
            }
        } else {
romanb's avatar
romanb committed
158
            $executor = $this->_parse()->getSqlExecutor();
159 160
        }

161 162 163 164
        $params = $this->_prepareParams($params);

        if ( ! $this->_resultSetMapping) {
            $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
165 166
        }

167
        return $executor->execute($this->_em->getConnection(), $params);
zYne's avatar
zYne committed
168
    }
romanb's avatar
romanb committed
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
    
    /**
     * {@inheritdoc}
     *
     * @override
     */
    protected function _prepareParams(array $params)
    {
        $sqlParams = array();
        
        $paramMappings = $this->_parserResult->getParameterMappings();
        foreach ($params as $key => $value) {
            if (is_object($value)) {
                $values = $this->_em->getClassMetadata(get_class($value))->getIdentifierValues($value);
                $sqlPositions = $paramMappings[$key];
                $sqlParams = array_merge($sqlParams, array_combine((array)$sqlPositions, (array)$values));
            } else if (is_bool($value)) {
                $boolValue = $this->_em->getConnection()->getDatabasePlatform()->convertBooleans($value);
                foreach ($paramMappings[$key] as $position) {
                    $sqlParams[$position] = $boolValue;
                }
            } else {
                foreach ($paramMappings[$key] as $position) {
                    $sqlParams[$position] = $value;
                }
            }
        }
        ksort($sqlParams);
        
        return array_values($sqlParams);
    }
200

201
    /**
202
     * Defines a cache driver to be used for caching queries.
203
     *
204
     * @param Doctrine_Cache_Interface|null $driver Cache driver
205
     * @return Query This query instance.
206
     */
207
    public function setQueryCacheDriver($queryCache)
208
    {
209 210
        $this->_queryCache = $queryCache;
        return $this;
211
    }
212

zYne's avatar
zYne committed
213
    /**
214
     * Returns the cache driver used for query caching.
zYne's avatar
zYne committed
215
     *
216 217
     * @return CacheDriver The cache driver used for query caching or NULL, if this
     * 					   Query does not use query caching.
zYne's avatar
zYne committed
218
     */
219
    public function getQueryCacheDriver()
zYne's avatar
zYne committed
220
    {
221
        if ($this->_queryCache) {
222 223
            return $this->_queryCache;
        } else {
224
            return $this->_em->getConfiguration()->getQueryCacheImpl();
225
        }
226
    }
227

zYne's avatar
zYne committed
228
    /**
229
     * Defines how long the query cache will be active before expire.
zYne's avatar
zYne committed
230
     *
231
     * @param integer $timeToLive How long the cache entry is valid
232
     * @return Query This query instance.
zYne's avatar
zYne committed
233
     */
234
    public function setQueryCacheLifetime($timeToLive)
zYne's avatar
zYne committed
235
    {
236 237
        if ($timeToLive !== null) {
            $timeToLive = (int) $timeToLive;
romanb's avatar
romanb committed
238
        }
239 240 241
        $this->_queryCacheTTL = $timeToLive;

        return $this;
242
    }
243 244 245 246 247 248

    /**
     * Retrieves the lifetime of resultset cache.
     *
     * @return int
     */
249
    public function getQueryCacheLifetime()
250 251
    {
        return $this->_queryCacheTTL;
252
    }
253 254 255 256 257

    /**
     * Defines if the query cache is active or not.
     *
     * @param boolean $expire Whether or not to force query cache expiration.
258
     * @return Query This query instance.
259
     */
260
    public function setExpireQueryCache($expire = true)
261
    {
262
        $this->_expireQueryCache = $expire;
263 264

        return $this;
265
    }
266 267 268 269 270 271

    /**
     * Retrieves if the query cache is active or not.
     *
     * @return bool
     */
272
    public function getExpireQueryCache()
273 274
    {
        return $this->_expireQueryCache;
275
    }
276 277 278 279 280 281 282 283 284

    /**
     * @override
     */
    public function free()
    {
        parent::free();
        $this->_dql = null;
        $this->_state = self::STATE_CLEAN;
zYne's avatar
zYne committed
285
    }
286

zYne's avatar
zYne committed
287
    /**
288
     * Sets a DQL query string.
zYne's avatar
zYne committed
289
     *
290
     * @param string $dqlQuery DQL Query
zYne's avatar
zYne committed
291
     */
292
    public function setDql($dqlQuery)
zYne's avatar
zYne committed
293
    {
294 295 296 297
        $this->free();
        if ($dqlQuery !== null) {
            $this->_dql = $dqlQuery;
            $this->_state = self::STATE_DIRTY;
298
        }
299
    }
300

301 302 303 304 305 306 307
    /**
     * Returns the DQL query that is represented by this query object.
     *
     * @return string DQL query
     */
    public function getDql()
    {
308
        return $this->_dql;
309
    }
zYne's avatar
zYne committed
310

311 312 313 314 315 316 317 318 319 320 321 322 323
    /**
     * Returns the state of this query object
     * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
     * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
     *
     * @see AbstractQuery::STATE_CLEAN
     * @see AbstractQuery::STATE_DIRTY
     *
     * @return integer Return the query state
     */
    public function getState()
    {
        return $this->_state;
zYne's avatar
zYne committed
324
    }
325

326
    /**
327
     * Method to check if an arbitrary piece of DQL exists
328
     *
329 330
     * @param string $dql Arbitrary piece of DQL to check for
     * @return boolean
331
     */
332
    public function contains($dql)
333
    {
romanb's avatar
romanb committed
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
        return stripos($this->getDql(), $dql) === false ? false : true;
    }
    
    /**
     * Sets the position of the first result to retrieve (the "offset").
     *
     * @param integer $firstResult The first result to return.
     * @return Query This query object.
     */
    public function setFirstResult($firstResult)
    {
        $this->_firstResult = $firstResult;
        return $this;
    }
    
    /**
     * Gets the position of the first result the query object was set to retrieve (the "offset").
     * Returns NULL if {@link setFirstResult} was not applied to this query.
     * 
     * @return integer The position of the first result.
     */
    public function getFirstResult()
    {
        return $this->_firstResult;
    }
    
    /**
     * Sets the maximum number of results to retrieve (the "limit").
     * 
     * @param integer $maxResults
     * @return Query This query object.
     */
    public function setMaxResults($maxResults)
    {
        $this->_maxResults = $maxResults;
        return $this;
    }
    
    /**
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
     * Returns NULL if {@link setMaxResults} was not applied to this query.
     * 
     * @return integer Maximum number of results.
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
381
    }
382
}