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 $aliasstring $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($fromClause0$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($fromClause0$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$rootAliasestrue)) {
  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($join0, (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($join0, (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($keyExpr\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() === && $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() === && ($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'$orderBytrue);
  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. }