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