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