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

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 $rightSource, JoinConditionInterface $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 $rightSource, JoinConditionInterface $joinCondition)
  393. {
  394. return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::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 $rightSource, JoinConditionInterface $joinCondition)
  407. {
  408. return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::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 $rightSource, JoinConditionInterface $joinCondition)
  421. {
  422. return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::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, $joinType, JoinConditionInterface $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. }