Passed
Push — master ( 797014...bcb509 )
by
unknown
19:02
created

Query::setType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Extbase\Persistence\Generic;
17
18
use Psr\Container\ContainerInterface;
19
use TYPO3\CMS\Core\Utility\GeneralUtility;
20
use TYPO3\CMS\Extbase\Object\ObjectManager;
21
use TYPO3\CMS\Extbase\Persistence\ForwardCompatibleQueryInterface;
22
use TYPO3\CMS\Extbase\Persistence\ForwardCompatibleQueryResultInterface;
23
use TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException;
24
use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
25
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory;
26
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface;
27
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory;
28
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface;
29
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface;
30
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
31
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
32
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
33
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
34
35
/**
36
 * The Query class used to run queries against the database
37
 *
38
 * @todo v12: Drop ForwardCompatibleQueryInterface when merged into QueryInterface
39
 * @todo v12: Candidate to declare final - Can be decorated or standalone class implementing the interface
40
 */
41
class Query implements QueryInterface, ForwardCompatibleQueryInterface
42
{
43
    /**
44
     * An inner join.
45
     */
46
    const JCR_JOIN_TYPE_INNER = '{http://www.jcp.org/jcr/1.0}joinTypeInner';
47
48
    /**
49
     * A left-outer join.
50
     */
51
    const JCR_JOIN_TYPE_LEFT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeLeftOuter';
52
53
    /**
54
     * A right-outer join.
55
     */
56
    const JCR_JOIN_TYPE_RIGHT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeRightOuter';
57
58
    /**
59
     * Charset of strings in QOM
60
     */
61
    const CHARSET = 'utf-8';
62
63
    /**
64
     * @var string
65
     */
66
    protected $type;
67
68
    protected DataMapFactory $dataMapFactory;
69
    protected PersistenceManagerInterface $persistenceManager;
70
    protected QueryObjectModelFactory $qomFactory;
71
    protected ContainerInterface $container;
72
73
    /**
74
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface
75
     */
76
    protected $source;
77
78
    /**
79
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface
80
     */
81
    protected $constraint;
82
83
    /**
84
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Statement
85
     */
86
    protected $statement;
87
88
    /**
89
     * @var array<string, string>
90
     */
91
    protected $orderings = [];
92
93
    /**
94
     * @var int
95
     */
96
    protected $limit;
97
98
    /**
99
     * @var int
100
     */
101
    protected $offset;
102
103
    /**
104
     * The query settings.
105
     *
106
     * @var QuerySettingsInterface
107
     */
108
    protected $querySettings;
109
110
    /**
111
     * @var QueryInterface|null
112
     * @internal
113
     */
114
    protected $parentQuery;
115
116
    public function __construct(
117
        DataMapFactory $dataMapFactory,
118
        PersistenceManagerInterface $persistenceManager,
119
        QueryObjectModelFactory $qomFactory,
120
        ContainerInterface $container
121
    ) {
122
        $this->dataMapFactory = $dataMapFactory;
123
        $this->persistenceManager = $persistenceManager;
124
        $this->qomFactory = $qomFactory;
125
        $this->container = $container;
126
    }
127
128
    public function setType(string $type): void
129
    {
130
        $this->type = $type;
131
    }
132
133
    /**
134
     * @return ?QueryInterface
135
     * @internal
136
     */
137
    public function getParentQuery(): ?QueryInterface
138
    {
139
        return $this->parentQuery;
140
    }
141
142
    /**
143
     * @param ?QueryInterface $parentQuery
144
     * @internal
145
     */
146
    public function setParentQuery(?QueryInterface $parentQuery): void
147
    {
148
        $this->parentQuery = $parentQuery;
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
     */
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
     */
168
    public function getQuerySettings()
169
    {
170
        if (!$this->querySettings instanceof QuerySettingsInterface) {
0 ignored issues
show
introduced by
$this->querySettings is always a sub-type of TYPO3\CMS\Extbase\Persis...\QuerySettingsInterface.
Loading history...
171
            throw new Exception('Tried to get the query settings without setting them before.', 1248689115);
172
        }
173
        return $this->querySettings;
174
    }
175
176
    /**
177
     * Returns the type this query cares for.
178
     *
179
     * @return string
180
     */
181
    public function getType()
182
    {
183
        return $this->type;
184
    }
185
186
    /**
187
     * Sets the source to fetch the result from
188
     *
189
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source
190
     */
191
    public function setSource(SourceInterface $source)
192
    {
193
        $this->source = $source;
194
    }
195
196
    /**
197
     * Returns the selector's name or an empty string, if the source is not a selector
198
     * @todo This has to be checked at another place
199
     *
200
     * @return string The selector name
201
     */
202
    protected function getSelectorName()
203
    {
204
        $source = $this->getSource();
205
        if ($source instanceof SelectorInterface) {
206
            return $source->getSelectorName();
207
        }
208
        return '';
209
    }
210
211
    /**
212
     * Gets the node-tuple source for this query.
213
     *
214
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface the node-tuple source; non-null
215
     */
216
    public function getSource()
217
    {
218
        if ($this->source === null) {
219
            $this->source = $this->qomFactory->selector($this->getType(), $this->dataMapFactory->buildDataMap($this->getType())->getTableName());
220
        }
221
        return $this->source;
222
    }
223
224
    /**
225
     * Executes the query against the database and returns the result
226
     *
227
     * @param bool $returnRawQueryResult avoids the object mapping by the persistence
228
     * @return QueryResultInterface|array The query result object or an array if $returnRawQueryResult is TRUE
229
     */
230
    public function execute($returnRawQueryResult = false)
231
    {
232
        if ($returnRawQueryResult) {
233
            return $this->persistenceManager->getObjectDataByQuery($this);
234
        }
235
        if ($this->container->has(QueryResultInterface::class)) {
236
            /** @var QueryResultInterface $queryResult */
237
            $queryResult = $this->container->get(QueryResultInterface::class);
238
            if ($queryResult instanceof ForwardCompatibleQueryResultInterface) {
239
                $queryResult->setQuery($this);
240
                return $queryResult;
241
            }
242
        }
243
        // @deprecated since v11, will be removed in v12. Fallback to ObjectManager, drop together with ForwardCompatibleQueryResultInterface.
244
        /** @var QueryResultInterface $queryResult */
245
        $queryResult = GeneralUtility::makeInstance(ObjectManager::class)->get(QueryResultInterface::class, $this);
246
        return $queryResult;
247
    }
248
249
    /**
250
     * Sets the property names to order the result by. Expected like this:
251
     * array(
252
     * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
253
     * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
254
     * )
255
     * where 'foo' and 'bar' are property names.
256
     *
257
     * @param array $orderings The property names to order by
258
     * @return QueryInterface
259
     */
260
    public function setOrderings(array $orderings)
261
    {
262
        $this->orderings = $orderings;
263
        return $this;
264
    }
265
266
    /**
267
     * Returns the property names to order the result by. Like this:
268
     * array(
269
     * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
270
     * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
271
     * )
272
     *
273
     * @return array<string, string>
274
     */
275
    public function getOrderings()
276
    {
277
        return $this->orderings;
278
    }
279
280
    /**
281
     * Sets the maximum size of the result set to limit. Returns $this to allow
282
     * for chaining (fluid interface)
283
     *
284
     * @param int $limit
285
     * @throws \InvalidArgumentException
286
     * @return QueryInterface
287
     */
288
    public function setLimit($limit)
289
    {
290
        if (!is_int($limit) || $limit < 1) {
0 ignored issues
show
introduced by
The condition is_int($limit) is always true.
Loading history...
291
            throw new \InvalidArgumentException('The limit must be an integer >= 1', 1245071870);
292
        }
293
        $this->limit = $limit;
294
        return $this;
295
    }
296
297
    /**
298
     * Resets a previously set maximum size of the result set. Returns $this to allow
299
     * for chaining (fluid interface)
300
     *
301
     * @return QueryInterface
302
     */
303
    public function unsetLimit()
304
    {
305
        unset($this->limit);
306
        return $this;
307
    }
308
309
    /**
310
     * Returns the maximum size of the result set to limit.
311
     *
312
     * @return int
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
     */
327
    public function setOffset($offset)
328
    {
329
        if (!is_int($offset) || $offset < 0) {
0 ignored issues
show
introduced by
The condition is_int($offset) is always true.
Loading history...
330
            throw new \InvalidArgumentException('The offset must be a positive integer', 1245071872);
331
        }
332
        $this->offset = $offset;
333
        return $this;
334
    }
335
336
    /**
337
     * Returns the start offset of the result set.
338
     *
339
     * @return int
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
     */
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);
0 ignored issues
show
Bug introduced by
It seems like $statement can also be of type Doctrine\DBAL\Statement; however, parameter $statement of TYPO3\CMS\Extbase\Persis...delFactory::statement() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

369
        $this->statement = $this->qomFactory->statement(/** @scrutinizer ignore-type */ $statement, $parameters);
Loading history...
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
     */
388
    public function getConstraint()
389
    {
390
        return $this->constraint;
391
    }
392
393
    /**
394
     * Performs a logical conjunction of the given constraints. The method takes one or more constraints and concatenates them with a boolean AND.
395
     * It also accepts a single array of constraints to be concatenated.
396
     *
397
     * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
398
     * @throws Exception\InvalidNumberOfConstraintsException
399
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
400
     */
401
    public function logicalAnd($constraint1)
402
    {
403
        if (is_array($constraint1)) {
404
            $resultingConstraint = array_shift($constraint1);
405
            $constraints = $constraint1;
406
        } else {
407
            $constraints = func_get_args();
408
            $resultingConstraint = array_shift($constraints);
409
        }
410
        if ($resultingConstraint === null) {
411
            throw new InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1268056288);
412
        }
413
        foreach ($constraints as $constraint) {
414
            $resultingConstraint = $this->qomFactory->_and($resultingConstraint, $constraint);
415
        }
416
        return $resultingConstraint;
417
    }
