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