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

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