fdf6f1bf66ca7890fe6b775b3fe0ed3c0bcd54bc
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Query.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
18
19 /**
20 * The Query class used to run queries against the database
21 *
22 * @api
23 */
24 class Query implements QueryInterface
25 {
26 /**
27 * An inner join.
28 */
29 const JCR_JOIN_TYPE_INNER = '{http://www.jcp.org/jcr/1.0}joinTypeInner';
30
31 /**
32 * A left-outer join.
33 */
34 const JCR_JOIN_TYPE_LEFT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeLeftOuter';
35
36 /**
37 * A right-outer join.
38 */
39 const JCR_JOIN_TYPE_RIGHT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeRightOuter';
40
41 /**
42 * Charset of strings in QOM
43 */
44 const CHARSET = 'utf-8';
45
46 /**
47 * @var string
48 */
49 protected $type;
50
51 /**
52 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
53 */
54 protected $objectManager;
55
56 /**
57 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
58 */
59 protected $dataMapper;
60
61 /**
62 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
63
64 */
65 protected $persistenceManager;
66
67 /**
68 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
69 */
70 protected $qomFactory;
71
72 /**
73 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface
74 */
75 protected $source;
76
77 /**
78 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface
79 */
80 protected $constraint;
81
82 /**
83 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement
84 */
85 protected $statement;
86
87 /**
88 * @var int
89 */
90 protected $orderings = [];
91
92 /**
93 * @var int
94 */
95 protected $limit;
96
97 /**
98 * @var int
99 */
100 protected $offset;
101
102 /**
103 * The query settings.
104 *
105 * @var QuerySettingsInterface
106 */
107 protected $querySettings;
108
109 /**
110 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
111 */
112 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
113 {
114 $this->objectManager = $objectManager;
115 }
116
117 /**
118 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
119 */
120 public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
121 {
122 $this->dataMapper = $dataMapper;
123 }
124
125 /**
126 * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
127 */
128 public function injectPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager)
129 {
130 $this->persistenceManager = $persistenceManager;
131 }
132
133 /**
134 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
135 */
136 public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
137 {
138 $this->qomFactory = $qomFactory;
139 }
140
141 /**
142 * Constructs a query object working on the given class name
143 *
144 * @param string $type
145 */
146 public function __construct($type)
147 {
148 $this->type = $type;
149 }
150
151 /**
152 * Sets the Query Settings. These Query settings must match the settings expected by
153 * the specific Storage Backend.
154 *
155 * @param QuerySettingsInterface $querySettings The Query Settings
156 * @api This method is not part of TYPO3.Flow API
157 */
158 public function setQuerySettings(QuerySettingsInterface $querySettings)
159 {
160 $this->querySettings = $querySettings;
161 }
162
163 /**
164 * Returns the Query Settings.
165 *
166 * @throws Exception
167 * @return QuerySettingsInterface $querySettings The Query Settings
168 * @api This method is not part of TYPO3.Flow API
169 */
170 public function getQuerySettings()
171 {
172 if (!$this->querySettings instanceof QuerySettingsInterface) {
173 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Tried to get the query settings without seting them before.', 1248689115);
174 }
175 return $this->querySettings;
176 }
177
178 /**
179 * Returns the type this query cares for.
180 *
181 * @return string
182 * @api
183 */
184 public function getType()
185 {
186 return $this->type;
187 }
188
189 /**
190 * Sets the source to fetch the result from
191 *
192 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source
193 */
194 public function setSource(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source)
195 {
196 $this->source = $source;
197 }
198
199 /**
200 * Returns the selectorn name or an empty string, if the source is not a selector
201 * @todo This has to be checked at another place
202 *
203 * @return string The selector name
204 */
205 protected function getSelectorName()
206 {
207 $source = $this->getSource();
208 if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
209 return $source->getSelectorName();
210 }
211 return '';
212 }
213
214 /**
215 * Gets the node-tuple source for this query.
216 *
217 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface the node-tuple source; non-null
218 */
219 public function getSource()
220 {
221 if ($this->source === null) {
222 $this->source = $this->qomFactory->selector($this->getType(), $this->dataMapper->convertClassNameToTableName($this->getType()));
223 }
224 return $this->source;
225 }
226
227 /**
228 * Executes the query against the database and returns the result
229 *
230 * @param bool $returnRawQueryResult avoids the object mapping by the persistence
231 * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array The query result object or an array if $returnRawQueryResult is TRUE
232 * @api
233 */
234 public function execute($returnRawQueryResult = false)
235 {
236 if ($returnRawQueryResult) {
237 return $this->persistenceManager->getObjectDataByQuery($this);
238 }
239 return $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\QueryResultInterface::class, $this);
240 }
241
242 /**
243 * Sets the property names to order the result by. Expected like this:
244 * array(
245 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
246 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
247 * )
248 * where 'foo' and 'bar' are property names.
249 *
250 * @param array $orderings The property names to order by
251 * @return QueryInterface
252 * @api
253 */
254 public function setOrderings(array $orderings)
255 {
256 $this->orderings = $orderings;
257 return $this;
258 }
259
260 /**
261 * Returns the property names to order the result by. Like this:
262 * array(
263 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
264 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
265 * )
266 *
267 * @return array
268 * @api
269 */
270 public function getOrderings()
271 {
272 return $this->orderings;
273 }
274
275 /**
276 * Sets the maximum size of the result set to limit. Returns $this to allow
277 * for chaining (fluid interface)
278 *
279 * @param int $limit
280 * @throws \InvalidArgumentException
281 * @return QueryInterface
282 * @api
283 */
284 public function setLimit($limit)
285 {
286 if (!is_int($limit) || $limit < 1) {
287 throw new \InvalidArgumentException('The limit must be an integer >= 1', 1245071870);
288 }
289 $this->limit = $limit;
290 return $this;
291 }
292
293 /**
294 * Resets a previously set maximum size of the result set. Returns $this to allow
295 * for chaining (fluid interface)
296 *
297 * @return QueryInterface
298 * @api
299 */
300 public function unsetLimit()
301 {
302 unset($this->limit);
303 return $this;
304 }
305
306 /**
307 * Returns the maximum size of the result set to limit.
308 *
309 * @return int
310 * @api
311 */
312 public function getLimit()
313 {
314 return $this->limit;
315 }
316
317 /**
318 * Sets the start offset of the result set to offset. Returns $this to
319 * allow for chaining (fluid interface)
320 *
321 * @param int $offset
322 * @throws \InvalidArgumentException
323 * @return QueryInterface
324 * @api
325 */
326 public function setOffset($offset)
327 {
328 if (!is_int($offset) || $offset < 0) {
329 throw new \InvalidArgumentException('The offset must be a positive integer', 1245071872);
330 }
331 $this->offset = $offset;
332 return $this;
333 }
334
335 /**
336 * Returns the start offset of the result set.
337 *
338 * @return int
339 * @api
340 */
341 public function getOffset()
342 {
343 return $this->offset;
344 }
345
346 /**
347 * The constraint used to limit the result set. Returns $this to allow
348 * for chaining (fluid interface)
349 *
350 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint
351 * @return QueryInterface
352 * @api
353 */
354 public function matching($constraint)
355 {
356 $this->constraint = $constraint;
357 return $this;
358 }
359
360 /**
361 * Sets the statement of this query. If you use this, you will lose the abstraction from a concrete storage
362 * backend (database).
363 *
364 * @param string|\TYPO3\CMS\Core\Database\Query\QueryBuilder|\Doctrine\DBAL\Statement $statement The statement
365 * @param array $parameters An array of parameters. These will be bound to placeholders '?' in the $statement.
366 * @return QueryInterface
367 */
368 public function statement($statement, array $parameters = [])
369 {
370 $this->statement = $this->qomFactory->statement($statement, $parameters);
371 return $this;
372 }
373
374 /**
375 * Returns the statement of this query.
376 *
377 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement
378 */
379 public function getStatement()
380 {
381 return $this->statement;
382 }
383
384 /**
385 * Gets the constraint for this query.
386 *
387 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface|NULL the constraint, or null if none
388 * @api
389 */
390 public function getConstraint()
391 {
392 return $this->constraint;
393 }
394
395 /**
396 * Performs a logical conjunction of the given constraints. The method takes one or more constraints and concatenates them with a boolean AND.
397 * It also accepts a single array of constraints to be concatenated.
398 *
399 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
400 * @throws Exception\InvalidNumberOfConstraintsException
401 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
402 * @api
403 */
404 public function logicalAnd($constraint1)
405 {
406 if (is_array($constraint1)) {
407 $resultingConstraint = array_shift($constraint1);
408 $constraints = $constraint1;
409 } else {
410 $constraints = func_get_args();
411 $resultingConstraint = array_shift($constraints);
412 }
413 if ($resultingConstraint === null) {
414 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1268056288);
415 }
416 foreach ($constraints as $constraint) {
417 $resultingConstraint = $this->qomFactory->_and($resultingConstraint, $constraint);
418 }
419 return $resultingConstraint;
420 }
421
422 /**
423 * Performs a logical disjunction of the two given constraints
424 *
425 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
426 * @throws Exception\InvalidNumberOfConstraintsException
427 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface
428 * @api
429 */
430 public function logicalOr($constraint1)
431 {
432 if (is_array($constraint1)) {
433 $resultingConstraint = array_shift($constraint1);
434 $constraints = $constraint1;
435 } else {
436 $constraints = func_get_args();
437 $resultingConstraint = array_shift($constraints);
438 }
439 if ($resultingConstraint === null) {
440 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1268056289);
441 }
442 foreach ($constraints as $constraint) {
443 $resultingConstraint = $this->qomFactory->_or($resultingConstraint, $constraint);
444 }
445 return $resultingConstraint;
446 }
447
448 /**
449 * Performs a logical negation of the given constraint
450 *
451 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint Constraint to negate
452 * @throws \RuntimeException
453 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface
454 * @api
455 */
456 public function logicalNot(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint)
457 {
458 return $this->qomFactory->not($constraint);
459 }
460
461 /**
462 * Returns an equals criterion used for matching objects against a query
463 *
464 * @param string $propertyName The name of the property to compare against
465 * @param mixed $operand The value to compare with
466 * @param bool $caseSensitive Whether the equality test should be done case-sensitive
467 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
468 * @api
469 */
470 public function equals($propertyName, $operand, $caseSensitive = true)
471 {
472 if (is_object($operand) || $caseSensitive) {
473 $comparison = $this->qomFactory->comparison(
474 $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
475 QueryInterface::OPERATOR_EQUAL_TO,
476 $operand
477 );
478 } else {
479 $comparison = $this->qomFactory->comparison(
480 $this->qomFactory->lowerCase($this->qomFactory->propertyValue($propertyName, $this->getSelectorName())),
481 QueryInterface::OPERATOR_EQUAL_TO,
482 mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET)
483 );
484 }
485 return $comparison;
486 }
487
488 /**
489 * Returns a like criterion used for matching objects against a query
490 *
491 * @param string $propertyName The name of the property to compare against
492 * @param mixed $operand The value to compare with
493 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
494 * @api
495 */
496 public function like($propertyName, $operand)
497 {
498 return $this->qomFactory->comparison(
499 $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
500 QueryInterface::OPERATOR_LIKE,
501 $operand
502 );
503 }
504
505 /**
506 * Returns a "contains" criterion used for matching objects against a query.
507 * It matches if the multivalued property contains the given operand.
508 *
509 * @param string $propertyName The name of the (multivalued) property to compare against
510 * @param mixed $operand The value to compare with
511 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
512 * @api
513 */
514 public function contains($propertyName, $operand)
515 {
516 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_CONTAINS, $operand);
517 }
518
519 /**
520 * Returns an "in" criterion used for matching objects against a query. It
521 * matches if the property's value is contained in the multivalued operand.
522 *
523 * @param string $propertyName The name of the property to compare against
524 * @param mixed $operand The value to compare with, multivalued
525 * @throws Exception\UnexpectedTypeException
526 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
527 * @api
528 */
529 public function in($propertyName, $operand)
530 {
531 if (!\TYPO3\CMS\Extbase\Utility\TypeHandlingUtility::isValidTypeForMultiValueComparison($operand)) {
532 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('The "in" operator must be given a multivalued operand (array, ArrayAccess, Traversable).', 1264678095);
533 }
534 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_IN, $operand);
535 }
536
537 /**
538 * Returns a less than criterion used for matching objects against a query
539 *
540 * @param string $propertyName The name of the property to compare against
541 * @param mixed $operand The value to compare with
542 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
543 * @api
544 */
545 public function lessThan($propertyName, $operand)
546 {
547 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN, $operand);
548 }
549
550 /**
551 * Returns a less or equal than criterion used for matching objects against a query
552 *
553 * @param string $propertyName The name of the property to compare against
554 * @param mixed $operand The value to compare with
555 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
556 * @api
557 */
558 public function lessThanOrEqual($propertyName, $operand)
559 {
560 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO, $operand);
561 }
562
563 /**
564 * Returns a greater than criterion used for matching objects against a query
565 *
566 * @param string $propertyName The name of the property to compare against
567 * @param mixed $operand The value to compare with
568 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
569 * @api
570 */
571 public function greaterThan($propertyName, $operand)
572 {
573 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN, $operand);
574 }
575
576 /**
577 * Returns a greater than or equal criterion used for matching objects against a query
578 *
579 * @param string $propertyName The name of the property to compare against
580 * @param mixed $operand The value to compare with
581 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
582 * @api
583 */
584 public function greaterThanOrEqual($propertyName, $operand)
585 {
586 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $operand);
587 }
588
589 /**
590 * Returns a greater than or equal criterion used for matching objects against a query
591 *
592 * @param string $propertyName The name of the property to compare against
593 * @param mixed $operandLower The value of the lower boundary to compare against
594 * @param mixed $operandUpper The value of the upper boundary to compare against
595 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
596 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException
597 * @api
598 */
599 public function between($propertyName, $operandLower, $operandUpper)
600 {
601 return $this->logicalAnd(
602 $this->greaterThanOrEqual($propertyName, $operandLower),
603 $this->lessThanOrEqual($propertyName, $operandUpper)
604 );
605 }
606
607 /**
608 */
609 public function __wakeup()
610 {
611 $this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
612 $this->persistenceManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface::class);
613 $this->dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
614 $this->qomFactory = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class);
615 }
616
617 /**
618 * @return array
619 */
620 public function __sleep()
621 {
622 return ['type', 'source', 'constraint', 'statement', 'orderings', 'limit', 'offset', 'querySettings'];
623 }
624
625 /**
626 * Returns the query result count.
627 *
628 * @return int The query result count
629 * @api
630 */
631 public function count()
632 {
633 return $this->execute()->count();
634 }
635
636 /**
637 * Returns an "isEmpty" criterion used for matching objects against a query.
638 * It matches if the multivalued property contains no values or is NULL.
639 *
640 * @param string $propertyName The name of the multivalued property to compare against
641 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\NotImplementedException
642 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException if used on a single-valued property
643 * @return bool
644 * @api
645 */
646 public function isEmpty($propertyName)
647 {
648 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\NotImplementedException(__METHOD__, 1476122265);
649 }
650 }