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

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