vendor/doctrine/orm/lib/Doctrine/ORM/QueryBuilder.php line 40

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