vendor/phpcr/phpcr-utils/src/PHPCR/Util/QOM/QueryBuilder.php line 373

Open in your IDE?
  1. <?php
  2. namespace PHPCR\Util\QOM;
  3. use InvalidArgumentException;
  4. use PHPCR\Query\QOM\ColumnInterface;
  5. use PHPCR\Query\QOM\ConstraintInterface;
  6. use PHPCR\Query\QOM\DynamicOperandInterface;
  7. use PHPCR\Query\QOM\JoinConditionInterface;
  8. use PHPCR\Query\QOM\OrderingInterface;
  9. use PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
  10. use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
  11. use PHPCR\Query\QOM\QueryObjectModelInterface;
  12. use PHPCR\Query\QOM\SourceInterface;
  13. use PHPCR\Query\QueryInterface;
  14. use PHPCR\Query\QueryResultInterface;
  15. use RuntimeException;
  16. /**
  17.  * QueryBuilder class is responsible for dynamically create QOM queries.
  18.  *
  19.  * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
  20.  * @license http://opensource.org/licenses/MIT MIT License
  21.  * @author      Nacho Martín <nitram.ohcan@gmail.com>
  22.  * @author      Guilherme Blanco <guilhermeblanco@hotmail.com>
  23.  * @author      Benjamin Eberlei <kontakt@beberlei.de>
  24.  */
  25. class QueryBuilder
  26. {
  27.     /** The builder states. */
  28.     const STATE_DIRTY 0;
  29.     const STATE_CLEAN 1;
  30.     /**
  31.      * @var int The state of the query object. Can be dirty or clean.
  32.      */
  33.     private $state self::STATE_CLEAN;
  34.     /**
  35.      * @var QueryObjectModelFactoryInterface QOMFactory
  36.      */
  37.     private $qomFactory;
  38.     /**
  39.      * @var int The maximum number of results to retrieve.
  40.      */
  41.     private $firstResult null;
  42.     /**
  43.      * @var int The maximum number of results to retrieve.
  44.      */
  45.     private $maxResults null;
  46.     /**
  47.      * @var array with the orderings that determine the order of the result
  48.      */
  49.     private $orderings = [];
  50.     /**
  51.      * @var ConstraintInterface to apply to the query.
  52.      */
  53.     private $constraint null;
  54.     /**
  55.      * @var array with the columns to be selected.
  56.      */
  57.     private $columns = [];
  58.     /**
  59.      * @var SourceInterface source of the query.
  60.      */
  61.     private $source null;
  62.     /**
  63.      * QOM tree.
  64.      *
  65.      * @var QueryObjectModelInterface
  66.      */
  67.     private $query null;
  68.     /**
  69.      * @var array The query parameters.
  70.      */
  71.     private $params = [];
  72.     /**
  73.      * Initializes a new QueryBuilder.
  74.      *
  75.      * @param QueryObjectModelFactoryInterface $qomFactory
  76.      */
  77.     public function __construct(QueryObjectModelFactoryInterface $qomFactory)
  78.     {
  79.         $this->qomFactory $qomFactory;
  80.     }
  81.     /**
  82.      * Get a query builder instance from an existing query.
  83.      *
  84.      * @param string $statement the statement in the specified language
  85.      * @param string $language  the query language
  86.      *
  87.      * @throws InvalidArgumentException
  88.      *
  89.      * @return QueryBuilder This QueryBuilder instance.
  90.      */
  91.     public function setFromQuery($statement$language)
  92.     {
  93.         if (QueryInterface::JCR_SQL2 === $language) {
  94.             $converter = new Sql2ToQomQueryConverter($this->qomFactory);
  95.             $statement $converter->parse($statement);
  96.         }
  97.         if (!$statement instanceof QueryObjectModelInterface) {
  98.             throw new InvalidArgumentException("Language '$language' not supported");
  99.         }
  100.         $this->state self::STATE_DIRTY;
  101.         $this->source $statement->getSource();
  102.         $this->constraint $statement->getConstraint();
  103.         $this->orderings $statement->getOrderings();
  104.         $this->columns $statement->getColumns();
  105.         return $this;
  106.     }
  107.     /**
  108.      * Get the associated QOMFactory for this query builder.
  109.      *
  110.      * @return QueryObjectModelFactoryInterface
  111.      */
  112.     public function getQOMFactory()
  113.     {
  114.         return $this->qomFactory;
  115.     }
  116.     /**
  117.      * Shortcut for getQOMFactory().
  118.      */
  119.     public function qomf()
  120.     {
  121.         return $this->getQOMFactory();
  122.     }
  123.     /**
  124.      * sets the position of the first result to retrieve (the "offset").
  125.      *
  126.      * @param int $firstResult The First result to return.
  127.      *
  128.      * @return QueryBuilder This QueryBuilder instance.
  129.      */
  130.     public function setFirstResult($firstResult)
  131.     {
  132.         $this->firstResult $firstResult;
  133.         return $this;
  134.     }
  135.     /**
  136.      * Gets the position of the first result the query object was set to retrieve (the "offset").
  137.      * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
  138.      *
  139.      * @return int The position of the first result.
  140.      */
  141.     public function getFirstResult()
  142.     {
  143.         return $this->firstResult;
  144.     }
  145.     /**
  146.      * Sets the maximum number of results to retrieve (the "limit").
  147.      *
  148.      * @param int $maxResults The maximum number of results to retrieve.
  149.      *
  150.      * @return QueryBuilder This QueryBuilder instance.
  151.      */
  152.     public function setMaxResults($maxResults)
  153.     {
  154.         $this->maxResults $maxResults;
  155.         return $this;
  156.     }
  157.     /**
  158.      * Gets the maximum number of results the query object was set to retrieve (the "limit").
  159.      * Returns NULL if {@link setMaxResults} was not applied to this query builder.
  160.      *
  161.      * @return int Maximum number of results.
  162.      */
  163.     public function getMaxResults()
  164.     {
  165.         return $this->maxResults;
  166.     }
  167.     /**
  168.      * Gets the array of orderings.
  169.      *
  170.      * @return OrderingInterface[] Orderings to apply.
  171.      */
  172.     public function getOrderings()
  173.     {
  174.         return $this->orderings;
  175.     }
  176.     /**
  177.      * Adds an ordering to the query results.
  178.      *
  179.      * @param DynamicOperandInterface $sort  The ordering expression.
  180.      * @param string                  $order The ordering direction.
  181.      *
  182.      * @throws InvalidArgumentException
  183.      *
  184.      * @return QueryBuilder This QueryBuilder instance.
  185.      */
  186.     public function addOrderBy(DynamicOperandInterface $sort$order 'ASC')
  187.     {
  188.         $order strtoupper($order);
  189.         if (!in_array($order, ['ASC''DESC'])) {
  190.             throw new InvalidArgumentException('Order must be one of "ASC" or "DESC"');
  191.         }
  192.         $this->state self::STATE_DIRTY;
  193.         if ($order === 'DESC') {
  194.             $ordering $this->qomFactory->descending($sort);
  195.         } else {
  196.             $ordering $this->qomFactory->ascending($sort);
  197.         }
  198.         $this->orderings[] = $ordering;
  199.         return $this;
  200.     }
  201.     /**
  202.      * Specifies an ordering for the query results.
  203.      * Replaces any previously specified orderings, if any.
  204.      *
  205.      * @param DynamicOperandInterface $sort  The ordering expression.
  206.      * @param string                  $order The ordering direction.
  207.      *
  208.      * @return QueryBuilder This QueryBuilder instance.
  209.      */
  210.     public function orderBy(DynamicOperandInterface $sort$order 'ASC')
  211.     {
  212.         $this->orderings = [];
  213.         $this->addOrderBy($sort$order);
  214.         return $this;
  215.     }
  216.     /**
  217.      * Specifies one restriction (may be simple or composed).
  218.      * Replaces any previously specified restrictions, if any.
  219.      *
  220.      * @param ConstraintInterface $constraint
  221.      *
  222.      * @return QueryBuilder This QueryBuilder instance.
  223.      */
  224.     public function where(ConstraintInterface $constraint)
  225.     {
  226.         $this->state self::STATE_DIRTY;
  227.         $this->constraint $constraint;
  228.         return $this;
  229.     }
  230.     /**
  231.      * Returns the constraint to apply.
  232.      *
  233.      * @return ConstraintInterface the constraint to be applied
  234.      */
  235.     public function getConstraint()
  236.     {
  237.         return $this->constraint;
  238.     }
  239.     /**
  240.      * Creates a new constraint formed by applying a logical AND to the
  241.      * existing constraint and the new one.
  242.      *
  243.      * Order of ands is important:
  244.      *
  245.      * Given $this->constraint = $constraint1
  246.      * running andWhere($constraint2)
  247.      * resulting constraint will be $constraint1 AND $constraint2
  248.      *
  249.      * If there is no previous constraint then it will simply store the
  250.      * provided one
  251.      *
  252.      * @param ConstraintInterface $constraint
  253.      *
  254.      * @return QueryBuilder This QueryBuilder instance.
  255.      */
  256.     public function andWhere(ConstraintInterface $constraint)
  257.     {
  258.         $this->state self::STATE_DIRTY;
  259.         if ($this->constraint) {
  260.             $this->constraint $this->qomFactory->andConstraint($this->constraint$constraint);
  261.         } else {
  262.             $this->constraint $constraint;
  263.         }
  264.         return $this;
  265.     }
  266.     /**
  267.      * Creates a new constraint formed by applying a logical OR to the
  268.      * existing constraint and the new one.
  269.      *
  270.      * Order of ands is important:
  271.      *
  272.      * Given $this->constraint = $constraint1
  273.      * running orWhere($constraint2)
  274.      * resulting constraint will be $constraint1 OR $constraint2
  275.      *
  276.      * If there is no previous constraint then it will simply store the
  277.      * provided one
  278.      *
  279.      * @param ConstraintInterface $constraint
  280.      *
  281.      * @return QueryBuilder This QueryBuilder instance.
  282.      */
  283.     public function orWhere(ConstraintInterface $constraint)
  284.     {
  285.         $this->state self::STATE_DIRTY;
  286.         if ($this->constraint) {
  287.             $this->constraint $this->qomFactory->orConstraint($this->constraint$constraint);
  288.         } else {
  289.             $this->constraint $constraint;
  290.         }
  291.         return $this;
  292.     }
  293.     /**
  294.      * Returns the columns to be selected.
  295.      *
  296.      * @return ColumnInterface[] The columns to be selected
  297.      */
  298.     public function getColumns()
  299.     {
  300.         return $this->columns;
  301.     }
  302.     /**
  303.      * Sets the columns to be selected.
  304.      *
  305.      * @param ColumnInterface[] $columns The columns to be selected
  306.      *
  307.      * @return QueryBuilder This QueryBuilder instance.
  308.      */
  309.     public function setColumns(array $columns)
  310.     {
  311.         $this->columns $columns;
  312.         return $this;
  313.     }
  314.     /**
  315.      * Identifies a property in the specified or default selector to include in the tabular view of query results.
  316.      * Replaces any previously specified columns to be selected if any.
  317.      *
  318.      * @param string $selectorName
  319.      * @param string $propertyName
  320.      * @param string $columnName
  321.      *
  322.      * @return QueryBuilder This QueryBuilder instance.
  323.      */
  324.     public function select($selectorName$propertyName$columnName null)
  325.     {
  326.         $this->state self::STATE_DIRTY;
  327.         $this->columns = [$this->qomFactory->column($selectorName$propertyName$columnName)];
  328.         return $this;
  329.     }
  330.     /**
  331.      * Adds a property in the specified or default selector to include in the tabular view of query results.
  332.      *
  333.      * @param string $selectorName
  334.      * @param string $propertyName
  335.      * @param string $columnName
  336.      *
  337.      * @return QueryBuilder This QueryBuilder instance.
  338.      */
  339.     public function addSelect($selectorName$propertyName$columnName null)
  340.     {
  341.         $this->state self::STATE_DIRTY;
  342.         $this->columns[] = $this->qomFactory->column($selectorName$propertyName$columnName);
  343.         return $this;
  344.     }
  345.     /**
  346.      * Sets the default Selector or the node-tuple Source. Can be a selector
  347.      * or a join.
  348.      *
  349.      * @param SourceInterface $source
  350.      *
  351.      * @return QueryBuilder This QueryBuilder instance.
  352.      */
  353.     public function from(SourceInterface $source)
  354.     {
  355.         $this->state self::STATE_DIRTY;
  356.         $this->source $source;
  357.         return $this;
  358.     }
  359.     /**
  360.      * Gets the default Selector.
  361.      *
  362.      * @return SourceInterface The default selector.
  363.      */
  364.     public function getSource()
  365.     {
  366.         return $this->source;
  367.     }
  368.     /**
  369.      * Performs an inner join between the stored source and the supplied source.
  370.      *
  371.      * @param SourceInterface        $rightSource
  372.      * @param JoinConditionInterface $joinCondition
  373.      *
  374.      * @throws RuntimeException if there is not an existing source.
  375.      *
  376.      * @return QueryBuilder This QueryBuilder instance.
  377.      */
  378.     public function join(SourceInterface $rightSourceJoinConditionInterface $joinCondition)
  379.     {
  380.         return $this->innerJoin($rightSource$joinCondition);
  381.     }
  382.     /**
  383.      * Performs an inner join between the stored source and the supplied source.
  384.      *
  385.      * @param SourceInterface        $rightSource
  386.      * @param JoinConditionInterface $joinCondition
  387.      *
  388.      * @throws RuntimeException if there is not an existing source.
  389.      *
  390.      * @return QueryBuilder This QueryBuilder instance.
  391.      */
  392.     public function innerJoin(SourceInterface $rightSourceJoinConditionInterface $joinCondition)
  393.     {
  394.         return $this->joinWithType($rightSourceQueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER$joinCondition);
  395.     }
  396.     /**
  397.      * Performs an left outer join between the stored source and the supplied source.
  398.      *
  399.      * @param SourceInterface        $rightSource
  400.      * @param JoinConditionInterface $joinCondition
  401.      *
  402.      * @throws RuntimeException if there is not an existing source.
  403.      *
  404.      * @return QueryBuilder This QueryBuilder instance.
  405.      */
  406.     public function leftJoin(SourceInterface $rightSourceJoinConditionInterface $joinCondition)
  407.     {
  408.         return $this->joinWithType($rightSourceQueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER$joinCondition);
  409.     }
  410.     /**
  411.      * Performs a right outer join between the stored source and the supplied source.
  412.      *
  413.      * @param SourceInterface        $rightSource
  414.      * @param JoinConditionInterface $joinCondition
  415.      *
  416.      * @throws RuntimeException if there is not an existing source.
  417.      *
  418.      * @return QueryBuilder This QueryBuilder instance.
  419.      */
  420.     public function rightJoin(SourceInterface $rightSourceJoinConditionInterface $joinCondition)
  421.     {
  422.         return $this->joinWithType($rightSourceQueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER$joinCondition);
  423.     }
  424.     /**
  425.      * Performs an join between the stored source and the supplied source.
  426.      *
  427.      * @param SourceInterface        $rightSource
  428.      * @param string                 $joinType      as specified in PHPCR\Query\QOM\QueryObjectModelConstantsInterface
  429.      * @param JoinConditionInterface $joinCondition
  430.      *
  431.      * @throws RuntimeException if there is not an existing source.
  432.      *
  433.      * @return QueryBuilder This QueryBuilder instance.
  434.      */
  435.     public function joinWithType(SourceInterface $rightSource$joinTypeJoinConditionInterface $joinCondition)
  436.     {
  437.         if (!$this->source) {
  438.             throw new RuntimeException('Cannot perform a join without a previous call to from');
  439.         }
  440.         $this->state self::STATE_DIRTY;
  441.         $this->source $this->qomFactory->join($this->source$rightSource$joinType$joinCondition);
  442.         return $this;
  443.     }
  444.     /**
  445.      * Gets the query built.
  446.      *
  447.      * @return QueryObjectModelInterface
  448.      */
  449.     public function getQuery()
  450.     {
  451.         if ($this->query !== null && $this->state === self::STATE_CLEAN) {
  452.             return $this->query;
  453.         }
  454.         $this->state self::STATE_CLEAN;
  455.         $this->query $this->qomFactory->createQuery($this->source$this->constraint$this->orderings$this->columns);
  456.         if ($this->firstResult) {
  457.             $this->query->setOffset($this->firstResult);
  458.         }
  459.         if ($this->maxResults) {
  460.             $this->query->setLimit($this->maxResults);
  461.         }
  462.         return $this->query;
  463.     }
  464.     /**
  465.      * Executes the query setting firstResult and maxResults.
  466.      *
  467.      * @return QueryResultInterface
  468.      */
  469.     public function execute()
  470.     {
  471.         if ($this->query === null || $this->state === self::STATE_DIRTY) {
  472.             $this->query $this->getQuery();
  473.         }
  474.         foreach ($this->params as $key => $value) {
  475.             $this->query->bindValue($key$value);
  476.         }
  477.         return $this->query->execute();
  478.     }
  479.     /**
  480.      * Sets a query parameter for the query being constructed.
  481.      *
  482.      * @param string $key   The parameter name.
  483.      * @param mixed  $value The parameter value.
  484.      *
  485.      * @return QueryBuilder This QueryBuilder instance.
  486.      */
  487.     public function setParameter($key$value)
  488.     {
  489.         $this->params[$key] = $value;
  490.         return $this;
  491.     }
  492.     /**
  493.      * Gets a (previously set) query parameter of the query being constructed.
  494.      *
  495.      * @param string $key The key (name) of the bound parameter.
  496.      *
  497.      * @return mixed The value of the bound parameter.
  498.      */
  499.     public function getParameter($key)
  500.     {
  501.         return isset($this->params[$key]) ? $this->params[$key] : null;
  502.     }
  503.     /**
  504.      * Sets a collection of query parameters for the query being constructed.
  505.      *
  506.      * @param array $params The query parameters to set.
  507.      *
  508.      * @return QueryBuilder This QueryBuilder instance.
  509.      */
  510.     public function setParameters(array $params)
  511.     {
  512.         $this->params $params;
  513.         return $this;
  514.     }
  515.     /**
  516.      * Gets all defined query parameters for the query being constructed.
  517.      *
  518.      * @return array The currently defined query parameters.
  519.      */
  520.     public function getParameters()
  521.     {
  522.         return $this->params;
  523.     }
  524. }