vendor/doctrine/orm/src/QueryBuilder.php line 41

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM;
  4. use Doctrine\Common\Collections\ArrayCollection;
  5. use Doctrine\Common\Collections\Criteria;
  6. use Doctrine\Deprecations\Deprecation;
  7. use Doctrine\ORM\Internal\CriteriaOrderings;
  8. use Doctrine\ORM\Query\Expr;
  9. use Doctrine\ORM\Query\Parameter;
  10. use Doctrine\ORM\Query\QueryExpressionVisitor;
  11. use InvalidArgumentException;
  12. use RuntimeException;
  13. use function array_keys;
  14. use function array_merge;
  15. use function array_unshift;
  16. use function assert;
  17. use function func_get_args;
  18. use function func_num_args;
  19. use function implode;
  20. use function in_array;
  21. use function is_array;
  22. use function is_numeric;
  23. use function is_object;
  24. use function is_string;
  25. use function key;
  26. use function reset;
  27. use function sprintf;
  28. use function str_starts_with;
  29. use function strpos;
  30. use function strrpos;
  31. use function substr;
  32. /**
  33. * This class is responsible for building DQL query strings via an object oriented
  34. * PHP interface.
  35. */
  36. class QueryBuilder
  37. {
  38. use CriteriaOrderings;
  39. /** @deprecated */
  40. public const SELECT = 0;
  41. /** @deprecated */
  42. public const DELETE = 1;
  43. /** @deprecated */
  44. public const UPDATE = 2;
  45. /** @deprecated */
  46. public const STATE_DIRTY = 0;
  47. /** @deprecated */
  48. public const STATE_CLEAN = 1;
  49. /**
  50. * The EntityManager used by this QueryBuilder.
  51. *
  52. * @var EntityManagerInterface
  53. */
  54. private $em;
  55. /**
  56. * The array of DQL parts collected.
  57. *
  58. * @psalm-var array<string, mixed>
  59. */
  60. private $dqlParts = [
  61. 'distinct' => false,
  62. 'select' => [],
  63. 'from' => [],
  64. 'join' => [],
  65. 'set' => [],
  66. 'where' => null,
  67. 'groupBy' => [],
  68. 'having' => null,
  69. 'orderBy' => [],
  70. ];
  71. /**
  72. * The type of query this is. Can be select, update or delete.
  73. *
  74. * @var int
  75. * @psalm-var self::SELECT|self::DELETE|self::UPDATE
  76. */
  77. private $type = self::SELECT;
  78. /**
  79. * The state of the query object. Can be dirty or clean.
  80. *
  81. * @var int
  82. * @psalm-var self::STATE_*
  83. */
  84. private $state = self::STATE_CLEAN;
  85. /**
  86. * The complete DQL string for this query.
  87. *
  88. * @var string|null
  89. */
  90. private $dql;
  91. /**
  92. * The query parameters.
  93. *
  94. * @var ArrayCollection
  95. * @psalm-var ArrayCollection<int, Parameter>
  96. */
  97. private $parameters;
  98. /**
  99. * The index of the first result to retrieve.
  100. *
  101. * @var int
  102. */
  103. private $firstResult = 0;
  104. /**
  105. * The maximum number of results to retrieve.
  106. *
  107. * @var int|null
  108. */
  109. private $maxResults = null;
  110. /**
  111. * Keeps root entity alias names for join entities.
  112. *
  113. * @psalm-var array<string, string>
  114. */
  115. private $joinRootAliases = [];
  116. /**
  117. * Whether to use second level cache, if available.
  118. *
  119. * @var bool
  120. */
  121. protected $cacheable = false;
  122. /**
  123. * Second level cache region name.
  124. *
  125. * @var string|null
  126. */
  127. protected $cacheRegion;
  128. /**
  129. * Second level query cache mode.
  130. *
  131. * @var int|null
  132. * @psalm-var Cache::MODE_*|null
  133. */
  134. protected $cacheMode;
  135. /** @var int */
  136. protected $lifetime = 0;
  137. /**
  138. * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
  139. *
  140. * @param EntityManagerInterface $em The EntityManager to use.
  141. */
  142. public function __construct(EntityManagerInterface $em)
  143. {
  144. $this->em = $em;
  145. $this->parameters = new ArrayCollection();
  146. }
  147. /**
  148. * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
  149. * This producer method is intended for convenient inline usage. Example:
  150. *
  151. * <code>
  152. * $qb = $em->createQueryBuilder();
  153. * $qb
  154. * ->select('u')
  155. * ->from('User', 'u')
  156. * ->where($qb->expr()->eq('u.id', 1));
  157. * </code>
  158. *
  159. * For more complex expression construction, consider storing the expression
  160. * builder object in a local variable.
  161. *
  162. * @return Query\Expr
  163. */
  164. public function expr()
  165. {
  166. return $this->em->getExpressionBuilder();
  167. }
  168. /**
  169. * Enable/disable second level query (result) caching for this query.
  170. *
  171. * @param bool $cacheable
  172. *
  173. * @return $this
  174. */
  175. public function setCacheable($cacheable)
  176. {
  177. $this->cacheable = (bool) $cacheable;
  178. return $this;
  179. }
  180. /**
  181. * Are the query results enabled for second level cache?
  182. *
  183. * @return bool
  184. */
  185. public function isCacheable()
  186. {
  187. return $this->cacheable;
  188. }
  189. /**
  190. * @param string $cacheRegion
  191. *
  192. * @return $this
  193. */
  194. public function setCacheRegion($cacheRegion)
  195. {
  196. $this->cacheRegion = (string) $cacheRegion;
  197. return $this;
  198. }
  199. /**
  200. * Obtain the name of the second level query cache region in which query results will be stored
  201. *
  202. * @return string|null The cache region name; NULL indicates the default region.
  203. */
  204. public function getCacheRegion()
  205. {
  206. return $this->cacheRegion;
  207. }
  208. /** @return int */
  209. public function getLifetime()
  210. {
  211. return $this->lifetime;
  212. }
  213. /**
  214. * Sets the life-time for this query into second level cache.
  215. *
  216. * @param int $lifetime
  217. *
  218. * @return $this
  219. */
  220. public function setLifetime($lifetime)
  221. {
  222. $this->lifetime = (int) $lifetime;
  223. return $this;
  224. }
  225. /**
  226. * @return int|null
  227. * @psalm-return Cache::MODE_*|null
  228. */
  229. public function getCacheMode()
  230. {
  231. return $this->cacheMode;
  232. }
  233. /**
  234. * @param int $cacheMode
  235. * @psalm-param Cache::MODE_* $cacheMode
  236. *
  237. * @return $this
  238. */
  239. public function setCacheMode($cacheMode)
  240. {
  241. $this->cacheMode = (int) $cacheMode;
  242. return $this;
  243. }
  244. /**
  245. * Gets the type of the currently built query.
  246. *
  247. * @deprecated If necessary, track the type of the query being built outside of the builder.
  248. *
  249. * @return int
  250. * @psalm-return self::SELECT|self::DELETE|self::UPDATE
  251. */
  252. public function getType()
  253. {
  254. Deprecation::trigger(
  255. 'doctrine/dbal',
  256. 'https://github.com/doctrine/orm/pull/9945',
  257. 'Relying on the type of the query being built is deprecated.'
  258. . ' If necessary, track the type of the query being built outside of the builder.'
  259. );
  260. return $this->type;
  261. }
  262. /**
  263. * Gets the associated EntityManager for this query builder.
  264. *
  265. * @return EntityManagerInterface
  266. */
  267. public function getEntityManager()
  268. {
  269. return $this->em;
  270. }
  271. /**
  272. * Gets the state of this query builder instance.
  273. *
  274. * @deprecated The builder state is an internal concern.
  275. *
  276. * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
  277. * @psalm-return self::STATE_*
  278. */
  279. public function getState()
  280. {
  281. Deprecation::trigger(
  282. 'doctrine/dbal',
  283. 'https://github.com/doctrine/orm/pull/9945',
  284. 'Relying on the query builder state is deprecated as it is an internal concern.'
  285. );
  286. return $this->state;
  287. }
  288. /**
  289. * Gets the complete DQL string formed by the current specifications of this QueryBuilder.
  290. *
  291. * <code>
  292. * $qb = $em->createQueryBuilder()
  293. * ->select('u')
  294. * ->from('User', 'u');
  295. * echo $qb->getDql(); // SELECT u FROM User u
  296. * </code>
  297. *
  298. * @return string The DQL query string.
  299. */
  300. public function getDQL()
  301. {
  302. if ($this->dql !== null && $this->state === self::STATE_CLEAN) {
  303. return $this->dql;
  304. }
  305. switch ($this->type) {
  306. case self::DELETE:
  307. $dql = $this->getDQLForDelete();
  308. break;
  309. case self::UPDATE:
  310. $dql = $this->getDQLForUpdate();
  311. break;
  312. case self::SELECT:
  313. default:
  314. $dql = $this->getDQLForSelect();
  315. break;
  316. }
  317. $this->state = self::STATE_CLEAN;
  318. $this->dql = $dql;
  319. return $dql;
  320. }
  321. /**
  322. * Constructs a Query instance from the current specifications of the builder.
  323. *
  324. * <code>
  325. * $qb = $em->createQueryBuilder()
  326. * ->select('u')
  327. * ->from('User', 'u');
  328. * $q = $qb->getQuery();
  329. * $results = $q->execute();
  330. * </code>
  331. *
  332. * @return Query
  333. */
  334. public function getQuery()
  335. {
  336. $parameters = clone $this->parameters;
  337. $query = $this->em->createQuery($this->getDQL())
  338. ->setParameters($parameters)
  339. ->setFirstResult($this->firstResult)
  340. ->setMaxResults($this->maxResults);
  341. if ($this->lifetime) {
  342. $query->setLifetime($this->lifetime);
  343. }
  344. if ($this->cacheMode) {
  345. $query->setCacheMode($this->cacheMode);
  346. }
  347. if ($this->cacheable) {
  348. $query->setCacheable($this->cacheable);
  349. }
  350. if ($this->cacheRegion) {
  351. $query->setCacheRegion($this->cacheRegion);
  352. }
  353. return $query;
  354. }
  355. /**
  356. * Finds the root entity alias of the joined entity.
  357. *
  358. * @param string $alias The alias of the new join entity
  359. * @param string $parentAlias The parent entity alias of the join relationship
  360. */
  361. private function findRootAlias(string $alias, string $parentAlias): string
  362. {
  363. if (in_array($parentAlias, $this->getRootAliases(), true)) {
  364. $rootAlias = $parentAlias;
  365. } elseif (isset($this->joinRootAliases[$parentAlias])) {
  366. $rootAlias = $this->joinRootAliases[$parentAlias];
  367. } else {
  368. // Should never happen with correct joining order. Might be
  369. // thoughtful to throw exception instead.
  370. $rootAlias = $this->getRootAlias();
  371. }
  372. $this->joinRootAliases[$alias] = $rootAlias;
  373. return $rootAlias;
  374. }
  375. /**
  376. * Gets the FIRST root alias of the query. This is the first entity alias involved
  377. * in the construction of the query.
  378. *
  379. * <code>
  380. * $qb = $em->createQueryBuilder()
  381. * ->select('u')
  382. * ->from('User', 'u');
  383. *
  384. * echo $qb->getRootAlias(); // u
  385. * </code>
  386. *
  387. * @deprecated Please use $qb->getRootAliases() instead.
  388. *
  389. * @return string
  390. *
  391. * @throws RuntimeException
  392. */
  393. public function getRootAlias()
  394. {
  395. $aliases = $this->getRootAliases();
  396. if (! isset($aliases[0])) {
  397. throw new RuntimeException('No alias was set before invoking getRootAlias().');
  398. }
  399. return $aliases[0];
  400. }
  401. /**
  402. * Gets the root aliases of the query. This is the entity aliases involved
  403. * in the construction of the query.
  404. *
  405. * <code>
  406. * $qb = $em->createQueryBuilder()
  407. * ->select('u')
  408. * ->from('User', 'u');
  409. *
  410. * $qb->getRootAliases(); // array('u')
  411. * </code>
  412. *
  413. * @return string[]
  414. * @psalm-return list<string>
  415. */
  416. public function getRootAliases()
  417. {
  418. $aliases = [];
  419. foreach ($this->dqlParts['from'] as &$fromClause) {
  420. if (is_string($fromClause)) {
  421. $spacePos = strrpos($fromClause, ' ');
  422. $from = substr($fromClause, 0, $spacePos);
  423. $alias = substr($fromClause, $spacePos + 1);
  424. $fromClause = new Query\Expr\From($from, $alias);
  425. }
  426. $aliases[] = $fromClause->getAlias();
  427. }
  428. return $aliases;
  429. }
  430. /**
  431. * Gets all the aliases that have been used in the query.
  432. * Including all select root aliases and join aliases
  433. *
  434. * <code>
  435. * $qb = $em->createQueryBuilder()
  436. * ->select('u')
  437. * ->from('User', 'u')
  438. * ->join('u.articles','a');
  439. *
  440. * $qb->getAllAliases(); // array('u','a')
  441. * </code>
  442. *
  443. * @return string[]
  444. * @psalm-return list<string>
  445. */
  446. public function getAllAliases()
  447. {
  448. return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
  449. }
  450. /**
  451. * Gets the root entities of the query. This is the entity aliases involved
  452. * in the construction of the query.
  453. *
  454. * <code>
  455. * $qb = $em->createQueryBuilder()
  456. * ->select('u')
  457. * ->from('User', 'u');
  458. *
  459. * $qb->getRootEntities(); // array('User')
  460. * </code>
  461. *
  462. * @return string[]
  463. * @psalm-return list<string>
  464. */
  465. public function getRootEntities()
  466. {
  467. $entities = [];
  468. foreach ($this->dqlParts['from'] as &$fromClause) {
  469. if (is_string($fromClause)) {
  470. $spacePos = strrpos($fromClause, ' ');
  471. $from = substr($fromClause, 0, $spacePos);
  472. $alias = substr($fromClause, $spacePos + 1);
  473. $fromClause = new Query\Expr\From($from, $alias);
  474. }
  475. $entities[] = $fromClause->getFrom();
  476. }
  477. return $entities;
  478. }
  479. /**
  480. * Sets a query parameter for the query being constructed.
  481. *
  482. * <code>
  483. * $qb = $em->createQueryBuilder()
  484. * ->select('u')
  485. * ->from('User', 'u')
  486. * ->where('u.id = :user_id')
  487. * ->setParameter('user_id', 1);
  488. * </code>
  489. *
  490. * @param string|int $key The parameter position or name.
  491. * @param mixed $value The parameter value.
  492. * @param string|int|null $type ParameterType::* or \Doctrine\DBAL\Types\Type::* constant
  493. *
  494. * @return $this
  495. */
  496. public function setParameter($key, $value, $type = null)
  497. {
  498. $existingParameter = $this->getParameter($key);
  499. if ($existingParameter !== null) {
  500. $existingParameter->setValue($value, $type);
  501. return $this;
  502. }
  503. $this->parameters->add(new Parameter($key, $value, $type));
  504. return $this;
  505. }
  506. /**
  507. * Sets a collection of query parameters for the query being constructed.
  508. *
  509. * <code>
  510. * $qb = $em->createQueryBuilder()
  511. * ->select('u')
  512. * ->from('User', 'u')
  513. * ->where('u.id = :user_id1 OR u.id = :user_id2')
  514. * ->setParameters(new ArrayCollection(array(
  515. * new Parameter('user_id1', 1),
  516. * new Parameter('user_id2', 2)
  517. * )));
  518. * </code>
  519. *
  520. * @param ArrayCollection|mixed[] $parameters The query parameters to set.
  521. * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
  522. *
  523. * @return $this
  524. */
  525. public function setParameters($parameters)
  526. {
  527. // BC compatibility with 2.3-
  528. if (is_array($parameters)) {
  529. /** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */
  530. $parameterCollection = new ArrayCollection();
  531. foreach ($parameters as $key => $value) {
  532. $parameter = new Parameter($key, $value);
  533. $parameterCollection->add($parameter);
  534. }
  535. $parameters = $parameterCollection;
  536. }
  537. $this->parameters = $parameters;
  538. return $this;
  539. }
  540. /**
  541. * Gets all defined query parameters for the query being constructed.
  542. *
  543. * @return ArrayCollection The currently defined query parameters.
  544. * @psalm-return ArrayCollection<int, Parameter>
  545. */
  546. public function getParameters()
  547. {
  548. return $this->parameters;
  549. }
  550. /**
  551. * Gets a (previously set) query parameter of the query being constructed.
  552. *
  553. * @param string|int $key The key (index or name) of the bound parameter.
  554. *
  555. * @return Parameter|null The value of the bound parameter.
  556. */
  557. public function getParameter($key)
  558. {
  559. $key = Parameter::normalizeName($key);
  560. $filteredParameters = $this->parameters->filter(
  561. static function (Parameter $parameter) use ($key): bool {
  562. $parameterName = $parameter->getName();
  563. return $key === $parameterName;
  564. }
  565. );
  566. return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
  567. }
  568. /**
  569. * Sets the position of the first result to retrieve (the "offset").
  570. *
  571. * @param int|null $firstResult The first result to return.
  572. *
  573. * @return $this
  574. */
  575. public function setFirstResult($firstResult)
  576. {
  577. $this->firstResult = (int) $firstResult;
  578. return $this;
  579. }
  580. /**
  581. * Gets the position of the first result the query object was set to retrieve (the "offset").
  582. * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
  583. *
  584. * @return int|null The position of the first result.
  585. */
  586. public function getFirstResult()
  587. {
  588. return $this->firstResult;
  589. }
  590. /**
  591. * Sets the maximum number of results to retrieve (the "limit").
  592. *
  593. * @param int|null $maxResults The maximum number of results to retrieve.
  594. *
  595. * @return $this
  596. */
  597. public function setMaxResults($maxResults)
  598. {
  599. if ($maxResults !== null) {
  600. $maxResults = (int) $maxResults;
  601. }
  602. $this->maxResults = $maxResults;
  603. return $this;
  604. }
  605. /**
  606. * Gets the maximum number of results the query object was set to retrieve (the "limit").
  607. * Returns NULL if {@link setMaxResults} was not applied to this query builder.
  608. *
  609. * @return int|null Maximum number of results.
  610. */
  611. public function getMaxResults()
  612. {
  613. return $this->maxResults;
  614. }
  615. /**
  616. * Either appends to or replaces a single, generic query part.
  617. *
  618. * The available parts are: 'select', 'from', 'join', 'set', 'where',
  619. * 'groupBy', 'having' and 'orderBy'.
  620. *
  621. * @param string $dqlPartName The DQL part name.
  622. * @param string|object|array $dqlPart An Expr object.
  623. * @param bool $append Whether to append (true) or replace (false).
  624. * @psalm-param string|object|list<string>|array{join: array<int|string, object>} $dqlPart
  625. *
  626. * @return $this
  627. */
  628. public function add($dqlPartName, $dqlPart, $append = false)
  629. {
  630. if ($append && ($dqlPartName === 'where' || $dqlPartName === 'having')) {
  631. throw new InvalidArgumentException(
  632. "Using \$append = true does not have an effect with 'where' or 'having' " .
  633. 'parts. See QueryBuilder#andWhere() for an example for correct usage.'
  634. );
  635. }
  636. $isMultiple = is_array($this->dqlParts[$dqlPartName])
  637. && ! ($dqlPartName === 'join' && ! $append);
  638. // Allow adding any part retrieved from self::getDQLParts().
  639. if (is_array($dqlPart) && $dqlPartName !== 'join') {
  640. $dqlPart = reset($dqlPart);
  641. }
  642. // This is introduced for backwards compatibility reasons.
  643. // TODO: Remove for 3.0
  644. if ($dqlPartName === 'join') {
  645. $newDqlPart = [];
  646. foreach ($dqlPart as $k => $v) {
  647. $k = is_numeric($k) ? $this->getRootAlias() : $k;
  648. $newDqlPart[$k] = $v;
  649. }
  650. $dqlPart = $newDqlPart;
  651. }
  652. if ($append && $isMultiple) {
  653. if (is_array($dqlPart)) {
  654. $key = key($dqlPart);
  655. $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
  656. } else {
  657. $this->dqlParts[$dqlPartName][] = $dqlPart;
  658. }
  659. } else {
  660. $this->dqlParts[$dqlPartName] = $isMultiple ? [$dqlPart] : $dqlPart;
  661. }
  662. $this->state = self::STATE_DIRTY;
  663. return $this;
  664. }
  665. /**
  666. * Specifies an item that is to be returned in the query result.
  667. * Replaces any previously specified selections, if any.
  668. *
  669. * <code>
  670. * $qb = $em->createQueryBuilder()
  671. * ->select('u', 'p')
  672. * ->from('User', 'u')
  673. * ->leftJoin('u.Phonenumbers', 'p');
  674. * </code>
  675. *
  676. * @param mixed $select The selection expressions.
  677. *
  678. * @return $this
  679. */
  680. public function select($select = null)
  681. {
  682. $this->type = self::SELECT;
  683. if (empty($select)) {
  684. return $this;
  685. }
  686. $selects = is_array($select) ? $select : func_get_args();
  687. return $this->add('select', new Expr\Select($selects), false);
  688. }
  689. /**
  690. * Adds a DISTINCT flag to this query.
  691. *
  692. * <code>
  693. * $qb = $em->createQueryBuilder()
  694. * ->select('u')
  695. * ->distinct()
  696. * ->from('User', 'u');
  697. * </code>
  698. *
  699. * @param bool $flag
  700. *
  701. * @return $this
  702. */
  703. public function distinct($flag = true)
  704. {
  705. $flag = (bool) $flag;
  706. if ($this->dqlParts['distinct'] !== $flag) {
  707. $this->dqlParts['distinct'] = $flag;
  708. $this->state = self::STATE_DIRTY;
  709. }
  710. return $this;
  711. }
  712. /**
  713. * Adds an item that is to be returned in the query result.
  714. *
  715. * <code>
  716. * $qb = $em->createQueryBuilder()
  717. * ->select('u')
  718. * ->addSelect('p')
  719. * ->from('User', 'u')
  720. * ->leftJoin('u.Phonenumbers', 'p');
  721. * </code>
  722. *
  723. * @param mixed $select The selection expression.
  724. *
  725. * @return $this
  726. */
  727. public function addSelect($select = null)
  728. {
  729. $this->type = self::SELECT;
  730. if (empty($select)) {
  731. return $this;
  732. }
  733. $selects = is_array($select) ? $select : func_get_args();
  734. return $this->add('select', new Expr\Select($selects), true);
  735. }
  736. /**
  737. * Turns the query being built into a bulk delete query that ranges over
  738. * a certain entity type.
  739. *
  740. * <code>
  741. * $qb = $em->createQueryBuilder()
  742. * ->delete('User', 'u')
  743. * ->where('u.id = :user_id')
  744. * ->setParameter('user_id', 1);
  745. * </code>
  746. *
  747. * @param string|null $delete The class/type whose instances are subject to the deletion.
  748. * @param string|null $alias The class/type alias used in the constructed query.
  749. *
  750. * @return $this
  751. */
  752. public function delete($delete = null, $alias = null)
  753. {
  754. $this->type = self::DELETE;
  755. if (! $delete) {
  756. return $this;
  757. }
  758. if (! $alias) {
  759. Deprecation::trigger(
  760. 'doctrine/orm',
  761. 'https://github.com/doctrine/orm/issues/9733',
  762. 'Omitting the alias is deprecated and will throw an exception in Doctrine 3.0.'
  763. );
  764. }
  765. return $this->add('from', new Expr\From($delete, $alias));
  766. }
  767. /**
  768. * Turns the query being built into a bulk update query that ranges over
  769. * a certain entity type.
  770. *
  771. * <code>
  772. * $qb = $em->createQueryBuilder()
  773. * ->update('User', 'u')
  774. * ->set('u.password', '?1')
  775. * ->where('u.id = ?2');
  776. * </code>
  777. *
  778. * @param string|null $update The class/type whose instances are subject to the update.
  779. * @param string|null $alias The class/type alias used in the constructed query.
  780. *
  781. * @return $this
  782. */
  783. public function update($update = null, $alias = null)
  784. {
  785. $this->type = self::UPDATE;
  786. if (! $update) {
  787. return $this;
  788. }
  789. if (! $alias) {
  790. Deprecation::trigger(
  791. 'doctrine/orm',
  792. 'https://github.com/doctrine/orm/issues/9733',
  793. 'Omitting the alias is deprecated and will throw an exception in Doctrine 3.0.'
  794. );
  795. }
  796. return $this->add('from', new Expr\From($update, $alias));
  797. }
  798. /**
  799. * Creates and adds a query root corresponding to the entity identified by the given alias,
  800. * forming a cartesian product with any existing query roots.
  801. *
  802. * <code>
  803. * $qb = $em->createQueryBuilder()
  804. * ->select('u')
  805. * ->from('User', 'u');
  806. * </code>
  807. *
  808. * @param string $from The class name.
  809. * @param string $alias The alias of the class.
  810. * @param string|null $indexBy The index for the from.
  811. *
  812. * @return $this
  813. */
  814. public function from($from, $alias, $indexBy = null)
  815. {
  816. return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
  817. }
  818. /**
  819. * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
  820. * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
  821. * setting an index by.
  822. *
  823. * <code>
  824. * $qb = $userRepository->createQueryBuilder('u')
  825. * ->indexBy('u', 'u.id');
  826. *
  827. * // Is equivalent to...
  828. *
  829. * $qb = $em->createQueryBuilder()
  830. * ->select('u')
  831. * ->from('User', 'u', 'u.id');
  832. * </code>
  833. *
  834. * @param string $alias The root alias of the class.
  835. * @param string $indexBy The index for the from.
  836. *
  837. * @return $this
  838. *
  839. * @throws Query\QueryException
  840. */
  841. public function indexBy($alias, $indexBy)
  842. {
  843. $rootAliases = $this->getRootAliases();
  844. if (! in_array($alias, $rootAliases, true)) {
  845. throw new Query\QueryException(
  846. sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
  847. );
  848. }
  849. foreach ($this->dqlParts['from'] as &$fromClause) {
  850. assert($fromClause instanceof Expr\From);
  851. if ($fromClause->getAlias() !== $alias) {
  852. continue;
  853. }
  854. $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
  855. }
  856. return $this;
  857. }
  858. /**
  859. * Creates and adds a join over an entity association to the query.
  860. *
  861. * The entities in the joined association will be fetched as part of the query
  862. * result if the alias used for the joined association is placed in the select
  863. * expressions.
  864. *
  865. * <code>
  866. * $qb = $em->createQueryBuilder()
  867. * ->select('u')
  868. * ->from('User', 'u')
  869. * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
  870. * </code>
  871. *
  872. * @param string $join The relationship to join.
  873. * @param string $alias The alias of the join.
  874. * @param string|null $conditionType The condition type constant. Either ON or WITH.
  875. * @param string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition The condition for the join.
  876. * @param string|null $indexBy The index for the join.
  877. * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType
  878. *
  879. * @return $this
  880. */
  881. public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
  882. {
  883. return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
  884. }
  885. /**
  886. * Creates and adds a join over an entity association to the query.
  887. *
  888. * The entities in the joined association will be fetched as part of the query
  889. * result if the alias used for the joined association is placed in the select
  890. * expressions.
  891. *
  892. * [php]
  893. * $qb = $em->createQueryBuilder()
  894. * ->select('u')
  895. * ->from('User', 'u')
  896. * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
  897. *
  898. * @param string $join The relationship to join.
  899. * @param string $alias The alias of the join.
  900. * @param string|null $conditionType The condition type constant. Either ON or WITH.
  901. * @param string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition The condition for the join.
  902. * @param string|null $indexBy The index for the join.
  903. * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType
  904. *
  905. * @return $this
  906. */
  907. public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
  908. {
  909. $parentAlias = substr($join, 0, (int) strpos($join, '.'));
  910. $rootAlias = $this->findRootAlias($alias, $parentAlias);
  911. $join = new Expr\Join(
  912. Expr\Join::INNER_JOIN,
  913. $join,
  914. $alias,
  915. $conditionType,
  916. $condition,
  917. $indexBy
  918. );
  919. return $this->add('join', [$rootAlias => $join], true);
  920. }
  921. /**
  922. * Creates and adds a left join over an entity association to the query.
  923. *
  924. * The entities in the joined association will be fetched as part of the query
  925. * result if the alias used for the joined association is placed in the select
  926. * expressions.
  927. *
  928. * <code>
  929. * $qb = $em->createQueryBuilder()
  930. * ->select('u')
  931. * ->from('User', 'u')
  932. * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
  933. * </code>
  934. *
  935. * @param string $join The relationship to join.
  936. * @param string $alias The alias of the join.
  937. * @param string|null $conditionType The condition type constant. Either ON or WITH.
  938. * @param string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition The condition for the join.
  939. * @param string|null $indexBy The index for the join.
  940. * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType
  941. *
  942. * @return $this
  943. */
  944. public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
  945. {
  946. $parentAlias = substr($join, 0, (int) strpos($join, '.'));
  947. $rootAlias = $this->findRootAlias($alias, $parentAlias);
  948. $join = new Expr\Join(
  949. Expr\Join::LEFT_JOIN,
  950. $join,
  951. $alias,
  952. $conditionType,
  953. $condition,
  954. $indexBy
  955. );
  956. return $this->add('join', [$rootAlias => $join], true);
  957. }
  958. /**
  959. * Sets a new value for a field in a bulk update query.
  960. *
  961. * <code>
  962. * $qb = $em->createQueryBuilder()
  963. * ->update('User', 'u')
  964. * ->set('u.password', '?1')
  965. * ->where('u.id = ?2');
  966. * </code>
  967. *
  968. * @param string $key The key/field to set.
  969. * @param mixed $value The value, expression, placeholder, etc.
  970. *
  971. * @return $this
  972. */
  973. public function set($key, $value)
  974. {
  975. return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
  976. }
  977. /**
  978. * Specifies one or more restrictions to the query result.
  979. * Replaces any previously specified restrictions, if any.
  980. *
  981. * <code>
  982. * $qb = $em->createQueryBuilder()
  983. * ->select('u')
  984. * ->from('User', 'u')
  985. * ->where('u.id = ?');
  986. *
  987. * // You can optionally programmatically build and/or expressions
  988. * $qb = $em->createQueryBuilder();
  989. *
  990. * $or = $qb->expr()->orX();
  991. * $or->add($qb->expr()->eq('u.id', 1));
  992. * $or->add($qb->expr()->eq('u.id', 2));
  993. *
  994. * $qb->update('User', 'u')
  995. * ->set('u.password', '?')
  996. * ->where($or);
  997. * </code>
  998. *
  999. * @param mixed $predicates The restriction predicates.
  1000. *
  1001. * @return $this
  1002. */
  1003. public function where($predicates)
  1004. {
  1005. if (! (func_num_args() === 1 && $predicates instanceof Expr\Composite)) {
  1006. $predicates = new Expr\Andx(func_get_args());
  1007. }
  1008. return $this->add('where', $predicates);
  1009. }
  1010. /**
  1011. * Adds one or more restrictions to the query results, forming a logical
  1012. * conjunction with any previously specified restrictions.
  1013. *
  1014. * <code>
  1015. * $qb = $em->createQueryBuilder()
  1016. * ->select('u')
  1017. * ->from('User', 'u')
  1018. * ->where('u.username LIKE ?')
  1019. * ->andWhere('u.is_active = 1');
  1020. * </code>
  1021. *
  1022. * @see where()
  1023. *
  1024. * @param mixed $where The query restrictions.
  1025. *
  1026. * @return $this
  1027. */
  1028. public function andWhere()
  1029. {
  1030. $args = func_get_args();
  1031. $where = $this->getDQLPart('where');
  1032. if ($where instanceof Expr\Andx) {
  1033. $where->addMultiple($args);
  1034. } else {
  1035. array_unshift($args, $where);
  1036. $where = new Expr\Andx($args);
  1037. }
  1038. return $this->add('where', $where);
  1039. }
  1040. /**
  1041. * Adds one or more restrictions to the query results, forming a logical
  1042. * disjunction with any previously specified restrictions.
  1043. *
  1044. * <code>
  1045. * $qb = $em->createQueryBuilder()
  1046. * ->select('u')
  1047. * ->from('User', 'u')
  1048. * ->where('u.id = 1')
  1049. * ->orWhere('u.id = 2');
  1050. * </code>
  1051. *
  1052. * @see where()
  1053. *
  1054. * @param mixed $where The WHERE statement.
  1055. *
  1056. * @return $this
  1057. */
  1058. public function orWhere()
  1059. {
  1060. $args = func_get_args();
  1061. $where = $this->getDQLPart('where');
  1062. if ($where instanceof Expr\Orx) {
  1063. $where->addMultiple($args);
  1064. } else {
  1065. array_unshift($args, $where);
  1066. $where = new Expr\Orx($args);
  1067. }
  1068. return $this->add('where', $where);
  1069. }
  1070. /**
  1071. * Specifies a grouping over the results of the query.
  1072. * Replaces any previously specified groupings, if any.
  1073. *
  1074. * <code>
  1075. * $qb = $em->createQueryBuilder()
  1076. * ->select('u')
  1077. * ->from('User', 'u')
  1078. * ->groupBy('u.id');
  1079. * </code>
  1080. *
  1081. * @param string $groupBy The grouping expression.
  1082. *
  1083. * @return $this
  1084. */
  1085. public function groupBy($groupBy)
  1086. {
  1087. return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
  1088. }
  1089. /**
  1090. * Adds a grouping expression to the query.
  1091. *
  1092. * <code>
  1093. * $qb = $em->createQueryBuilder()
  1094. * ->select('u')
  1095. * ->from('User', 'u')
  1096. * ->groupBy('u.lastLogin')
  1097. * ->addGroupBy('u.createdAt');
  1098. * </code>
  1099. *
  1100. * @param string $groupBy The grouping expression.
  1101. *
  1102. * @return $this
  1103. */
  1104. public function addGroupBy($groupBy)
  1105. {
  1106. return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
  1107. }
  1108. /**
  1109. * Specifies a restriction over the groups of the query.
  1110. * Replaces any previous having restrictions, if any.
  1111. *
  1112. * @param mixed $having The restriction over the groups.
  1113. *
  1114. * @return $this
  1115. */
  1116. public function having($having)
  1117. {
  1118. if (! (func_num_args() === 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
  1119. $having = new Expr\Andx(func_get_args());
  1120. }
  1121. return $this->add('having', $having);
  1122. }
  1123. /**
  1124. * Adds a restriction over the groups of the query, forming a logical
  1125. * conjunction with any existing having restrictions.
  1126. *
  1127. * @param mixed $having The restriction to append.
  1128. *
  1129. * @return $this
  1130. */
  1131. public function andHaving($having)
  1132. {
  1133. $args = func_get_args();
  1134. $having = $this->getDQLPart('having');
  1135. if ($having instanceof Expr\Andx) {
  1136. $having->addMultiple($args);
  1137. } else {
  1138. array_unshift($args, $having);
  1139. $having = new Expr\Andx($args);
  1140. }
  1141. return $this->add('having', $having);
  1142. }
  1143. /**
  1144. * Adds a restriction over the groups of the query, forming a logical
  1145. * disjunction with any existing having restrictions.
  1146. *
  1147. * @param mixed $having The restriction to add.
  1148. *
  1149. * @return $this
  1150. */
  1151. public function orHaving($having)
  1152. {
  1153. $args = func_get_args();
  1154. $having = $this->getDQLPart('having');
  1155. if ($having instanceof Expr\Orx) {
  1156. $having->addMultiple($args);
  1157. } else {
  1158. array_unshift($args, $having);
  1159. $having = new Expr\Orx($args);
  1160. }
  1161. return $this->add('having', $having);
  1162. }
  1163. /**
  1164. * Specifies an ordering for the query results.
  1165. * Replaces any previously specified orderings, if any.
  1166. *
  1167. * @param string|Expr\OrderBy $sort The ordering expression.
  1168. * @param string|null $order The ordering direction.
  1169. *
  1170. * @return $this
  1171. */
  1172. public function orderBy($sort, $order = null)
  1173. {
  1174. $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order);
  1175. return $this->add('orderBy', $orderBy);
  1176. }
  1177. /**
  1178. * Adds an ordering to the query results.
  1179. *
  1180. * @param string|Expr\OrderBy $sort The ordering expression.
  1181. * @param string|null $order The ordering direction.
  1182. *
  1183. * @return $this
  1184. */
  1185. public function addOrderBy($sort, $order = null)
  1186. {
  1187. $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order);
  1188. return $this->add('orderBy', $orderBy, true);
  1189. }
  1190. /**
  1191. * Adds criteria to the query.
  1192. *
  1193. * Adds where expressions with AND operator.
  1194. * Adds orderings.
  1195. * Overrides firstResult and maxResults if they're set.
  1196. *
  1197. * @return $this
  1198. *
  1199. * @throws Query\QueryException
  1200. */
  1201. public function addCriteria(Criteria $criteria)
  1202. {
  1203. $allAliases = $this->getAllAliases();
  1204. if (! isset($allAliases[0])) {
  1205. throw new Query\QueryException('No aliases are set before invoking addCriteria().');
  1206. }
  1207. $visitor = new QueryExpressionVisitor($this->getAllAliases());
  1208. $whereExpression = $criteria->getWhereExpression();
  1209. if ($whereExpression) {
  1210. $this->andWhere($visitor->dispatch($whereExpression));
  1211. foreach ($visitor->getParameters() as $parameter) {
  1212. $this->parameters->add($parameter);
  1213. }
  1214. }
  1215. foreach (self::getCriteriaOrderings($criteria) as $sort => $order) {
  1216. $hasValidAlias = false;
  1217. foreach ($allAliases as $alias) {
  1218. if (str_starts_with($sort . '.', $alias . '.')) {
  1219. $hasValidAlias = true;
  1220. break;
  1221. }
  1222. }
  1223. if (! $hasValidAlias) {
  1224. $sort = $allAliases[0] . '.' . $sort;
  1225. }
  1226. $this->addOrderBy($sort, $order);
  1227. }
  1228. // Overwrite limits only if they was set in criteria
  1229. $firstResult = $criteria->getFirstResult();
  1230. if ($firstResult > 0) {
  1231. $this->setFirstResult($firstResult);
  1232. }
  1233. $maxResults = $criteria->getMaxResults();
  1234. if ($maxResults !== null) {
  1235. $this->setMaxResults($maxResults);
  1236. }
  1237. return $this;
  1238. }
  1239. /**
  1240. * Gets a query part by its name.
  1241. *
  1242. * @param string $queryPartName
  1243. *
  1244. * @return mixed $queryPart
  1245. */
  1246. public function getDQLPart($queryPartName)
  1247. {
  1248. return $this->dqlParts[$queryPartName];
  1249. }
  1250. /**
  1251. * Gets all query parts.
  1252. *
  1253. * @psalm-return array<string, mixed> $dqlParts
  1254. */
  1255. public function getDQLParts()
  1256. {
  1257. return $this->dqlParts;
  1258. }
  1259. private function getDQLForDelete(): string
  1260. {
  1261. return 'DELETE'
  1262. . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
  1263. . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
  1264. . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
  1265. }
  1266. private function getDQLForUpdate(): string
  1267. {
  1268. return 'UPDATE'
  1269. . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
  1270. . $this->getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
  1271. . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
  1272. . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
  1273. }
  1274. private function getDQLForSelect(): string
  1275. {
  1276. $dql = 'SELECT'
  1277. . ($this->dqlParts['distinct'] === true ? ' DISTINCT' : '')
  1278. . $this->getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
  1279. $fromParts = $this->getDQLPart('from');
  1280. $joinParts = $this->getDQLPart('join');
  1281. $fromClauses = [];
  1282. // Loop through all FROM clauses
  1283. if (! empty($fromParts)) {
  1284. $dql .= ' FROM ';
  1285. foreach ($fromParts as $from) {
  1286. $fromClause = (string) $from;
  1287. if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
  1288. foreach ($joinParts[$from->getAlias()] as $join) {
  1289. $fromClause .= ' ' . ((string) $join);
  1290. }
  1291. }
  1292. $fromClauses[] = $fromClause;
  1293. }
  1294. }
  1295. $dql .= implode(', ', $fromClauses)
  1296. . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
  1297. . $this->getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
  1298. . $this->getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
  1299. . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
  1300. return $dql;
  1301. }
  1302. /** @psalm-param array<string, mixed> $options */
  1303. private function getReducedDQLQueryPart(string $queryPartName, array $options = []): string
  1304. {
  1305. $queryPart = $this->getDQLPart($queryPartName);
  1306. if (empty($queryPart)) {
  1307. return $options['empty'] ?? '';
  1308. }
  1309. return ($options['pre'] ?? '')
  1310. . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
  1311. . ($options['post'] ?? '');
  1312. }
  1313. /**
  1314. * Resets DQL parts.
  1315. *
  1316. * @param string[]|null $parts
  1317. * @psalm-param list<string>|null $parts
  1318. *
  1319. * @return $this
  1320. */
  1321. public function resetDQLParts($parts = null)
  1322. {
  1323. if ($parts === null) {
  1324. $parts = array_keys($this->dqlParts);
  1325. }
  1326. foreach ($parts as $part) {
  1327. $this->resetDQLPart($part);
  1328. }
  1329. return $this;
  1330. }
  1331. /**
  1332. * Resets single DQL part.
  1333. *
  1334. * @param string $part
  1335. *
  1336. * @return $this
  1337. */
  1338. public function resetDQLPart($part)
  1339. {
  1340. $this->dqlParts[$part] = is_array($this->dqlParts[$part]) ? [] : null;
  1341. $this->state = self::STATE_DIRTY;
  1342. return $this;
  1343. }
  1344. /**
  1345. * Gets a string representation of this QueryBuilder which corresponds to
  1346. * the final DQL query being constructed.
  1347. *
  1348. * @return string The string representation of this QueryBuilder.
  1349. */
  1350. public function __toString()
  1351. {
  1352. return $this->getDQL();
  1353. }
  1354. /**
  1355. * Deep clones all expression objects in the DQL parts.
  1356. *
  1357. * @return void
  1358. */
  1359. public function __clone()
  1360. {
  1361. foreach ($this->dqlParts as $part => $elements) {
  1362. if (is_array($this->dqlParts[$part])) {
  1363. foreach ($this->dqlParts[$part] as $idx => $element) {
  1364. if (is_object($element)) {
  1365. $this->dqlParts[$part][$idx] = clone $element;
  1366. }
  1367. }
  1368. } elseif (is_object($elements)) {
  1369. $this->dqlParts[$part] = clone $elements;
  1370. }
  1371. }
  1372. $parameters = [];
  1373. foreach ($this->parameters as $parameter) {
  1374. $parameters[] = clone $parameter;
  1375. }
  1376. $this->parameters = new ArrayCollection($parameters);
  1377. }
  1378. }