AbstractQuery.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  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. namespace Doctrine\ORM;
  20. use Countable;
  21. use Doctrine\Common\Collections\ArrayCollection;
  22. use Doctrine\Common\Collections\Collection;
  23. use Doctrine\DBAL\Cache\QueryCacheProfile;
  24. use Doctrine\DBAL\Driver\Statement;
  25. use Doctrine\ORM\Cache\Logging\CacheLogger;
  26. use Doctrine\ORM\Cache\QueryCacheKey;
  27. use Doctrine\ORM\Cache\TimestampCacheKey;
  28. use Doctrine\ORM\Internal\Hydration\IterableResult;
  29. use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
  30. use Doctrine\ORM\Query\Parameter;
  31. use Doctrine\ORM\Query\QueryException;
  32. use Doctrine\ORM\Query\ResultSetMapping;
  33. use Doctrine\Persistence\Mapping\MappingException;
  34. use Traversable;
  35. use function array_map;
  36. use function array_shift;
  37. use function count;
  38. use function is_array;
  39. use function is_numeric;
  40. use function is_object;
  41. use function is_scalar;
  42. use function iterator_count;
  43. use function iterator_to_array;
  44. use function ksort;
  45. use function reset;
  46. use function serialize;
  47. use function sha1;
  48. use function trigger_error;
  49. use const E_USER_DEPRECATED;
  50. /**
  51. * Base contract for ORM queries. Base class for Query and NativeQuery.
  52. *
  53. * @link www.doctrine-project.org
  54. */
  55. abstract class AbstractQuery
  56. {
  57. /* Hydration mode constants */
  58. /**
  59. * Hydrates an object graph. This is the default behavior.
  60. */
  61. public const HYDRATE_OBJECT = 1;
  62. /**
  63. * Hydrates an array graph.
  64. */
  65. public const HYDRATE_ARRAY = 2;
  66. /**
  67. * Hydrates a flat, rectangular result set with scalar values.
  68. */
  69. public const HYDRATE_SCALAR = 3;
  70. /**
  71. * Hydrates a single scalar value.
  72. */
  73. public const HYDRATE_SINGLE_SCALAR = 4;
  74. /**
  75. * Very simple object hydrator (optimized for performance).
  76. */
  77. public const HYDRATE_SIMPLEOBJECT = 5;
  78. /**
  79. * The parameter map of this query.
  80. *
  81. * @var ArrayCollection|Parameter[]
  82. * @psalm-var ArrayCollection<int, Parameter>
  83. */
  84. protected $parameters;
  85. /**
  86. * The user-specified ResultSetMapping to use.
  87. *
  88. * @var ResultSetMapping
  89. */
  90. protected $_resultSetMapping;
  91. /**
  92. * The entity manager used by this query object.
  93. *
  94. * @var EntityManagerInterface
  95. */
  96. protected $_em;
  97. /**
  98. * The map of query hints.
  99. *
  100. * @psalm-var array<string, mixed>
  101. */
  102. protected $_hints = [];
  103. /**
  104. * The hydration mode.
  105. *
  106. * @var string|int
  107. */
  108. protected $_hydrationMode = self::HYDRATE_OBJECT;
  109. /** @var QueryCacheProfile */
  110. protected $_queryCacheProfile;
  111. /**
  112. * Whether or not expire the result cache.
  113. *
  114. * @var bool
  115. */
  116. protected $_expireResultCache = false;
  117. /** @var QueryCacheProfile */
  118. protected $_hydrationCacheProfile;
  119. /**
  120. * Whether to use second level cache, if available.
  121. *
  122. * @var bool
  123. */
  124. protected $cacheable = false;
  125. /** @var bool */
  126. protected $hasCache = false;
  127. /**
  128. * Second level cache region name.
  129. *
  130. * @var string|null
  131. */
  132. protected $cacheRegion;
  133. /**
  134. * Second level query cache mode.
  135. *
  136. * @var int|null
  137. */
  138. protected $cacheMode;
  139. /** @var CacheLogger|null */
  140. protected $cacheLogger;
  141. /** @var int */
  142. protected $lifetime = 0;
  143. /**
  144. * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
  145. */
  146. public function __construct(EntityManagerInterface $em)
  147. {
  148. $this->_em = $em;
  149. $this->parameters = new ArrayCollection();
  150. $this->_hints = $em->getConfiguration()->getDefaultQueryHints();
  151. $this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
  152. if ($this->hasCache) {
  153. $this->cacheLogger = $em->getConfiguration()
  154. ->getSecondLevelCacheConfiguration()
  155. ->getCacheLogger();
  156. }
  157. }
  158. /**
  159. * Enable/disable second level query (result) caching for this query.
  160. *
  161. * @param bool $cacheable
  162. *
  163. * @return static This query instance.
  164. */
  165. public function setCacheable($cacheable)
  166. {
  167. $this->cacheable = (bool) $cacheable;
  168. return $this;
  169. }
  170. /**
  171. * @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
  172. */
  173. public function isCacheable()
  174. {
  175. return $this->cacheable;
  176. }
  177. /**
  178. * @param string $cacheRegion
  179. *
  180. * @return static This query instance.
  181. */
  182. public function setCacheRegion($cacheRegion)
  183. {
  184. $this->cacheRegion = (string) $cacheRegion;
  185. return $this;
  186. }
  187. /**
  188. * Obtain the name of the second level query cache region in which query results will be stored
  189. *
  190. * @return string|null The cache region name; NULL indicates the default region.
  191. */
  192. public function getCacheRegion()
  193. {
  194. return $this->cacheRegion;
  195. }
  196. /**
  197. * @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
  198. */
  199. protected function isCacheEnabled()
  200. {
  201. return $this->cacheable && $this->hasCache;
  202. }
  203. /**
  204. * @return int
  205. */
  206. public function getLifetime()
  207. {
  208. return $this->lifetime;
  209. }
  210. /**
  211. * Sets the life-time for this query into second level cache.
  212. *
  213. * @param int $lifetime
  214. *
  215. * @return static This query instance.
  216. */
  217. public function setLifetime($lifetime)
  218. {
  219. $this->lifetime = (int) $lifetime;
  220. return $this;
  221. }
  222. /**
  223. * @return int
  224. */
  225. public function getCacheMode()
  226. {
  227. return $this->cacheMode;
  228. }
  229. /**
  230. * @param int $cacheMode
  231. *
  232. * @return static This query instance.
  233. */
  234. public function setCacheMode($cacheMode)
  235. {
  236. $this->cacheMode = (int) $cacheMode;
  237. return $this;
  238. }
  239. /**
  240. * Gets the SQL query that corresponds to this query object.
  241. * The returned SQL syntax depends on the connection driver that is used
  242. * by this query object at the time of this method call.
  243. *
  244. * @return string SQL query
  245. */
  246. abstract public function getSQL();
  247. /**
  248. * Retrieves the associated EntityManager of this Query instance.
  249. *
  250. * @return EntityManager
  251. */
  252. public function getEntityManager()
  253. {
  254. return $this->_em;
  255. }
  256. /**
  257. * Frees the resources used by the query object.
  258. *
  259. * Resets Parameters, Parameter Types and Query Hints.
  260. *
  261. * @return void
  262. */
  263. public function free()
  264. {
  265. $this->parameters = new ArrayCollection();
  266. $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
  267. }
  268. /**
  269. * Get all defined parameters.
  270. *
  271. * @return ArrayCollection The defined query parameters.
  272. */
  273. public function getParameters()
  274. {
  275. return $this->parameters;
  276. }
  277. /**
  278. * Gets a query parameter.
  279. *
  280. * @param mixed $key The key (index or name) of the bound parameter.
  281. *
  282. * @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
  283. */
  284. public function getParameter($key)
  285. {
  286. $key = Query\Parameter::normalizeName($key);
  287. $filteredParameters = $this->parameters->filter(
  288. static function (Query\Parameter $parameter) use ($key): bool {
  289. $parameterName = $parameter->getName();
  290. return $key === $parameterName;
  291. }
  292. );
  293. return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
  294. }
  295. /**
  296. * Sets a collection of query parameters.
  297. *
  298. * @param ArrayCollection|mixed[] $parameters
  299. *
  300. * @return static This query instance.
  301. *
  302. * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
  303. */
  304. public function setParameters($parameters)
  305. {
  306. // BC compatibility with 2.3-
  307. if (is_array($parameters)) {
  308. /** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */
  309. $parameterCollection = new ArrayCollection();
  310. foreach ($parameters as $key => $value) {
  311. $parameterCollection->add(new Parameter($key, $value));
  312. }
  313. $parameters = $parameterCollection;
  314. }
  315. $this->parameters = $parameters;
  316. return $this;
  317. }
  318. /**
  319. * Sets a query parameter.
  320. *
  321. * @param string|int $key The parameter position or name.
  322. * @param mixed $value The parameter value.
  323. * @param string|null $type The parameter type. If specified, the given value will be run through
  324. * the type conversion of this type. This is usually not needed for
  325. * strings and numeric types.
  326. *
  327. * @return static This query instance.
  328. */
  329. public function setParameter($key, $value, $type = null)
  330. {
  331. $existingParameter = $this->getParameter($key);
  332. if ($existingParameter !== null) {
  333. $existingParameter->setValue($value, $type);
  334. return $this;
  335. }
  336. $this->parameters->add(new Parameter($key, $value, $type));
  337. return $this;
  338. }
  339. /**
  340. * Processes an individual parameter value.
  341. *
  342. * @param mixed $value
  343. *
  344. * @return mixed[]|string|int|float|bool
  345. *
  346. * @throws ORMInvalidArgumentException
  347. *
  348. * @psalm-return array|scalar
  349. */
  350. public function processParameterValue($value)
  351. {
  352. if (is_scalar($value)) {
  353. return $value;
  354. }
  355. if ($value instanceof Collection) {
  356. $value = iterator_to_array($value);
  357. }
  358. if (is_array($value)) {
  359. $value = $this->processArrayParameterValue($value);
  360. return $value;
  361. }
  362. if ($value instanceof Mapping\ClassMetadata) {
  363. return $value->name;
  364. }
  365. if (! is_object($value)) {
  366. return $value;
  367. }
  368. try {
  369. $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
  370. if ($value === null) {
  371. throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
  372. }
  373. } catch (MappingException | ORMMappingException $e) {
  374. /* Silence any mapping exceptions. These can occur if the object in
  375. question is not a mapped entity, in which case we just don't do
  376. any preparation on the value.
  377. Depending on MappingDriver, either MappingException or
  378. ORMMappingException is thrown. */
  379. $value = $this->potentiallyProcessIterable($value);
  380. }
  381. return $value;
  382. }
  383. /**
  384. * If no mapping is detected, trying to resolve the value as a Traversable
  385. *
  386. * @param mixed $value
  387. *
  388. * @return mixed
  389. */
  390. private function potentiallyProcessIterable($value)
  391. {
  392. if ($value instanceof Traversable) {
  393. $value = iterator_to_array($value);
  394. $value = $this->processArrayParameterValue($value);
  395. }
  396. return $value;
  397. }
  398. /**
  399. * Process a parameter value which was previously identified as an array
  400. *
  401. * @param mixed[] $value
  402. *
  403. * @return mixed[]
  404. */
  405. private function processArrayParameterValue(array $value): array
  406. {
  407. foreach ($value as $key => $paramValue) {
  408. $paramValue = $this->processParameterValue($paramValue);
  409. $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
  410. }
  411. return $value;
  412. }
  413. /**
  414. * Sets the ResultSetMapping that should be used for hydration.
  415. *
  416. * @return static This query instance.
  417. */
  418. public function setResultSetMapping(Query\ResultSetMapping $rsm)
  419. {
  420. $this->translateNamespaces($rsm);
  421. $this->_resultSetMapping = $rsm;
  422. return $this;
  423. }
  424. /**
  425. * Gets the ResultSetMapping used for hydration.
  426. *
  427. * @return ResultSetMapping
  428. */
  429. protected function getResultSetMapping()
  430. {
  431. return $this->_resultSetMapping;
  432. }
  433. /**
  434. * Allows to translate entity namespaces to full qualified names.
  435. *
  436. * @return void
  437. */
  438. private function translateNamespaces(Query\ResultSetMapping $rsm)
  439. {
  440. $translate = function ($alias): string {
  441. return $this->_em->getClassMetadata($alias)->getName();
  442. };
  443. $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
  444. $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
  445. }
  446. /**
  447. * Set a cache profile for hydration caching.
  448. *
  449. * If no result cache driver is set in the QueryCacheProfile, the default
  450. * result cache driver is used from the configuration.
  451. *
  452. * Important: Hydration caching does NOT register entities in the
  453. * UnitOfWork when retrieved from the cache. Never use result cached
  454. * entities for requests that also flush the EntityManager. If you want
  455. * some form of caching with UnitOfWork registration you should use
  456. * {@see AbstractQuery::setResultCacheProfile()}.
  457. *
  458. * @return static This query instance.
  459. *
  460. * @example
  461. * $lifetime = 100;
  462. * $resultKey = "abc";
  463. * $query->setHydrationCacheProfile(new QueryCacheProfile());
  464. * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
  465. */
  466. public function setHydrationCacheProfile(?QueryCacheProfile $profile = null)
  467. {
  468. if ($profile !== null && ! $profile->getResultCacheDriver()) {
  469. $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
  470. $profile = $profile->setResultCacheDriver($resultCacheDriver);
  471. }
  472. $this->_hydrationCacheProfile = $profile;
  473. return $this;
  474. }
  475. /**
  476. * @return QueryCacheProfile
  477. */
  478. public function getHydrationCacheProfile()
  479. {
  480. return $this->_hydrationCacheProfile;
  481. }
  482. /**
  483. * Set a cache profile for the result cache.
  484. *
  485. * If no result cache driver is set in the QueryCacheProfile, the default
  486. * result cache driver is used from the configuration.
  487. *
  488. * @return static This query instance.
  489. */
  490. public function setResultCacheProfile(?QueryCacheProfile $profile = null)
  491. {
  492. if ($profile !== null && ! $profile->getResultCacheDriver()) {
  493. $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
  494. $profile = $profile->setResultCacheDriver($resultCacheDriver);
  495. }
  496. $this->_queryCacheProfile = $profile;
  497. return $this;
  498. }
  499. /**
  500. * Defines a cache driver to be used for caching result sets and implicitly enables caching.
  501. *
  502. * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
  503. *
  504. * @return static This query instance.
  505. *
  506. * @throws ORMException
  507. */
  508. public function setResultCacheDriver($resultCacheDriver = null)
  509. {
  510. if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
  511. throw ORMException::invalidResultCacheDriver();
  512. }
  513. $this->_queryCacheProfile = $this->_queryCacheProfile
  514. ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
  515. : new QueryCacheProfile(0, null, $resultCacheDriver);
  516. return $this;
  517. }
  518. /**
  519. * Returns the cache driver used for caching result sets.
  520. *
  521. * @deprecated
  522. *
  523. * @return \Doctrine\Common\Cache\Cache Cache driver
  524. */
  525. public function getResultCacheDriver()
  526. {
  527. if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
  528. return $this->_queryCacheProfile->getResultCacheDriver();
  529. }
  530. return $this->_em->getConfiguration()->getResultCacheImpl();
  531. }
  532. /**
  533. * Set whether or not to cache the results of this query and if so, for
  534. * how long and which ID to use for the cache entry.
  535. *
  536. * @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
  537. *
  538. * @param bool $useCache
  539. * @param int $lifetime
  540. * @param string $resultCacheId
  541. *
  542. * @return static This query instance.
  543. */
  544. public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
  545. {
  546. return $useCache
  547. ? $this->enableResultCache($lifetime, $resultCacheId)
  548. : $this->disableResultCache();
  549. }
  550. /**
  551. * Enables caching of the results of this query, for given or default amount of seconds
  552. * and optionally specifies which ID to use for the cache entry.
  553. *
  554. * @param int|null $lifetime How long the cache entry is valid, in seconds.
  555. * @param string|null $resultCacheId ID to use for the cache entry.
  556. *
  557. * @return static This query instance.
  558. */
  559. public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null): self
  560. {
  561. $this->setResultCacheLifetime($lifetime);
  562. $this->setResultCacheId($resultCacheId);
  563. return $this;
  564. }
  565. /**
  566. * Disables caching of the results of this query.
  567. *
  568. * @return static This query instance.
  569. */
  570. public function disableResultCache(): self
  571. {
  572. $this->_queryCacheProfile = null;
  573. return $this;
  574. }
  575. /**
  576. * Defines how long the result cache will be active before expire.
  577. *
  578. * @param int|null $lifetime How long the cache entry is valid.
  579. *
  580. * @return static This query instance.
  581. */
  582. public function setResultCacheLifetime($lifetime)
  583. {
  584. $lifetime = $lifetime !== null ? (int) $lifetime : 0;
  585. $this->_queryCacheProfile = $this->_queryCacheProfile
  586. ? $this->_queryCacheProfile->setLifetime($lifetime)
  587. : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
  588. return $this;
  589. }
  590. /**
  591. * Retrieves the lifetime of resultset cache.
  592. *
  593. * @deprecated
  594. *
  595. * @return int
  596. */
  597. public function getResultCacheLifetime()
  598. {
  599. return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
  600. }
  601. /**
  602. * Defines if the result cache is active or not.
  603. *
  604. * @param bool $expire Whether or not to force resultset cache expiration.
  605. *
  606. * @return static This query instance.
  607. */
  608. public function expireResultCache($expire = true)
  609. {
  610. $this->_expireResultCache = $expire;
  611. return $this;
  612. }
  613. /**
  614. * Retrieves if the resultset cache is active or not.
  615. *
  616. * @return bool
  617. */
  618. public function getExpireResultCache()
  619. {
  620. return $this->_expireResultCache;
  621. }
  622. /**
  623. * @return QueryCacheProfile
  624. */
  625. public function getQueryCacheProfile()
  626. {
  627. return $this->_queryCacheProfile;
  628. }
  629. /**
  630. * Change the default fetch mode of an association for this query.
  631. *
  632. * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
  633. *
  634. * @param string $class
  635. * @param string $assocName
  636. * @param int $fetchMode
  637. *
  638. * @return static This query instance.
  639. */
  640. public function setFetchMode($class, $assocName, $fetchMode)
  641. {
  642. if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
  643. $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
  644. }
  645. $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
  646. return $this;
  647. }
  648. /**
  649. * Defines the processing mode to be used during hydration / result set transformation.
  650. *
  651. * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
  652. * One of the Query::HYDRATE_* constants.
  653. *
  654. * @return static This query instance.
  655. */
  656. public function setHydrationMode($hydrationMode)
  657. {
  658. $this->_hydrationMode = $hydrationMode;
  659. return $this;
  660. }
  661. /**
  662. * Gets the hydration mode currently used by the query.
  663. *
  664. * @return string|int
  665. */
  666. public function getHydrationMode()
  667. {
  668. return $this->_hydrationMode;
  669. }
  670. /**
  671. * Gets the list of results for the query.
  672. *
  673. * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
  674. *
  675. * @param string|int $hydrationMode
  676. *
  677. * @return mixed
  678. */
  679. public function getResult($hydrationMode = self::HYDRATE_OBJECT)
  680. {
  681. return $this->execute(null, $hydrationMode);
  682. }
  683. /**
  684. * Gets the array of results for the query.
  685. *
  686. * Alias for execute(null, HYDRATE_ARRAY).
  687. *
  688. * @return mixed[]
  689. */
  690. public function getArrayResult()
  691. {
  692. return $this->execute(null, self::HYDRATE_ARRAY);
  693. }
  694. /**
  695. * Gets the scalar results for the query.
  696. *
  697. * Alias for execute(null, HYDRATE_SCALAR).
  698. *
  699. * @return mixed[]
  700. */
  701. public function getScalarResult()
  702. {
  703. return $this->execute(null, self::HYDRATE_SCALAR);
  704. }
  705. /**
  706. * Get exactly one result or null.
  707. *
  708. * @param string|int $hydrationMode
  709. *
  710. * @return mixed
  711. *
  712. * @throws NonUniqueResultException
  713. */
  714. public function getOneOrNullResult($hydrationMode = null)
  715. {
  716. try {
  717. $result = $this->execute(null, $hydrationMode);
  718. } catch (NoResultException $e) {
  719. return null;
  720. }
  721. if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  722. return null;
  723. }
  724. if (! is_array($result)) {
  725. return $result;
  726. }
  727. if (count($result) > 1) {
  728. throw new NonUniqueResultException();
  729. }
  730. return array_shift($result);
  731. }
  732. /**
  733. * Gets the single result of the query.
  734. *
  735. * Enforces the presence as well as the uniqueness of the result.
  736. *
  737. * If the result is not unique, a NonUniqueResultException is thrown.
  738. * If there is no result, a NoResultException is thrown.
  739. *
  740. * @param string|int $hydrationMode
  741. *
  742. * @return mixed
  743. *
  744. * @throws NonUniqueResultException If the query result is not unique.
  745. * @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
  746. */
  747. public function getSingleResult($hydrationMode = null)
  748. {
  749. $result = $this->execute(null, $hydrationMode);
  750. if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  751. throw new NoResultException();
  752. }
  753. if (! is_array($result)) {
  754. return $result;
  755. }
  756. if (count($result) > 1) {
  757. throw new NonUniqueResultException();
  758. }
  759. return array_shift($result);
  760. }
  761. /**
  762. * Gets the single scalar result of the query.
  763. *
  764. * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
  765. *
  766. * @return mixed The scalar result.
  767. *
  768. * @throws NoResultException If the query returned no result.
  769. * @throws NonUniqueResultException If the query result is not unique.
  770. */
  771. public function getSingleScalarResult()
  772. {
  773. return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
  774. }
  775. /**
  776. * Sets a query hint. If the hint name is not recognized, it is silently ignored.
  777. *
  778. * @param string $name The name of the hint.
  779. * @param mixed $value The value of the hint.
  780. *
  781. * @return static This query instance.
  782. */
  783. public function setHint($name, $value)
  784. {
  785. $this->_hints[$name] = $value;
  786. return $this;
  787. }
  788. /**
  789. * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
  790. *
  791. * @param string $name The name of the hint.
  792. *
  793. * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
  794. */
  795. public function getHint($name)
  796. {
  797. return $this->_hints[$name] ?? false;
  798. }
  799. /**
  800. * Check if the query has a hint
  801. *
  802. * @param string $name The name of the hint
  803. *
  804. * @return bool False if the query does not have any hint
  805. */
  806. public function hasHint($name)
  807. {
  808. return isset($this->_hints[$name]);
  809. }
  810. /**
  811. * Return the key value map of query hints that are currently set.
  812. *
  813. * @return array<string,mixed>
  814. */
  815. public function getHints()
  816. {
  817. return $this->_hints;
  818. }
  819. /**
  820. * Executes the query and returns an IterableResult that can be used to incrementally
  821. * iterate over the result.
  822. *
  823. * @deprecated
  824. *
  825. * @param ArrayCollection|mixed[]|null $parameters The query parameters.
  826. * @param string|int|null $hydrationMode The hydration mode to use.
  827. *
  828. * @return IterableResult
  829. */
  830. public function iterate($parameters = null, $hydrationMode = null)
  831. {
  832. @trigger_error(
  833. 'Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
  834. E_USER_DEPRECATED
  835. );
  836. if ($hydrationMode !== null) {
  837. $this->setHydrationMode($hydrationMode);
  838. }
  839. if (! empty($parameters)) {
  840. $this->setParameters($parameters);
  841. }
  842. $rsm = $this->getResultSetMapping();
  843. $stmt = $this->_doExecute();
  844. return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
  845. }
  846. /**
  847. * Executes the query and returns an iterable that can be used to incrementally
  848. * iterate over the result.
  849. *
  850. * @param ArrayCollection|mixed[] $parameters The query parameters.
  851. * @param string|int|null $hydrationMode The hydration mode to use.
  852. *
  853. * @return iterable<mixed>
  854. */
  855. public function toIterable(iterable $parameters = [], $hydrationMode = null): iterable
  856. {
  857. if ($hydrationMode !== null) {
  858. $this->setHydrationMode($hydrationMode);
  859. }
  860. if (
  861. ($this->isCountable($parameters) && count($parameters) !== 0)
  862. || ($parameters instanceof Traversable && iterator_count($parameters) !== 0)
  863. ) {
  864. $this->setParameters($parameters);
  865. }
  866. $rsm = $this->getResultSetMapping();
  867. if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
  868. throw QueryException::iterateWithMixedResultNotAllowed();
  869. }
  870. $stmt = $this->_doExecute();
  871. return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints);
  872. }
  873. /**
  874. * Executes the query.
  875. *
  876. * @param ArrayCollection|mixed[]|null $parameters Query parameters.
  877. * @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
  878. *
  879. * @return mixed
  880. */
  881. public function execute($parameters = null, $hydrationMode = null)
  882. {
  883. if ($this->cacheable && $this->isCacheEnabled()) {
  884. return $this->executeUsingQueryCache($parameters, $hydrationMode);
  885. }
  886. return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
  887. }
  888. /**
  889. * Execute query ignoring second level cache.
  890. *
  891. * @param ArrayCollection|mixed[]|null $parameters
  892. * @param string|int|null $hydrationMode
  893. *
  894. * @return mixed
  895. */
  896. private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
  897. {
  898. if ($hydrationMode !== null) {
  899. $this->setHydrationMode($hydrationMode);
  900. }
  901. if (! empty($parameters)) {
  902. $this->setParameters($parameters);
  903. }
  904. $setCacheEntry = static function (): void {
  905. };
  906. if ($this->_hydrationCacheProfile !== null) {
  907. [$cacheKey, $realCacheKey] = $this->getHydrationCacheId();
  908. $queryCacheProfile = $this->getHydrationCacheProfile();
  909. $cache = $queryCacheProfile->getResultCacheDriver();
  910. $result = $cache->fetch($cacheKey);
  911. if (isset($result[$realCacheKey])) {
  912. return $result[$realCacheKey];
  913. }
  914. if (! $result) {
  915. $result = [];
  916. }
  917. $setCacheEntry = static function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile): void {
  918. $result[$realCacheKey] = $data;
  919. $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
  920. };
  921. }
  922. $stmt = $this->_doExecute();
  923. if (is_numeric($stmt)) {
  924. $setCacheEntry($stmt);
  925. return $stmt;
  926. }
  927. $rsm = $this->getResultSetMapping();
  928. $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
  929. $setCacheEntry($data);
  930. return $data;
  931. }
  932. /**
  933. * Load from second level cache or executes the query and put into cache.
  934. *
  935. * @param ArrayCollection|mixed[]|null $parameters
  936. * @param string|int|null $hydrationMode
  937. *
  938. * @return mixed
  939. */
  940. private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
  941. {
  942. $rsm = $this->getResultSetMapping();
  943. $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
  944. $queryKey = new QueryCacheKey(
  945. $this->getHash(),
  946. $this->lifetime,
  947. $this->cacheMode ?: Cache::MODE_NORMAL,
  948. $this->getTimestampKey()
  949. );
  950. $result = $queryCache->get($queryKey, $rsm, $this->_hints);
  951. if ($result !== null) {
  952. if ($this->cacheLogger) {
  953. $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
  954. }
  955. return $result;
  956. }
  957. $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
  958. $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
  959. if ($this->cacheLogger) {
  960. $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
  961. if ($cached) {
  962. $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
  963. }
  964. }
  965. return $result;
  966. }
  967. /**
  968. * @return TimestampCacheKey|null
  969. */
  970. private function getTimestampKey()
  971. {
  972. $entityName = reset($this->_resultSetMapping->aliasMap);
  973. if (empty($entityName)) {
  974. return null;
  975. }
  976. $metadata = $this->_em->getClassMetadata($entityName);
  977. return new Cache\TimestampCacheKey($metadata->rootEntityName);
  978. }
  979. /**
  980. * Get the result cache id to use to store the result set cache entry.
  981. * Will return the configured id if it exists otherwise a hash will be
  982. * automatically generated for you.
  983. *
  984. * @return array<string, string> ($key, $hash)
  985. */
  986. protected function getHydrationCacheId()
  987. {
  988. $parameters = [];
  989. foreach ($this->getParameters() as $parameter) {
  990. $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
  991. }
  992. $sql = $this->getSQL();
  993. $queryCacheProfile = $this->getHydrationCacheProfile();
  994. $hints = $this->getHints();
  995. $hints['hydrationMode'] = $this->getHydrationMode();
  996. ksort($hints);
  997. return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
  998. }
  999. /**
  1000. * Set the result cache id to use to store the result set cache entry.
  1001. * If this is not explicitly set by the developer then a hash is automatically
  1002. * generated for you.
  1003. *
  1004. * @param string $id
  1005. *
  1006. * @return static This query instance.
  1007. */
  1008. public function setResultCacheId($id)
  1009. {
  1010. $this->_queryCacheProfile = $this->_queryCacheProfile
  1011. ? $this->_queryCacheProfile->setCacheKey($id)
  1012. : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
  1013. return $this;
  1014. }
  1015. /**
  1016. * Get the result cache id to use to store the result set cache entry if set.
  1017. *
  1018. * @deprecated
  1019. *
  1020. * @return string
  1021. */
  1022. public function getResultCacheId()
  1023. {
  1024. return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
  1025. }
  1026. /**
  1027. * Executes the query and returns a the resulting Statement object.
  1028. *
  1029. * @return Statement The executed database statement that holds the results.
  1030. */
  1031. abstract protected function _doExecute();
  1032. /**
  1033. * Cleanup Query resource when clone is called.
  1034. *
  1035. * @return void
  1036. */
  1037. public function __clone()
  1038. {
  1039. $this->parameters = new ArrayCollection();
  1040. $this->_hints = [];
  1041. $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
  1042. }
  1043. /**
  1044. * Generates a string of currently query to use for the cache second level cache.
  1045. *
  1046. * @return string
  1047. */
  1048. protected function getHash()
  1049. {
  1050. $query = $this->getSQL();
  1051. $hints = $this->getHints();
  1052. $params = array_map(function (Parameter $parameter) {
  1053. $value = $parameter->getValue();
  1054. // Small optimization
  1055. // Does not invoke processParameterValue for scalar value
  1056. if (is_scalar($value)) {
  1057. return $value;
  1058. }
  1059. return $this->processParameterValue($value);
  1060. }, $this->parameters->getValues());
  1061. ksort($hints);
  1062. return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
  1063. }
  1064. /** @param iterable<mixed> $subject */
  1065. private function isCountable(iterable $subject): bool
  1066. {
  1067. return $subject instanceof Countable || is_array($subject);
  1068. }
  1069. }