QueryBuilder.php 42 KB

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