418
419
    /**
420
     * Performs a logical disjunction of the two given constraints
421
     *
422
     * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
423
     * @throws Exception\InvalidNumberOfConstraintsException
424
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface
425
     */
426
    public function logicalOr($constraint1)
427
    {
428
        if (is_array($constraint1)) {
429
            $resultingConstraint = array_shift($constraint1);
430
            $constraints = $constraint1;
431
        } else {
432
            $constraints = func_get_args();
433
            $resultingConstraint = array_shift($constraints);
434
        }
435
        if ($resultingConstraint === null) {
436
            throw new InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1268056289);
437
        }
438
        foreach ($constraints as $constraint) {
439
            $resultingConstraint = $this->qomFactory->_or($resultingConstraint, $constraint);
440
        }
441
        return $resultingConstraint;
442
    }
443
444
    /**
445
     * Performs a logical negation of the given constraint
446
     *
447
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface $constraint Constraint to negate
448
     * @throws \RuntimeException
449
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface
450
     */
451
    public function logicalNot(ConstraintInterface $constraint)
452
    {
453
        return $this->qomFactory->not($constraint);
454
    }
455
456
    /**
457
     * Returns an equals criterion used for matching objects against a query
458
     *
459
     * @param string $propertyName The name of the property to compare against
460
     * @param mixed $operand The value to compare with
461
     * @param bool $caseSensitive Whether the equality test should be done case-sensitive
462
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
463
     */
