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

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