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