464
    public function equals($propertyName, $operand, $caseSensitive = true)
465
    {
466
        if (is_object($operand) || $caseSensitive) {
467
            $comparison = $this->qomFactory->comparison(
468
                $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
469
                QueryInterface::OPERATOR_EQUAL_TO,
470
                $operand
471
            );
472
        } else {
473
            $comparison = $this->qomFactory->comparison(
474
                $this->qomFactory->lowerCase($this->qomFactory->propertyValue($propertyName, $this->getSelectorName())),
475
                QueryInterface::OPERATOR_EQUAL_TO,
476
                mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET)
0 ignored issues
show
Bug introduced by
mb_strtolower($operand, ...Generic\Query::CHARSET) of type string is incompatible with the type TYPO3\CMS\Extbase\Persis...\StaticOperandInterface expected by parameter $operand2 of TYPO3\CMS\Extbase\Persis...elFactory::comparison(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

476
                /** @scrutinizer ignore-type */ mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET)
Loading history...
477
            );
478
        }
479
        return $comparison;
480
    }
481
482
    /**
483
     * Returns a like criterion used for matching objects against a query
484
     *
485
     * @param string $propertyName The name of the property to compare against
486
     * @param mixed $operand The value to compare with
487
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
488
     */
489
    public function like($propertyName, $operand)
