vendor/doctrine/lib/Doctrine/ORM/AbstractQuery.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\Types\Type,
       
    23     Doctrine\ORM\Query\QueryException;
       
    24 
       
    25 /**
       
    26  * Base contract for ORM queries. Base class for Query and NativeQuery.
       
    27  *
       
    28  * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
       
    29  * @link    www.doctrine-project.org
       
    30  * @since   2.0
       
    31  * @version $Revision$
       
    32  * @author  Benjamin Eberlei <kontakt@beberlei.de>
       
    33  * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
       
    34  * @author  Jonathan Wage <jonwage@gmail.com>
       
    35  * @author  Roman Borschel <roman@code-factory.org>
       
    36  * @author  Konsta Vesterinen <kvesteri@cc.hut.fi>
       
    37  */
       
    38 abstract class AbstractQuery
       
    39 {
       
    40     /* Hydration mode constants */
       
    41     /**
       
    42      * Hydrates an object graph. This is the default behavior.
       
    43      */
       
    44     const HYDRATE_OBJECT = 1;
       
    45     /**
       
    46      * Hydrates an array graph.
       
    47      */
       
    48     const HYDRATE_ARRAY = 2;
       
    49     /**
       
    50      * Hydrates a flat, rectangular result set with scalar values.
       
    51      */
       
    52     const HYDRATE_SCALAR = 3;
       
    53     /**
       
    54      * Hydrates a single scalar value.
       
    55      */
       
    56     const HYDRATE_SINGLE_SCALAR = 4;
       
    57 
       
    58     /**
       
    59      * Very simple object hydrator (optimized for performance).
       
    60      */
       
    61     const HYDRATE_SIMPLEOBJECT = 5;
       
    62 
       
    63     /**
       
    64      * @var array The parameter map of this query.
       
    65      */
       
    66     protected $_params = array();
       
    67 
       
    68     /**
       
    69      * @var array The parameter type map of this query.
       
    70      */
       
    71     protected $_paramTypes = array();
       
    72 
       
    73     /**
       
    74      * @var ResultSetMapping The user-specified ResultSetMapping to use.
       
    75      */
       
    76     protected $_resultSetMapping;
       
    77 
       
    78     /**
       
    79      * @var Doctrine\ORM\EntityManager The entity manager used by this query object.
       
    80      */
       
    81     protected $_em;
       
    82 
       
    83     /**
       
    84      * @var array The map of query hints.
       
    85      */
       
    86     protected $_hints = array();
       
    87 
       
    88     /**
       
    89      * @var integer The hydration mode.
       
    90      */
       
    91     protected $_hydrationMode = self::HYDRATE_OBJECT;
       
    92 
       
    93     /**
       
    94      * The locally set cache driver used for caching result sets of this query.
       
    95      *
       
    96      * @var CacheDriver
       
    97      */
       
    98     protected $_resultCacheDriver;
       
    99 
       
   100     /**
       
   101      * Boolean flag for whether or not to cache the results of this query.
       
   102      *
       
   103      * @var boolean
       
   104      */
       
   105     protected $_useResultCache;
       
   106 
       
   107     /**
       
   108      * @var string The id to store the result cache entry under.
       
   109      */
       
   110     protected $_resultCacheId;
       
   111 
       
   112     /**
       
   113      * @var boolean Boolean value that indicates whether or not expire the result cache.
       
   114      */
       
   115     protected $_expireResultCache = false;
       
   116 
       
   117     /**
       
   118      * @var int Result Cache lifetime.
       
   119      */
       
   120     protected $_resultCacheTTL;
       
   121 
       
   122     /**
       
   123      * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
       
   124      *
       
   125      * @param Doctrine\ORM\EntityManager $entityManager
       
   126      */
       
   127     public function __construct(EntityManager $em)
       
   128     {
       
   129         $this->_em = $em;
       
   130     }
       
   131 
       
   132     /**
       
   133      * Retrieves the associated EntityManager of this Query instance.
       
   134      *
       
   135      * @return Doctrine\ORM\EntityManager
       
   136      */
       
   137     public function getEntityManager()
       
   138     {
       
   139         return $this->_em;
       
   140     }
       
   141 
       
   142     /**
       
   143      * Frees the resources used by the query object.
       
   144      *
       
   145      * Resets Parameters, Parameter Types and Query Hints.
       
   146      *
       
   147      * @return void
       
   148      */
       
   149     public function free()
       
   150     {
       
   151         $this->_params = array();
       
   152         $this->_paramTypes = array();
       
   153         $this->_hints = array();
       
   154     }
       
   155 
       
   156     /**
       
   157      * Get all defined parameters.
       
   158      *
       
   159      * @return array The defined query parameters.
       
   160      */
       
   161     public function getParameters()
       
   162     {
       
   163         return $this->_params;
       
   164     }
       
   165 
       
   166     /**
       
   167      * Gets a query parameter.
       
   168      *
       
   169      * @param mixed $key The key (index or name) of the bound parameter.
       
   170      * @return mixed The value of the bound parameter.
       
   171      */
       
   172     public function getParameter($key)
       
   173     {
       
   174         return isset($this->_params[$key]) ? $this->_params[$key] : null;
       
   175     }
       
   176 
       
   177     /**
       
   178      * Gets the SQL query that corresponds to this query object.
       
   179      * The returned SQL syntax depends on the connection driver that is used
       
   180      * by this query object at the time of this method call.
       
   181      *
       
   182      * @return string SQL query
       
   183      */
       
   184     abstract public function getSQL();
       
   185 
       
   186     /**
       
   187      * Sets a query parameter.
       
   188      *
       
   189      * @param string|integer $key The parameter position or name.
       
   190      * @param mixed $value The parameter value.
       
   191      * @param string $type The parameter type. If specified, the given value will be run through
       
   192      *                     the type conversion of this type. This is usually not needed for
       
   193      *                     strings and numeric types.
       
   194      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   195      */
       
   196     public function setParameter($key, $value, $type = null)
       
   197     {
       
   198         if ($type === null) {
       
   199             $type = Query\ParameterTypeInferer::inferType($value);
       
   200         }
       
   201         
       
   202         $this->_paramTypes[$key] = $type;
       
   203         $this->_params[$key] = $value;
       
   204         
       
   205         return $this;
       
   206     }
       
   207 
       
   208     /**
       
   209      * Sets a collection of query parameters.
       
   210      *
       
   211      * @param array $params
       
   212      * @param array $types
       
   213      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   214      */
       
   215     public function setParameters(array $params, array $types = array())
       
   216     {
       
   217         foreach ($params as $key => $value) {
       
   218             if (isset($types[$key])) {
       
   219                 $this->setParameter($key, $value, $types[$key]);
       
   220             } else {
       
   221                 $this->setParameter($key, $value);
       
   222             }
       
   223         }
       
   224         return $this;
       
   225     }
       
   226 
       
   227     /**
       
   228      * Sets the ResultSetMapping that should be used for hydration.
       
   229      *
       
   230      * @param ResultSetMapping $rsm
       
   231      * @return Doctrine\ORM\AbstractQuery
       
   232      */
       
   233     public function setResultSetMapping(Query\ResultSetMapping $rsm)
       
   234     {
       
   235         $this->_resultSetMapping = $rsm;
       
   236         return $this;
       
   237     }
       
   238 
       
   239     /**
       
   240      * Defines a cache driver to be used for caching result sets.
       
   241      *
       
   242      * @param Doctrine\Common\Cache\Cache $driver Cache driver
       
   243      * @return Doctrine\ORM\AbstractQuery
       
   244      */
       
   245     public function setResultCacheDriver($resultCacheDriver = null)
       
   246     {
       
   247         if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
       
   248             throw ORMException::invalidResultCacheDriver();
       
   249         }
       
   250         $this->_resultCacheDriver = $resultCacheDriver;
       
   251         if ($resultCacheDriver) {
       
   252             $this->_useResultCache = true;
       
   253         }
       
   254         return $this;
       
   255     }
       
   256 
       
   257     /**
       
   258      * Returns the cache driver used for caching result sets.
       
   259      *
       
   260      * @return Doctrine\Common\Cache\Cache Cache driver
       
   261      */
       
   262     public function getResultCacheDriver()
       
   263     {
       
   264         if ($this->_resultCacheDriver) {
       
   265             return $this->_resultCacheDriver;
       
   266         } else {
       
   267             return $this->_em->getConfiguration()->getResultCacheImpl();
       
   268         }
       
   269     }
       
   270 
       
   271     /**
       
   272      * Set whether or not to cache the results of this query and if so, for
       
   273      * how long and which ID to use for the cache entry.
       
   274      *
       
   275      * @param boolean $bool
       
   276      * @param integer $timeToLive
       
   277      * @param string $resultCacheId
       
   278      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   279      */
       
   280     public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
       
   281     {
       
   282         $this->_useResultCache = $bool;
       
   283         if ($timeToLive) {
       
   284             $this->setResultCacheLifetime($timeToLive);
       
   285         }
       
   286         if ($resultCacheId) {
       
   287             $this->_resultCacheId = $resultCacheId;
       
   288         }
       
   289         return $this;
       
   290     }
       
   291 
       
   292     /**
       
   293      * Defines how long the result cache will be active before expire.
       
   294      *
       
   295      * @param integer $timeToLive How long the cache entry is valid.
       
   296      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   297      */
       
   298     public function setResultCacheLifetime($timeToLive)
       
   299     {
       
   300         if ($timeToLive !== null) {
       
   301             $timeToLive = (int) $timeToLive;
       
   302         }
       
   303 
       
   304         $this->_resultCacheTTL = $timeToLive;
       
   305         return $this;
       
   306     }
       
   307 
       
   308     /**
       
   309      * Retrieves the lifetime of resultset cache.
       
   310      *
       
   311      * @return integer
       
   312      */
       
   313     public function getResultCacheLifetime()
       
   314     {
       
   315         return $this->_resultCacheTTL;
       
   316     }
       
   317 
       
   318     /**
       
   319      * Defines if the result cache is active or not.
       
   320      *
       
   321      * @param boolean $expire Whether or not to force resultset cache expiration.
       
   322      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   323      */
       
   324     public function expireResultCache($expire = true)
       
   325     {
       
   326         $this->_expireResultCache = $expire;
       
   327         return $this;
       
   328     }
       
   329 
       
   330     /**
       
   331      * Retrieves if the resultset cache is active or not.
       
   332      *
       
   333      * @return boolean
       
   334      */
       
   335     public function getExpireResultCache()
       
   336     {
       
   337         return $this->_expireResultCache;
       
   338     }
       
   339 
       
   340     /**
       
   341      * Change the default fetch mode of an association for this query.
       
   342      *
       
   343      * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
       
   344      *
       
   345      * @param  string $class
       
   346      * @param  string $assocName
       
   347      * @param  int $fetchMode
       
   348      * @return AbstractQuery
       
   349      */
       
   350     public function setFetchMode($class, $assocName, $fetchMode)
       
   351     {
       
   352         if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
       
   353             $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
       
   354         }
       
   355 
       
   356         $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
       
   357         return $this;
       
   358     }
       
   359 
       
   360     /**
       
   361      * Defines the processing mode to be used during hydration / result set transformation.
       
   362      *
       
   363      * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
       
   364      *                               One of the Query::HYDRATE_* constants.
       
   365      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   366      */
       
   367     public function setHydrationMode($hydrationMode)
       
   368     {
       
   369         $this->_hydrationMode = $hydrationMode;
       
   370         return $this;
       
   371     }
       
   372 
       
   373     /**
       
   374      * Gets the hydration mode currently used by the query.
       
   375      *
       
   376      * @return integer
       
   377      */
       
   378     public function getHydrationMode()
       
   379     {
       
   380         return $this->_hydrationMode;
       
   381     }
       
   382 
       
   383     /**
       
   384      * Gets the list of results for the query.
       
   385      *
       
   386      * Alias for execute(array(), $hydrationMode = HYDRATE_OBJECT).
       
   387      *
       
   388      * @return array
       
   389      */
       
   390     public function getResult($hydrationMode = self::HYDRATE_OBJECT)
       
   391     {
       
   392         return $this->execute(array(), $hydrationMode);
       
   393     }
       
   394 
       
   395     /**
       
   396      * Gets the array of results for the query.
       
   397      *
       
   398      * Alias for execute(array(), HYDRATE_ARRAY).
       
   399      *
       
   400      * @return array
       
   401      */
       
   402     public function getArrayResult()
       
   403     {
       
   404         return $this->execute(array(), self::HYDRATE_ARRAY);
       
   405     }
       
   406 
       
   407     /**
       
   408      * Gets the scalar results for the query.
       
   409      *
       
   410      * Alias for execute(array(), HYDRATE_SCALAR).
       
   411      *
       
   412      * @return array
       
   413      */
       
   414     public function getScalarResult()
       
   415     {
       
   416         return $this->execute(array(), self::HYDRATE_SCALAR);
       
   417     }
       
   418 
       
   419     /**
       
   420      * Get exactly one result or null.
       
   421      *
       
   422      * @throws NonUniqueResultException
       
   423      * @param int $hydrationMode
       
   424      * @return mixed
       
   425      */
       
   426     public function getOneOrNullResult($hydrationMode = null)
       
   427     {
       
   428         $result = $this->execute(array(), $hydrationMode);
       
   429 
       
   430         if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
       
   431             return null;
       
   432         }
       
   433 
       
   434         if (is_array($result)) {
       
   435             if (count($result) > 1) {
       
   436                 throw new NonUniqueResultException;
       
   437             }
       
   438             return array_shift($result);
       
   439         }
       
   440 
       
   441         return $result;
       
   442     }
       
   443 
       
   444     /**
       
   445      * Gets the single result of the query.
       
   446      *
       
   447      * Enforces the presence as well as the uniqueness of the result.
       
   448      *
       
   449      * If the result is not unique, a NonUniqueResultException is thrown.
       
   450      * If there is no result, a NoResultException is thrown.
       
   451      *
       
   452      * @param integer $hydrationMode
       
   453      * @return mixed
       
   454      * @throws NonUniqueResultException If the query result is not unique.
       
   455      * @throws NoResultException If the query returned no result.
       
   456      */
       
   457     public function getSingleResult($hydrationMode = null)
       
   458     {
       
   459         $result = $this->execute(array(), $hydrationMode);
       
   460 
       
   461         if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
       
   462             throw new NoResultException;
       
   463         }
       
   464 
       
   465         if (is_array($result)) {
       
   466             if (count($result) > 1) {
       
   467                 throw new NonUniqueResultException;
       
   468             }
       
   469             return array_shift($result);
       
   470         }
       
   471 
       
   472         return $result;
       
   473     }
       
   474 
       
   475     /**
       
   476      * Gets the single scalar result of the query.
       
   477      *
       
   478      * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
       
   479      *
       
   480      * @return mixed
       
   481      * @throws QueryException If the query result is not unique.
       
   482      */
       
   483     public function getSingleScalarResult()
       
   484     {
       
   485         return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
       
   486     }
       
   487 
       
   488     /**
       
   489      * Sets a query hint. If the hint name is not recognized, it is silently ignored.
       
   490      *
       
   491      * @param string $name The name of the hint.
       
   492      * @param mixed $value The value of the hint.
       
   493      * @return Doctrine\ORM\AbstractQuery
       
   494      */
       
   495     public function setHint($name, $value)
       
   496     {
       
   497         $this->_hints[$name] = $value;
       
   498         return $this;
       
   499     }
       
   500 
       
   501     /**
       
   502      * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
       
   503      *
       
   504      * @param string $name The name of the hint.
       
   505      * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
       
   506      */
       
   507     public function getHint($name)
       
   508     {
       
   509         return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
       
   510     }
       
   511 
       
   512     /**
       
   513      * Return the key value map of query hints that are currently set.
       
   514      * 
       
   515      * @return array
       
   516      */
       
   517     public function getHints()
       
   518     {
       
   519         return $this->_hints;
       
   520     }
       
   521 
       
   522     /**
       
   523      * Executes the query and returns an IterableResult that can be used to incrementally
       
   524      * iterate over the result.
       
   525      *
       
   526      * @param array $params The query parameters.
       
   527      * @param integer $hydrationMode The hydration mode to use.
       
   528      * @return IterableResult
       
   529      */
       
   530     public function iterate(array $params = array(), $hydrationMode = null)
       
   531     {
       
   532         if ($hydrationMode !== null) {
       
   533             $this->setHydrationMode($hydrationMode);
       
   534         }
       
   535 
       
   536         if ($params) {
       
   537             $this->setParameters($params);
       
   538         }
       
   539 
       
   540         $stmt = $this->_doExecute();
       
   541 
       
   542         return $this->_em->newHydrator($this->_hydrationMode)->iterate(
       
   543             $stmt, $this->_resultSetMapping, $this->_hints
       
   544         );
       
   545     }
       
   546 
       
   547     /**
       
   548      * Executes the query.
       
   549      *
       
   550      * @param array $params Any additional query parameters.
       
   551      * @param integer $hydrationMode Processing mode to be used during the hydration process.
       
   552      * @return mixed
       
   553      */
       
   554     public function execute($params = array(), $hydrationMode = null)
       
   555     {
       
   556         if ($hydrationMode !== null) {
       
   557             $this->setHydrationMode($hydrationMode);
       
   558         }
       
   559 
       
   560         if ($params) {
       
   561             $this->setParameters($params);
       
   562         }
       
   563 
       
   564         // Check result cache
       
   565         if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
       
   566             list($key, $hash) = $this->getResultCacheId();
       
   567             $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash);
       
   568 
       
   569             if ($cached === false || !isset($cached[$key])) {
       
   570                 // Cache miss.
       
   571                 $stmt = $this->_doExecute();
       
   572 
       
   573                 $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
       
   574                     $stmt, $this->_resultSetMapping, $this->_hints
       
   575                 );
       
   576 
       
   577                 $cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL);
       
   578 
       
   579                 return $result;
       
   580             } else {
       
   581                 // Cache hit.
       
   582                 return $cached[$key];
       
   583             }
       
   584         }
       
   585 
       
   586         $stmt = $this->_doExecute();
       
   587 
       
   588         if (is_numeric($stmt)) {
       
   589             return $stmt;
       
   590         }
       
   591 
       
   592         return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
       
   593                 $stmt, $this->_resultSetMapping, $this->_hints
       
   594                 );
       
   595     }
       
   596 
       
   597     /**
       
   598      * Set the result cache id to use to store the result set cache entry.
       
   599      * If this is not explicitely set by the developer then a hash is automatically
       
   600      * generated for you.
       
   601      *
       
   602      * @param string $id
       
   603      * @return Doctrine\ORM\AbstractQuery This query instance.
       
   604      */
       
   605     public function setResultCacheId($id)
       
   606     {
       
   607         $this->_resultCacheId = $id;
       
   608         return $this;
       
   609     }
       
   610 
       
   611     /**
       
   612      * Get the result cache id to use to store the result set cache entry.
       
   613      * Will return the configured id if it exists otherwise a hash will be
       
   614      * automatically generated for you.
       
   615      *
       
   616      * @return array ($key, $hash)
       
   617      */
       
   618     protected function getResultCacheId()
       
   619     {
       
   620         if ($this->_resultCacheId) {
       
   621             return array($this->_resultCacheId, $this->_resultCacheId);
       
   622         } else {
       
   623             $params = $this->_params;
       
   624             foreach ($params AS $key => $value) {
       
   625                 if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
       
   626                     if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
       
   627                         $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
       
   628                     } else {
       
   629                         $class = $this->_em->getClassMetadata(get_class($value));
       
   630                         $idValues = $class->getIdentifierValues($value);
       
   631                     }
       
   632                     $params[$key] = $idValues;
       
   633                 } else {
       
   634                     $params[$key] = $value;
       
   635                 }
       
   636             }
       
   637 
       
   638             $sql = $this->getSql();
       
   639             ksort($this->_hints);
       
   640             $key = implode(";", (array)$sql) . var_export($params, true) .
       
   641                 var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode;
       
   642             return array($key, md5($key));
       
   643         }
       
   644     }
       
   645 
       
   646     /**
       
   647      * Executes the query and returns a the resulting Statement object.
       
   648      *
       
   649      * @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
       
   650      */
       
   651     abstract protected function _doExecute();
       
   652 
       
   653     /**
       
   654      * Cleanup Query resource when clone is called.
       
   655      *
       
   656      * @return void
       
   657      */
       
   658     public function __clone()
       
   659     {
       
   660         $this->_params = array();
       
   661         $this->_paramTypes = array();
       
   662         $this->_hints = array();
       
   663     }
       
   664 }