c5ff08e0560c8047ade8a0d5ee362f6de320155a
[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 } else {
211 return '';
212 }
213 }
214
215 /**
216 * Gets the node-tuple source for this query.
217 *
218 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface the node-tuple source; non-null
219 */
220 public function getSource()
221 {
222 if ($this->source === null) {
223 $this->source = $this->qomFactory->selector($this->getType(), $this->dataMapper->convertClassNameToTableName($this->getType()));
224 }
225 return $this->source;
226 }
227
228 /**
229 * Executes the query against the database and returns the result
230 *
231 * @param bool $returnRawQueryResult avoids the object mapping by the persistence
232 * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array The query result object or an array if $returnRawQueryResult is TRUE
233 * @api
234 */
235 public function execute($returnRawQueryResult = false)
236 {
237 if ($returnRawQueryResult) {
238 return $this->persistenceManager->getObjectDataByQuery($this);
239 } else {
240 return $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\QueryResultInterface::class, $this);
241 }
242 }
243
244 /**
245 * Sets the property names to order the result by. Expected like this:
246 * array(
247 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
248 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
249 * )
250 * where 'foo' and 'bar' are property names.
251 *
252 * @param array $orderings The property names to order by
253 * @return QueryInterface
254 * @api
255 */
256 public function setOrderings(array $orderings)
257 {
258 $this->orderings = $orderings;
259 return $this;
260 }
261
262 /**
263 * Returns the property names to order the result by. Like this:
264 * array(
265 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
266 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
267 * )
268 *
269 * @return array
270 * @api
271 */
272 public function getOrderings()
273 {
274 return $this->orderings;
275 }
276
277 /**
278 * Sets the maximum size of the result set to limit. Returns $this to allow
279 * for chaining (fluid interface)
280 *
281 * @param int $limit
282 * @throws \InvalidArgumentException
283 * @return QueryInterface
284 * @api
285 */
286 public function setLimit($limit)
287 {
288 if (!is_int($limit) || $limit < 1) {
289 throw new \InvalidArgumentException('The limit must be an integer >= 1', 1245071870);
290 }
291 $this->limit = $limit;
292 return $this;
293 }
294
295 /**
296 * Resets a previously set maximum size of the result set. Returns $this to allow
297 * for chaining (fluid interface)
298 *
299 * @return QueryInterface
300 * @api
301 */
302 public function unsetLimit()
303 {
304 unset($this->limit);
305 return $this;
306 }
307
308 /**
309 * Returns the maximum size of the result set to limit.
310 *
311 * @return int
312 * @api
313 */
314 public function getLimit()
315 {
316 return $this->limit;
317 }
318
319 /**
320 * Sets the start offset of the result set to offset. Returns $this to
321 * allow for chaining (fluid interface)
322 *
323 * @param int $offset
324 * @throws \InvalidArgumentException
325 * @return QueryInterface
326 * @api
327 */
328 public function setOffset($offset)
329 {
330 if (!is_int($offset) || $offset < 0) {
331 throw new \InvalidArgumentException('The offset must be a positive integer', 1245071872);
332 }
333 $this->offset = $offset;
334 return $this;
335 }
336
337 /**
338 * Returns the start offset of the result set.
339 *
340 * @return int
341 * @api
342 */
343 public function getOffset()
344 {
345 return $this->offset;
346 }
347
348 /**
349 * The constraint used to limit the result set. Returns $this to allow
350 * for chaining (fluid interface)
351 *
352 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint
353 * @return QueryInterface
354 * @api
355 */
356 public function matching($constraint)
357 {
358 $this->constraint = $constraint;
359 return $this;
360 }
361
362 /**
363 * Sets the statement of this query. If you use this, you will lose the abstraction from a concrete storage
364 * backend (database).
365 *
366 * @param string|\TYPO3\CMS\Core\Database\Query\QueryBuilder|\Doctrine\DBAL\Statement $statement The statement
367 * @param array $parameters An array of parameters. These will be bound to placeholders '?' in the $statement.
368 * @return QueryInterface
369 */
370 public function statement($statement, array $parameters = [])
371 {
372 $this->statement = $this->qomFactory->statement($statement, $parameters);
373 return $this;
374 }
375
376 /**
377 * Returns the statement of this query.
378 *
379 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement
380 */
381 public function getStatement()
382 {
383 return $this->statement;
384 }
385
386 /**
387 * Gets the constraint for this query.
388 *
389 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface|NULL the constraint, or null if none
390 * @api
391 */
392 public function getConstraint()
393 {
394 return $this->constraint;
395 }
396
397 /**
398 * Performs a logical conjunction of the given constraints. The method takes one or more constraints and concatenates them with a boolean AND.
399 * It also accepts a single array of constraints to be concatenated.
400 *
401 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
402 * @throws Exception\InvalidNumberOfConstraintsException
403 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
404 * @api
405 */
406 public function logicalAnd($constraint1)
407 {
408 if (is_array($constraint1)) {
409 $resultingConstraint = array_shift($constraint1);
410 $constraints = $constraint1;
411 } else {
412 $constraints = func_get_args();
413 $resultingConstraint = array_shift($constraints);
414 }
415 if ($resultingConstraint === null) {
416 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);
417 }
418 foreach ($constraints as $constraint) {
419 $resultingConstraint = $this->qomFactory->_and($resultingConstraint, $constraint);
420 }
421 return $resultingConstraint;
422 }
423
424 /**
425 * Performs a logical disjunction of the two given constraints
426 *
427 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
428 * @throws Exception\InvalidNumberOfConstraintsException
429 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface
430 * @api
431 */
432 public function logicalOr($constraint1)
433 {
434 if (is_array($constraint1)) {
435 $resultingConstraint = array_shift($constraint1);
436 $constraints = $constraint1;
437 } else {
438 $constraints = func_get_args();
439 $resultingConstraint = array_shift($constraints);
440 }
441 if ($resultingConstraint === null) {
442 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);
443 }
444 foreach ($constraints as $constraint) {
445 $resultingConstraint = $this->qomFactory->_or($resultingConstraint, $constraint);
446 }
447 return $resultingConstraint;
448 }
449
450 /**
451 * Performs a logical negation of the given constraint
452 *
453 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint Constraint to negate
454 * @throws \RuntimeException
455 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface
456 * @api
457 */
458 public function logicalNot(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint)
459 {
460 return $this->qomFactory->not($constraint);
461 }
462
463 /**
464 * Returns an equals criterion used for matching objects against a query
465 *
466 * @param string $propertyName The name of the property to compare against
467 * @param mixed $operand The value to compare with
468 * @param bool $caseSensitive Whether the equality test should be done case-sensitive
469 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
470 * @api
471 */
472 public function equals($propertyName, $operand, $caseSensitive = true)
473 {
474 if (is_object($operand) || $caseSensitive) {
475 $comparison = $this->qomFactory->comparison(
476 $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
477 QueryInterface::OPERATOR_EQUAL_TO,
478 $operand
479 );
480 } else {
481 $comparison = $this->qomFactory->comparison(
482 $this->qomFactory->lowerCase($this->qomFactory->propertyValue($propertyName, $this->getSelectorName())),
483 QueryInterface::OPERATOR_EQUAL_TO,
484 mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET)
485 );
486 }
487 return $comparison;
488 }
489
490 /**
491 * Returns a like criterion used for matching objects against a query
492 *
493 * @param string $propertyName The name of the property to compare against
494 * @param mixed $operand The value to compare with
495 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
496 * @api
497 */
498 public function like($propertyName, $operand)
499 {
500 return $this->qomFactory->comparison(
501 $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
502 QueryInterface::OPERATOR_LIKE,
503 $operand
504 );
505 }
506
507 /**
508 * Returns a "contains" criterion used for matching objects against a query.
509 * It matches if the multivalued property contains the given operand.
510 *
511 * @param string $propertyName The name of the (multivalued) property to compare against
512 * @param mixed $operand The value to compare with
513 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
514 * @api
515 */
516 public function contains($propertyName, $operand)
517 {
518 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_CONTAINS, $operand);
519 }
520
521 /**
522 * Returns an "in" criterion used for matching objects against a query. It
523 * matches if the property's value is contained in the multivalued operand.
524 *
525 * @param string $propertyName The name of the property to compare against
526 * @param mixed $operand The value to compare with, multivalued
527 * @throws Exception\UnexpectedTypeException
528 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
529 * @api
530 */
531 public function in($propertyName, $operand)
532 {
533 if (!\TYPO3\CMS\Extbase\Utility\TypeHandlingUtility::isValidTypeForMultiValueComparison($operand)) {
534 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('The "in" operator must be given a multivalued operand (array, ArrayAccess, Traversable).', 1264678095);
535 }
536 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_IN, $operand);
537 }
538
539 /**
540 * Returns a less than criterion used for matching objects against a query
541 *
542 * @param string $propertyName The name of the property to compare against
543 * @param mixed $operand The value to compare with
544 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
545 * @api
546 */
547 public function lessThan($propertyName, $operand)
548 {
549 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN, $operand);
550 }
551
552 /**
553 * Returns a less or equal than criterion used for matching objects against a query
554 *
555 * @param string $propertyName The name of the property to compare against
556 * @param mixed $operand The value to compare with
557 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
558 * @api
559 */
560 public function lessThanOrEqual($propertyName, $operand)
561 {
562 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO, $operand);
563 }
564
565 /**
566 * Returns a greater than criterion used for matching objects against a query
567 *
568 * @param string $propertyName The name of the property to compare against
569 * @param mixed $operand The value to compare with
570 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
571 * @api
572 */
573 public function greaterThan($propertyName, $operand)
574 {
575 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN, $operand);
576 }
577
578 /**
579 * Returns a greater than or equal criterion used for matching objects against a query
580 *
581 * @param string $propertyName The name of the property to compare against
582 * @param mixed $operand The value to compare with
583 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
584 * @api
585 */
586 public function greaterThanOrEqual($propertyName, $operand)
587 {
588 return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $operand);
589 }
590
591 /**
592 * Returns a greater than or equal criterion used for matching objects against a query
593 *
594 * @param string $propertyName The name of the property to compare against
595 * @param mixed $operandLower The value of the lower boundary to compare against
596 * @param mixed $operandUpper The value of the upper boundary to compare against
597 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
598 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException
599 * @api
600 */
601 public function between($propertyName, $operandLower, $operandUpper)
602 {
603 return $this->logicalAnd(
604 $this->greaterThanOrEqual($propertyName, $operandLower),
605 $this->lessThanOrEqual($propertyName, $operandUpper)
606 );
607 }
608
609 /**
610 */
611 public function __wakeup()
612 {
613 $this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
614 $this->persistenceManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface::class);
615 $this->dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
616 $this->qomFactory = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class);
617 }
618
619 /**
620 * @return array
621 */
622 public function __sleep()
623 {
624 return ['type', 'source', 'constraint', 'statement', 'orderings', 'limit', 'offset', 'querySettings'];
625 }
626
627 /**
628 * Returns the query result count.
629 *
630 * @return int The query result count
631 * @api
632 */
633 public function count()
634 {
635 return $this->execute()->count();
636 }
637
638 /**
639 * Returns an "isEmpty" criterion used for matching objects against a query.
640 * It matches if the multivalued property contains no values or is NULL.
641 *
642 * @param string $propertyName The name of the multivalued property to compare against
643 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\NotImplementedException
644 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException if used on a single-valued property
645 * @return bool
646 * @api
647 */
648 public function isEmpty($propertyName)
649 {
650 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\NotImplementedException(__METHOD__, 1476122265);
651 }
652 }