490
    {
491
        return $this->qomFactory->comparison(
492
            $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
493
            QueryInterface::OPERATOR_LIKE,
494
            $operand
495
        );
496
    }
497
498
    /**
499
     * Returns a "contains" criterion used for matching objects against a query.
500
     * It matches if the multivalued property contains the given operand.
501
     *
502
     * @param string $propertyName The name of the (multivalued) property to compare against
503
     * @param mixed $operand The value to compare with
504
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
505
     */
506
    public function contains($propertyName, $operand)
507
    {
508
        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_CONTAINS, $operand);
509
    }
510
511
    /**
512
     * Returns an "in" criterion used for matching objects against a query. It
513
     * matches if the property's value is contained in the multivalued operand.
514
     *
515
     * @param string $propertyName The name of the property to compare against
516
     * @param mixed $operand The value to compare with, multivalued
517
     * @throws Exception\UnexpectedTypeException
518
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
519
     */
520
    public function in($propertyName, $operand)
521
    {
522
        if (!TypeHandlingUtility::isValidTypeForMultiValueComparison($operand)) {
523
            throw new UnexpectedTypeException('The "in" operator must be given a multivalued operand (array, ArrayAccess, Traversable).', 1264678095);
524
        }
525
        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_IN, $operand);
526
    }
527
528
    /**
529
     * Returns a less than criterion used for matching objects against a query
530
     *
531
     * @param string $propertyName The name of the property to compare against
532
     * @param mixed $operand The value to compare with
533
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
534
     */
535
    public function lessThan($propertyName, $operand)
536
    {
537
        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN, $operand);
538
    }
539
540
    /**
541
     * Returns a less or equal than criterion used for matching objects against a query
542
     *
543
     * @param string $propertyName The name of the property to compare against
544
     * @param mixed $operand The value to compare with
545
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
546
     */
547
    public function lessThanOrEqual($propertyName, $operand)
548
    {
549
        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO, $operand);
550
    }
551
552
    /**
553
     * Returns a greater 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
     */
559
    public function greaterThan($propertyName, $operand)
560
    {
561
        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN, $operand);
562
    }
563
564
    /**
565
     * Returns a greater than or equal criterion used for matching objects against a query
566
     *
567
     * @param string $propertyName The name of the property to compare against
568
     * @param mixed $operand The value to compare with
569
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface
570
     */
571
    public function greaterThanOrEqual($propertyName, $operand)
572
    {
573
        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $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 $operandLower The value of the lower boundary to compare against
581
     * @param mixed $operandUpper The value of the upper boundary to compare against
582
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface
583
     * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidNumberOfConstraintsException
584
     */
585
    public function between($propertyName, $operandLower, $operandUpper)
586
    {
587
        return $this->logicalAnd(
588
            $this->greaterThanOrEqual($propertyName, $operandLower),
589
            $this->lessThanOrEqual($propertyName, $operandUpper)
590
        );
591
    }
592
593
    /**
594
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
595
     */
596
    public function __wakeup()
597
    {
598
        $this->persistenceManager = GeneralUtility::makeInstance(PersistenceManagerInterface::class);
599
        $this->dataMapFactory = GeneralUtility::makeInstance(DataMapFactory::class);
600
        $this->qomFactory = GeneralUtility::makeInstance(QueryObjectModelFactory::class);
601
    }
602
603
    /**
604
     * @return array
605
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
606
     */
607
    public function __sleep()
608
    {
609
        return ['type', 'source', 'constraint', 'statement', 'orderings', 'limit', 'offset', 'querySettings'];
610
    }
611
612
    /**
613
     * Returns the query result count.
614
     *
615
     * @return int The query result count
616
     */
617
    public function count()
618
    {
619
        return $this->execute()->count();
620
    }
621
}
622