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

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