diff options
Diffstat (limited to 'vendor/doctrine/dbal/src/Query/QueryBuilder.php')
-rw-r--r-- | vendor/doctrine/dbal/src/Query/QueryBuilder.php | 1479 |
1 files changed, 1479 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Query/QueryBuilder.php b/vendor/doctrine/dbal/src/Query/QueryBuilder.php new file mode 100644 index 0000000..c6d3344 --- /dev/null +++ b/vendor/doctrine/dbal/src/Query/QueryBuilder.php | |||
@@ -0,0 +1,1479 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Query; | ||
6 | |||
7 | use Doctrine\DBAL\ArrayParameterType; | ||
8 | use Doctrine\DBAL\Cache\QueryCacheProfile; | ||
9 | use Doctrine\DBAL\Connection; | ||
10 | use Doctrine\DBAL\Exception; | ||
11 | use Doctrine\DBAL\ParameterType; | ||
12 | use Doctrine\DBAL\Query\Exception\NonUniqueAlias; | ||
13 | use Doctrine\DBAL\Query\Exception\UnknownAlias; | ||
14 | use Doctrine\DBAL\Query\Expression\CompositeExpression; | ||
15 | use Doctrine\DBAL\Query\Expression\ExpressionBuilder; | ||
16 | use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; | ||
17 | use Doctrine\DBAL\Result; | ||
18 | use Doctrine\DBAL\Statement; | ||
19 | use Doctrine\DBAL\Types\Type; | ||
20 | |||
21 | use function array_key_exists; | ||
22 | use function array_keys; | ||
23 | use function array_merge; | ||
24 | use function array_unshift; | ||
25 | use function count; | ||
26 | use function implode; | ||
27 | use function is_object; | ||
28 | use function substr; | ||
29 | |||
30 | /** | ||
31 | * QueryBuilder class is responsible to dynamically create SQL queries. | ||
32 | * | ||
33 | * Important: Verify that every feature you use will work with your database vendor. | ||
34 | * SQL Query Builder does not attempt to validate the generated SQL at all. | ||
35 | * | ||
36 | * The query builder does no validation whatsoever if certain features even work with the | ||
37 | * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements | ||
38 | * even if some vendors such as MySQL support it. | ||
39 | * | ||
40 | * @psalm-import-type WrapperParameterTypeArray from Connection | ||
41 | */ | ||
42 | class QueryBuilder | ||
43 | { | ||
44 | /** | ||
45 | * The complete SQL string for this query. | ||
46 | */ | ||
47 | private ?string $sql = null; | ||
48 | |||
49 | /** | ||
50 | * The query parameters. | ||
51 | * | ||
52 | * @var list<mixed>|array<string, mixed> | ||
53 | */ | ||
54 | private array $params = []; | ||
55 | |||
56 | /** | ||
57 | * The parameter type map of this query. | ||
58 | * | ||
59 | * @psalm-var WrapperParameterTypeArray | ||
60 | */ | ||
61 | private array $types = []; | ||
62 | |||
63 | /** | ||
64 | * The type of query this is. Can be select, update or delete. | ||
65 | */ | ||
66 | private QueryType $type = QueryType::SELECT; | ||
67 | |||
68 | /** | ||
69 | * The index of the first result to retrieve. | ||
70 | */ | ||
71 | private int $firstResult = 0; | ||
72 | |||
73 | /** | ||
74 | * The maximum number of results to retrieve or NULL to retrieve all results. | ||
75 | */ | ||
76 | private ?int $maxResults = null; | ||
77 | |||
78 | /** | ||
79 | * The counter of bound parameters used with {@see bindValue). | ||
80 | * | ||
81 | * @var int<0, max> | ||
82 | */ | ||
83 | private int $boundCounter = 0; | ||
84 | |||
85 | /** | ||
86 | * The SELECT parts of the query. | ||
87 | * | ||
88 | * @var string[] | ||
89 | */ | ||
90 | private array $select = []; | ||
91 | |||
92 | /** | ||
93 | * Whether this is a SELECT DISTINCT query. | ||
94 | */ | ||
95 | private bool $distinct = false; | ||
96 | |||
97 | /** | ||
98 | * The FROM parts of a SELECT query. | ||
99 | * | ||
100 | * @var From[] | ||
101 | */ | ||
102 | private array $from = []; | ||
103 | |||
104 | /** | ||
105 | * The table name for an INSERT, UPDATE or DELETE query. | ||
106 | */ | ||
107 | private ?string $table = null; | ||
108 | |||
109 | /** | ||
110 | * The list of joins, indexed by from alias. | ||
111 | * | ||
112 | * @var array<string, Join[]> | ||
113 | */ | ||
114 | private array $join = []; | ||
115 | |||
116 | /** | ||
117 | * The SET parts of an UPDATE query. | ||
118 | * | ||
119 | * @var string[] | ||
120 | */ | ||
121 | private array $set = []; | ||
122 | |||
123 | /** | ||
124 | * The WHERE part of a SELECT, UPDATE or DELETE query. | ||
125 | */ | ||
126 | private string|CompositeExpression|null $where = null; | ||
127 | |||
128 | /** | ||
129 | * The GROUP BY part of a SELECT query. | ||
130 | * | ||
131 | * @var string[] | ||
132 | */ | ||
133 | private array $groupBy = []; | ||
134 | |||
135 | /** | ||
136 | * The HAVING part of a SELECT query. | ||
137 | */ | ||
138 | private string|CompositeExpression|null $having = null; | ||
139 | |||
140 | /** | ||
141 | * The ORDER BY parts of a SELECT query. | ||
142 | * | ||
143 | * @var string[] | ||
144 | */ | ||
145 | private array $orderBy = []; | ||
146 | |||
147 | private ?ForUpdate $forUpdate = null; | ||
148 | |||
149 | /** | ||
150 | * The values of an INSERT query. | ||
151 | * | ||
152 | * @var array<string, mixed> | ||
153 | */ | ||
154 | private array $values = []; | ||
155 | |||
156 | /** | ||
157 | * The query cache profile used for caching results. | ||
158 | */ | ||
159 | private ?QueryCacheProfile $resultCacheProfile = null; | ||
160 | |||
161 | /** | ||
162 | * Initializes a new <tt>QueryBuilder</tt>. | ||
163 | * | ||
164 | * @param Connection $connection The DBAL Connection. | ||
165 | */ | ||
166 | public function __construct(private readonly Connection $connection) | ||
167 | { | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * Gets an ExpressionBuilder used for object-oriented construction of query expressions. | ||
172 | * This producer method is intended for convenient inline usage. Example: | ||
173 | * | ||
174 | * <code> | ||
175 | * $qb = $conn->createQueryBuilder() | ||
176 | * ->select('u') | ||
177 | * ->from('users', 'u') | ||
178 | * ->where($qb->expr()->eq('u.id', 1)); | ||
179 | * </code> | ||
180 | * | ||
181 | * For more complex expression construction, consider storing the expression | ||
182 | * builder object in a local variable. | ||
183 | */ | ||
184 | public function expr(): ExpressionBuilder | ||
185 | { | ||
186 | return $this->connection->createExpressionBuilder(); | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * Prepares and executes an SQL query and returns the first row of the result | ||
191 | * as an associative array. | ||
192 | * | ||
193 | * @return array<string, mixed>|false False is returned if no rows are found. | ||
194 | * | ||
195 | * @throws Exception | ||
196 | */ | ||
197 | public function fetchAssociative(): array|false | ||
198 | { | ||
199 | return $this->executeQuery()->fetchAssociative(); | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * Prepares and executes an SQL query and returns the first row of the result | ||
204 | * as a numerically indexed array. | ||
205 | * | ||
206 | * @return array<int, mixed>|false False is returned if no rows are found. | ||
207 | * | ||
208 | * @throws Exception | ||
209 | */ | ||
210 | public function fetchNumeric(): array|false | ||
211 | { | ||
212 | return $this->executeQuery()->fetchNumeric(); | ||
213 | } | ||
214 | |||
215 | /** | ||
216 | * Prepares and executes an SQL query and returns the value of a single column | ||
217 | * of the first row of the result. | ||
218 | * | ||
219 | * @return mixed|false False is returned if no rows are found. | ||
220 | * | ||
221 | * @throws Exception | ||
222 | */ | ||
223 | public function fetchOne(): mixed | ||
224 | { | ||
225 | return $this->executeQuery()->fetchOne(); | ||
226 | } | ||
227 | |||
228 | /** | ||
229 | * Prepares and executes an SQL query and returns the result as an array of numeric arrays. | ||
230 | * | ||
231 | * @return array<int,array<int,mixed>> | ||
232 | * | ||
233 | * @throws Exception | ||
234 | */ | ||
235 | public function fetchAllNumeric(): array | ||
236 | { | ||
237 | return $this->executeQuery()->fetchAllNumeric(); | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * Prepares and executes an SQL query and returns the result as an array of associative arrays. | ||
242 | * | ||
243 | * @return array<int,array<string,mixed>> | ||
244 | * | ||
245 | * @throws Exception | ||
246 | */ | ||
247 | public function fetchAllAssociative(): array | ||
248 | { | ||
249 | return $this->executeQuery()->fetchAllAssociative(); | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * Prepares and executes an SQL query and returns the result as an associative array with the keys | ||
254 | * mapped to the first column and the values mapped to the second column. | ||
255 | * | ||
256 | * @return array<mixed,mixed> | ||
257 | * | ||
258 | * @throws Exception | ||
259 | */ | ||
260 | public function fetchAllKeyValue(): array | ||
261 | { | ||
262 | return $this->executeQuery()->fetchAllKeyValue(); | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped | ||
267 | * to the first column and the values being an associative array representing the rest of the columns | ||
268 | * and their values. | ||
269 | * | ||
270 | * @return array<mixed,array<string,mixed>> | ||
271 | * | ||
272 | * @throws Exception | ||
273 | */ | ||
274 | public function fetchAllAssociativeIndexed(): array | ||
275 | { | ||
276 | return $this->executeQuery()->fetchAllAssociativeIndexed(); | ||
277 | } | ||
278 | |||
279 | /** | ||
280 | * Prepares and executes an SQL query and returns the result as an array of the first column values. | ||
281 | * | ||
282 | * @return array<int,mixed> | ||
283 | * | ||
284 | * @throws Exception | ||
285 | */ | ||
286 | public function fetchFirstColumn(): array | ||
287 | { | ||
288 | return $this->executeQuery()->fetchFirstColumn(); | ||
289 | } | ||
290 | |||
291 | /** | ||
292 | * Executes an SQL query (SELECT) and returns a Result. | ||
293 | * | ||
294 | * @throws Exception | ||
295 | */ | ||
296 | public function executeQuery(): Result | ||
297 | { | ||
298 | return $this->connection->executeQuery( | ||
299 | $this->getSQL(), | ||
300 | $this->params, | ||
301 | $this->types, | ||
302 | $this->resultCacheProfile, | ||
303 | ); | ||
304 | } | ||
305 | |||
306 | /** | ||
307 | * Executes an SQL statement and returns the number of affected rows. | ||
308 | * | ||
309 | * Should be used for INSERT, UPDATE and DELETE | ||
310 | * | ||
311 | * @return int|numeric-string The number of affected rows. | ||
312 | * | ||
313 | * @throws Exception | ||
314 | */ | ||
315 | public function executeStatement(): int|string | ||
316 | { | ||
317 | return $this->connection->executeStatement($this->getSQL(), $this->params, $this->types); | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * Gets the complete SQL string formed by the current specifications of this QueryBuilder. | ||
322 | * | ||
323 | * <code> | ||
324 | * $qb = $em->createQueryBuilder() | ||
325 | * ->select('u') | ||
326 | * ->from('User', 'u') | ||
327 | * echo $qb->getSQL(); // SELECT u FROM User u | ||
328 | * </code> | ||
329 | * | ||
330 | * @return string The SQL query string. | ||
331 | */ | ||
332 | public function getSQL(): string | ||
333 | { | ||
334 | return $this->sql ??= match ($this->type) { | ||
335 | QueryType::INSERT => $this->getSQLForInsert(), | ||
336 | QueryType::DELETE => $this->getSQLForDelete(), | ||
337 | QueryType::UPDATE => $this->getSQLForUpdate(), | ||
338 | QueryType::SELECT => $this->getSQLForSelect(), | ||
339 | }; | ||
340 | } | ||
341 | |||
342 | /** | ||
343 | * Sets a query parameter for the query being constructed. | ||
344 | * | ||
345 | * <code> | ||
346 | * $qb = $conn->createQueryBuilder() | ||
347 | * ->select('u') | ||
348 | * ->from('users', 'u') | ||
349 | * ->where('u.id = :user_id') | ||
350 | * ->setParameter('user_id', 1); | ||
351 | * </code> | ||
352 | * | ||
353 | * @param int<0, max>|string $key Parameter position or name | ||
354 | * | ||
355 | * @return $this This QueryBuilder instance. | ||
356 | */ | ||
357 | public function setParameter( | ||
358 | int|string $key, | ||
359 | mixed $value, | ||
360 | string|ParameterType|Type|ArrayParameterType $type = ParameterType::STRING, | ||
361 | ): self { | ||
362 | $this->params[$key] = $value; | ||
363 | $this->types[$key] = $type; | ||
364 | |||
365 | return $this; | ||
366 | } | ||
367 | |||
368 | /** | ||
369 | * Sets a collection of query parameters for the query being constructed. | ||
370 | * | ||
371 | * <code> | ||
372 | * $qb = $conn->createQueryBuilder() | ||
373 | * ->select('u') | ||
374 | * ->from('users', 'u') | ||
375 | * ->where('u.id = :user_id1 OR u.id = :user_id2') | ||
376 | * ->setParameters(array( | ||
377 | * 'user_id1' => 1, | ||
378 | * 'user_id2' => 2 | ||
379 | * )); | ||
380 | * </code> | ||
381 | * | ||
382 | * @param list<mixed>|array<string, mixed> $params | ||
383 | * @psalm-param WrapperParameterTypeArray $types | ||
384 | * | ||
385 | * @return $this This QueryBuilder instance. | ||
386 | */ | ||
387 | public function setParameters(array $params, array $types = []): self | ||
388 | { | ||
389 | $this->params = $params; | ||
390 | $this->types = $types; | ||
391 | |||
392 | return $this; | ||
393 | } | ||
394 | |||
395 | /** | ||
396 | * Gets all defined query parameters for the query being constructed indexed by parameter index or name. | ||
397 | * | ||
398 | * @return list<mixed>|array<string, mixed> The currently defined query parameters | ||
399 | */ | ||
400 | public function getParameters(): array | ||
401 | { | ||
402 | return $this->params; | ||
403 | } | ||
404 | |||
405 | /** | ||
406 | * Gets a (previously set) query parameter of the query being constructed. | ||
407 | * | ||
408 | * @param string|int $key The key (index or name) of the bound parameter. | ||
409 | * | ||
410 | * @return mixed The value of the bound parameter. | ||
411 | */ | ||
412 | public function getParameter(string|int $key): mixed | ||
413 | { | ||
414 | return $this->params[$key] ?? null; | ||
415 | } | ||
416 | |||
417 | /** | ||
418 | * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. | ||
419 | * | ||
420 | * @psalm-return WrapperParameterTypeArray | ||
421 | */ | ||
422 | public function getParameterTypes(): array | ||
423 | { | ||
424 | return $this->types; | ||
425 | } | ||
426 | |||
427 | /** | ||
428 | * Gets a (previously set) query parameter type of the query being constructed. | ||
429 | * | ||
430 | * @param int|string $key The key of the bound parameter type | ||
431 | */ | ||
432 | public function getParameterType(int|string $key): string|ParameterType|Type|ArrayParameterType | ||
433 | { | ||
434 | return $this->types[$key] ?? ParameterType::STRING; | ||
435 | } | ||
436 | |||
437 | /** | ||
438 | * Sets the position of the first result to retrieve (the "offset"). | ||
439 | * | ||
440 | * @param int $firstResult The first result to return. | ||
441 | * | ||
442 | * @return $this This QueryBuilder instance. | ||
443 | */ | ||
444 | public function setFirstResult(int $firstResult): self | ||
445 | { | ||
446 | $this->firstResult = $firstResult; | ||
447 | |||
448 | $this->sql = null; | ||
449 | |||
450 | return $this; | ||
451 | } | ||
452 | |||
453 | /** | ||
454 | * Gets the position of the first result the query object was set to retrieve (the "offset"). | ||
455 | * | ||
456 | * @return int The position of the first result. | ||
457 | */ | ||
458 | public function getFirstResult(): int | ||
459 | { | ||
460 | return $this->firstResult; | ||
461 | } | ||
462 | |||
463 | /** | ||
464 | * Sets the maximum number of results to retrieve (the "limit"). | ||
465 | * | ||
466 | * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results. | ||
467 | * | ||
468 | * @return $this This QueryBuilder instance. | ||
469 | */ | ||
470 | public function setMaxResults(?int $maxResults): self | ||
471 | { | ||
472 | $this->maxResults = $maxResults; | ||
473 | |||
474 | $this->sql = null; | ||
475 | |||
476 | return $this; | ||
477 | } | ||
478 | |||
479 | /** | ||
480 | * Gets the maximum number of results the query object was set to retrieve (the "limit"). | ||
481 | * Returns NULL if all results will be returned. | ||
482 | * | ||
483 | * @return int|null The maximum number of results. | ||
484 | */ | ||
485 | public function getMaxResults(): ?int | ||
486 | { | ||
487 | return $this->maxResults; | ||
488 | } | ||
489 | |||
490 | /** | ||
491 | * Locks the queried rows for a subsequent update. | ||
492 | * | ||
493 | * @return $this | ||
494 | */ | ||
495 | public function forUpdate(ConflictResolutionMode $conflictResolutionMode = ConflictResolutionMode::ORDINARY): self | ||
496 | { | ||
497 | $this->forUpdate = new ForUpdate($conflictResolutionMode); | ||
498 | |||
499 | $this->sql = null; | ||
500 | |||
501 | return $this; | ||
502 | } | ||
503 | |||
504 | /** | ||
505 | * Specifies an item that is to be returned in the query result. | ||
506 | * Replaces any previously specified selections, if any. | ||
507 | * | ||
508 | * <code> | ||
509 | * $qb = $conn->createQueryBuilder() | ||
510 | * ->select('u.id', 'p.id') | ||
511 | * ->from('users', 'u') | ||
512 | * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); | ||
513 | * </code> | ||
514 | * | ||
515 | * @param string ...$expressions The selection expressions. | ||
516 | * | ||
517 | * @return $this This QueryBuilder instance. | ||
518 | */ | ||
519 | public function select(string ...$expressions): self | ||
520 | { | ||
521 | $this->type = QueryType::SELECT; | ||
522 | |||
523 | $this->select = $expressions; | ||
524 | |||
525 | $this->sql = null; | ||
526 | |||
527 | return $this; | ||
528 | } | ||
529 | |||
530 | /** | ||
531 | * Adds or removes DISTINCT to/from the query. | ||
532 | * | ||
533 | * <code> | ||
534 | * $qb = $conn->createQueryBuilder() | ||
535 | * ->select('u.id') | ||
536 | * ->distinct() | ||
537 | * ->from('users', 'u') | ||
538 | * </code> | ||
539 | * | ||
540 | * @return $this This QueryBuilder instance. | ||
541 | */ | ||
542 | public function distinct(bool $distinct = true): self | ||
543 | { | ||
544 | $this->distinct = $distinct; | ||
545 | $this->sql = null; | ||
546 | |||
547 | return $this; | ||
548 | } | ||
549 | |||
550 | /** | ||
551 | * Adds an item that is to be returned in the query result. | ||
552 | * | ||
553 | * <code> | ||
554 | * $qb = $conn->createQueryBuilder() | ||
555 | * ->select('u.id') | ||
556 | * ->addSelect('p.id') | ||
557 | * ->from('users', 'u') | ||
558 | * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); | ||
559 | * </code> | ||
560 | * | ||
561 | * @param string $expression The selection expression. | ||
562 | * @param string ...$expressions Additional selection expressions. | ||
563 | * | ||
564 | * @return $this This QueryBuilder instance. | ||
565 | */ | ||
566 | public function addSelect(string $expression, string ...$expressions): self | ||
567 | { | ||
568 | $this->type = QueryType::SELECT; | ||
569 | |||
570 | $this->select = array_merge($this->select, [$expression], $expressions); | ||
571 | |||
572 | $this->sql = null; | ||
573 | |||
574 | return $this; | ||
575 | } | ||
576 | |||
577 | /** | ||
578 | * Turns the query being built into a bulk delete query that ranges over | ||
579 | * a certain table. | ||
580 | * | ||
581 | * <code> | ||
582 | * $qb = $conn->createQueryBuilder() | ||
583 | * ->delete('users u') | ||
584 | * ->where('u.id = :user_id') | ||
585 | * ->setParameter(':user_id', 1); | ||
586 | * </code> | ||
587 | * | ||
588 | * @param string $table The table whose rows are subject to the deletion. | ||
589 | * | ||
590 | * @return $this This QueryBuilder instance. | ||
591 | */ | ||
592 | public function delete(string $table): self | ||
593 | { | ||
594 | $this->type = QueryType::DELETE; | ||
595 | |||
596 | $this->table = $table; | ||
597 | |||
598 | $this->sql = null; | ||
599 | |||
600 | return $this; | ||
601 | } | ||
602 | |||
603 | /** | ||
604 | * Turns the query being built into a bulk update query that ranges over | ||
605 | * a certain table | ||
606 | * | ||
607 | * <code> | ||
608 | * $qb = $conn->createQueryBuilder() | ||
609 | * ->update('counters c') | ||
610 | * ->set('c.value', 'c.value + 1') | ||
611 | * ->where('c.id = ?'); | ||
612 | * </code> | ||
613 | * | ||
614 | * @param string $table The table whose rows are subject to the update. | ||
615 | * | ||
616 | * @return $this This QueryBuilder instance. | ||
617 | */ | ||
618 | public function update(string $table): self | ||
619 | { | ||
620 | $this->type = QueryType::UPDATE; | ||
621 | |||
622 | $this->table = $table; | ||
623 | |||
624 | $this->sql = null; | ||
625 | |||
626 | return $this; | ||
627 | } | ||
628 | |||
629 | /** | ||
630 | * Turns the query being built into an insert query that inserts into | ||
631 | * a certain table | ||
632 | * | ||
633 | * <code> | ||
634 | * $qb = $conn->createQueryBuilder() | ||
635 | * ->insert('users') | ||
636 | * ->values( | ||
637 | * array( | ||
638 | * 'name' => '?', | ||
639 | * 'password' => '?' | ||
640 | * ) | ||
641 | * ); | ||
642 | * </code> | ||
643 | * | ||
644 | * @param string $table The table into which the rows should be inserted. | ||
645 | * | ||
646 | * @return $this This QueryBuilder instance. | ||
647 | */ | ||
648 | public function insert(string $table): self | ||
649 | { | ||
650 | $this->type = QueryType::INSERT; | ||
651 | |||
652 | $this->table = $table; | ||
653 | |||
654 | $this->sql = null; | ||
655 | |||
656 | return $this; | ||
657 | } | ||
658 | |||
659 | /** | ||
660 | * Creates and adds a query root corresponding to the table identified by the | ||
661 | * given alias, forming a cartesian product with any existing query roots. | ||
662 | * | ||
663 | * <code> | ||
664 | * $qb = $conn->createQueryBuilder() | ||
665 | * ->select('u.id') | ||
666 | * ->from('users', 'u') | ||
667 | * </code> | ||
668 | * | ||
669 | * @param string $table The table. | ||
670 | * @param string|null $alias The alias of the table. | ||
671 | * | ||
672 | * @return $this This QueryBuilder instance. | ||
673 | */ | ||
674 | public function from(string $table, ?string $alias = null): self | ||
675 | { | ||
676 | $this->from[] = new From($table, $alias); | ||
677 | |||
678 | $this->sql = null; | ||
679 | |||
680 | return $this; | ||
681 | } | ||
682 | |||
683 | /** | ||
684 | * Creates and adds a join to the query. | ||
685 | * | ||
686 | * <code> | ||
687 | * $qb = $conn->createQueryBuilder() | ||
688 | * ->select('u.name') | ||
689 | * ->from('users', 'u') | ||
690 | * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); | ||
691 | * </code> | ||
692 | * | ||
693 | * @param string $fromAlias The alias that points to a from clause. | ||
694 | * @param string $join The table name to join. | ||
695 | * @param string $alias The alias of the join table. | ||
696 | * @param string $condition The condition for the join. | ||
697 | * | ||
698 | * @return $this This QueryBuilder instance. | ||
699 | */ | ||
700 | public function join(string $fromAlias, string $join, string $alias, ?string $condition = null): self | ||
701 | { | ||
702 | return $this->innerJoin($fromAlias, $join, $alias, $condition); | ||
703 | } | ||
704 | |||
705 | /** | ||
706 | * Creates and adds a join to the query. | ||
707 | * | ||
708 | * <code> | ||
709 | * $qb = $conn->createQueryBuilder() | ||
710 | * ->select('u.name') | ||
711 | * ->from('users', 'u') | ||
712 | * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); | ||
713 | * </code> | ||
714 | * | ||
715 | * @param string $fromAlias The alias that points to a from clause. | ||
716 | * @param string $join The table name to join. | ||
717 | * @param string $alias The alias of the join table. | ||
718 | * @param string $condition The condition for the join. | ||
719 | * | ||
720 | * @return $this This QueryBuilder instance. | ||
721 | */ | ||
722 | public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self | ||
723 | { | ||
724 | $this->join[$fromAlias][] = Join::inner($join, $alias, $condition); | ||
725 | |||
726 | $this->sql = null; | ||
727 | |||
728 | return $this; | ||
729 | } | ||
730 | |||
731 | /** | ||
732 | * Creates and adds a left join to the query. | ||
733 | * | ||
734 | * <code> | ||
735 | * $qb = $conn->createQueryBuilder() | ||
736 | * ->select('u.name') | ||
737 | * ->from('users', 'u') | ||
738 | * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); | ||
739 | * </code> | ||
740 | * | ||
741 | * @param string $fromAlias The alias that points to a from clause. | ||
742 | * @param string $join The table name to join. | ||
743 | * @param string $alias The alias of the join table. | ||
744 | * @param string $condition The condition for the join. | ||
745 | * | ||
746 | * @return $this This QueryBuilder instance. | ||
747 | */ | ||
748 | public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self | ||
749 | { | ||
750 | $this->join[$fromAlias][] = Join::left($join, $alias, $condition); | ||
751 | |||
752 | $this->sql = null; | ||
753 | |||
754 | return $this; | ||
755 | } | ||
756 | |||
757 | /** | ||
758 | * Creates and adds a right join to the query. | ||
759 | * | ||
760 | * <code> | ||
761 | * $qb = $conn->createQueryBuilder() | ||
762 | * ->select('u.name') | ||
763 | * ->from('users', 'u') | ||
764 | * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); | ||
765 | * </code> | ||
766 | * | ||
767 | * @param string $fromAlias The alias that points to a from clause. | ||
768 | * @param string $join The table name to join. | ||
769 | * @param string $alias The alias of the join table. | ||
770 | * @param string $condition The condition for the join. | ||
771 | * | ||
772 | * @return $this This QueryBuilder instance. | ||
773 | */ | ||
774 | public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self | ||
775 | { | ||
776 | $this->join[$fromAlias][] = Join::right($join, $alias, $condition); | ||
777 | |||
778 | $this->sql = null; | ||
779 | |||
780 | return $this; | ||
781 | } | ||
782 | |||
783 | /** | ||
784 | * Sets a new value for a column in a bulk update query. | ||
785 | * | ||
786 | * <code> | ||
787 | * $qb = $conn->createQueryBuilder() | ||
788 | * ->update('counters c') | ||
789 | * ->set('c.value', 'c.value + 1') | ||
790 | * ->where('c.id = ?'); | ||
791 | * </code> | ||
792 | * | ||
793 | * @param string $key The column to set. | ||
794 | * @param string $value The value, expression, placeholder, etc. | ||
795 | * | ||
796 | * @return $this This QueryBuilder instance. | ||
797 | */ | ||
798 | public function set(string $key, string $value): self | ||
799 | { | ||
800 | $this->set[] = $key . ' = ' . $value; | ||
801 | |||
802 | $this->sql = null; | ||
803 | |||
804 | return $this; | ||
805 | } | ||
806 | |||
807 | /** | ||
808 | * Specifies one or more restrictions to the query result. | ||
809 | * Replaces any previously specified restrictions, if any. | ||
810 | * | ||
811 | * <code> | ||
812 | * $qb = $conn->createQueryBuilder() | ||
813 | * ->select('c.value') | ||
814 | * ->from('counters', 'c') | ||
815 | * ->where('c.id = ?'); | ||
816 | * | ||
817 | * // You can optionally programmatically build and/or expressions | ||
818 | * $qb = $conn->createQueryBuilder(); | ||
819 | * | ||
820 | * $or = $qb->expr()->orx(); | ||
821 | * $or->add($qb->expr()->eq('c.id', 1)); | ||
822 | * $or->add($qb->expr()->eq('c.id', 2)); | ||
823 | * | ||
824 | * $qb->update('counters c') | ||
825 | * ->set('c.value', 'c.value + 1') | ||
826 | * ->where($or); | ||
827 | * </code> | ||
828 | * | ||
829 | * @param string|CompositeExpression $predicate The WHERE clause predicate. | ||
830 | * @param string|CompositeExpression ...$predicates Additional WHERE clause predicates. | ||
831 | * | ||
832 | * @return $this This QueryBuilder instance. | ||
833 | */ | ||
834 | public function where(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self | ||
835 | { | ||
836 | $this->where = $this->createPredicate($predicate, ...$predicates); | ||
837 | |||
838 | $this->sql = null; | ||
839 | |||
840 | return $this; | ||
841 | } | ||
842 | |||
843 | /** | ||
844 | * Adds one or more restrictions to the query results, forming a logical | ||
845 | * conjunction with any previously specified restrictions. | ||
846 | * | ||
847 | * <code> | ||
848 | * $qb = $conn->createQueryBuilder() | ||
849 | * ->select('u') | ||
850 | * ->from('users', 'u') | ||
851 | * ->where('u.username LIKE ?') | ||
852 | * ->andWhere('u.is_active = 1'); | ||
853 | * </code> | ||
854 | * | ||
855 | * @see where() | ||
856 | * | ||
857 | * @param string|CompositeExpression $predicate The predicate to append. | ||
858 | * @param string|CompositeExpression ...$predicates Additional predicates to append. | ||
859 | * | ||
860 | * @return $this This QueryBuilder instance. | ||
861 | */ | ||
862 | public function andWhere(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self | ||
863 | { | ||
864 | $this->where = $this->appendToPredicate( | ||
865 | $this->where, | ||
866 | CompositeExpression::TYPE_AND, | ||
867 | $predicate, | ||
868 | ...$predicates, | ||
869 | ); | ||
870 | |||
871 | $this->sql = null; | ||
872 | |||
873 | return $this; | ||
874 | } | ||
875 | |||
876 | /** | ||
877 | * Adds one or more restrictions to the query results, forming a logical | ||
878 | * disjunction with any previously specified restrictions. | ||
879 | * | ||
880 | * <code> | ||
881 | * $qb = $em->createQueryBuilder() | ||
882 | * ->select('u.name') | ||
883 | * ->from('users', 'u') | ||
884 | * ->where('u.id = 1') | ||
885 | * ->orWhere('u.id = 2'); | ||
886 | * </code> | ||
887 | * | ||
888 | * @see where() | ||
889 | * | ||
890 | * @param string|CompositeExpression $predicate The predicate to append. | ||
891 | * @param string|CompositeExpression ...$predicates Additional predicates to append. | ||
892 | * | ||
893 | * @return $this This QueryBuilder instance. | ||
894 | */ | ||
895 | public function orWhere(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self | ||
896 | { | ||
897 | $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_OR, $predicate, ...$predicates); | ||
898 | |||
899 | $this->sql = null; | ||
900 | |||
901 | return $this; | ||
902 | } | ||
903 | |||
904 | /** | ||
905 | * Specifies one or more grouping expressions over the results of the query. | ||
906 | * Replaces any previously specified groupings, if any. | ||
907 | * | ||
908 | * <code> | ||
909 | * $qb = $conn->createQueryBuilder() | ||
910 | * ->select('u.name') | ||
911 | * ->from('users', 'u') | ||
912 | * ->groupBy('u.id'); | ||
913 | * </code> | ||
914 | * | ||
915 | * @param string $expression The grouping expression | ||
916 | * @param string ...$expressions Additional grouping expressions | ||
917 | * | ||
918 | * @return $this This QueryBuilder instance. | ||
919 | */ | ||
920 | public function groupBy(string $expression, string ...$expressions): self | ||
921 | { | ||
922 | $this->groupBy = array_merge([$expression], $expressions); | ||
923 | |||
924 | $this->sql = null; | ||
925 | |||
926 | return $this; | ||
927 | } | ||
928 | |||
929 | /** | ||
930 | * Adds one or more grouping expressions to the query. | ||
931 | * | ||
932 | * <code> | ||
933 | * $qb = $conn->createQueryBuilder() | ||
934 | * ->select('u.name') | ||
935 | * ->from('users', 'u') | ||
936 | * ->groupBy('u.lastLogin') | ||
937 | * ->addGroupBy('u.createdAt'); | ||
938 | * </code> | ||
939 | * | ||
940 | * @param string $expression The grouping expression | ||
941 | * @param string ...$expressions Additional grouping expressions | ||
942 | * | ||
943 | * @return $this This QueryBuilder instance. | ||
944 | */ | ||
945 | public function addGroupBy(string $expression, string ...$expressions): self | ||
946 | { | ||
947 | $this->groupBy = array_merge($this->groupBy, [$expression], $expressions); | ||
948 | |||
949 | $this->sql = null; | ||
950 | |||
951 | return $this; | ||
952 | } | ||
953 | |||
954 | /** | ||
955 | * Sets a value for a column in an insert query. | ||
956 | * | ||
957 | * <code> | ||
958 | * $qb = $conn->createQueryBuilder() | ||
959 | * ->insert('users') | ||
960 | * ->values( | ||
961 | * array( | ||
962 | * 'name' => '?' | ||
963 | * ) | ||
964 | * ) | ||
965 | * ->setValue('password', '?'); | ||
966 | * </code> | ||
967 | * | ||
968 | * @param string $column The column into which the value should be inserted. | ||
969 | * @param string $value The value that should be inserted into the column. | ||
970 | * | ||
971 | * @return $this This QueryBuilder instance. | ||
972 | */ | ||
973 | public function setValue(string $column, string $value): self | ||
974 | { | ||
975 | $this->values[$column] = $value; | ||
976 | |||
977 | return $this; | ||
978 | } | ||
979 | |||
980 | /** | ||
981 | * Specifies values for an insert query indexed by column names. | ||
982 | * Replaces any previous values, if any. | ||
983 | * | ||
984 | * <code> | ||
985 | * $qb = $conn->createQueryBuilder() | ||
986 | * ->insert('users') | ||
987 | * ->values( | ||
988 | * array( | ||
989 | * 'name' => '?', | ||
990 | * 'password' => '?' | ||
991 | * ) | ||
992 | * ); | ||
993 | * </code> | ||
994 | * | ||
995 | * @param array<string, mixed> $values The values to specify for the insert query indexed by column names. | ||
996 | * | ||
997 | * @return $this This QueryBuilder instance. | ||
998 | */ | ||
999 | public function values(array $values): self | ||
1000 | { | ||
1001 | $this->values = $values; | ||
1002 | |||
1003 | $this->sql = null; | ||
1004 | |||
1005 | return $this; | ||
1006 | } | ||
1007 | |||
1008 | /** | ||
1009 | * Specifies a restriction over the groups of the query. | ||
1010 | * Replaces any previous having restrictions, if any. | ||
1011 | * | ||
1012 | * @param string|CompositeExpression $predicate The HAVING clause predicate. | ||
1013 | * @param string|CompositeExpression ...$predicates Additional HAVING clause predicates. | ||
1014 | * | ||
1015 | * @return $this This QueryBuilder instance. | ||
1016 | */ | ||
1017 | public function having(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self | ||
1018 | { | ||
1019 | $this->having = $this->createPredicate($predicate, ...$predicates); | ||
1020 | |||
1021 | $this->sql = null; | ||
1022 | |||
1023 | return $this; | ||
1024 | } | ||
1025 | |||
1026 | /** | ||
1027 | * Adds a restriction over the groups of the query, forming a logical | ||
1028 | * conjunction with any existing having restrictions. | ||
1029 | * | ||
1030 | * @param string|CompositeExpression $predicate The predicate to append. | ||
1031 | * @param string|CompositeExpression ...$predicates Additional predicates to append. | ||
1032 | * | ||
1033 | * @return $this This QueryBuilder instance. | ||
1034 | */ | ||
1035 | public function andHaving(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self | ||
1036 | { | ||
1037 | $this->having = $this->appendToPredicate( | ||
1038 | $this->having, | ||
1039 | CompositeExpression::TYPE_AND, | ||
1040 | $predicate, | ||
1041 | ...$predicates, | ||
1042 | ); | ||
1043 | |||
1044 | $this->sql = null; | ||
1045 | |||
1046 | return $this; | ||
1047 | } | ||
1048 | |||
1049 | /** | ||
1050 | * Adds a restriction over the groups of the query, forming a logical | ||
1051 | * disjunction with any existing having restrictions. | ||
1052 | * | ||
1053 | * @param string|CompositeExpression $predicate The predicate to append. | ||
1054 | * @param string|CompositeExpression ...$predicates Additional predicates to append. | ||
1055 | * | ||
1056 | * @return $this This QueryBuilder instance. | ||
1057 | */ | ||
1058 | public function orHaving(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self | ||
1059 | { | ||
1060 | $this->having = $this->appendToPredicate( | ||
1061 | $this->having, | ||
1062 | CompositeExpression::TYPE_OR, | ||
1063 | $predicate, | ||
1064 | ...$predicates, | ||
1065 | ); | ||
1066 | |||
1067 | $this->sql = null; | ||
1068 | |||
1069 | return $this; | ||
1070 | } | ||
1071 | |||
1072 | /** | ||
1073 | * Creates a CompositeExpression from one or more predicates combined by the AND logic. | ||
1074 | */ | ||
1075 | private function createPredicate( | ||
1076 | string|CompositeExpression $predicate, | ||
1077 | string|CompositeExpression ...$predicates, | ||
1078 | ): string|CompositeExpression { | ||
1079 | if (count($predicates) === 0) { | ||
1080 | return $predicate; | ||
1081 | } | ||
1082 | |||
1083 | return new CompositeExpression(CompositeExpression::TYPE_AND, $predicate, ...$predicates); | ||
1084 | } | ||
1085 | |||
1086 | /** | ||
1087 | * Appends the given predicates combined by the given type of logic to the current predicate. | ||
1088 | */ | ||
1089 | private function appendToPredicate( | ||
1090 | string|CompositeExpression|null $currentPredicate, | ||
1091 | string $type, | ||
1092 | string|CompositeExpression ...$predicates, | ||
1093 | ): string|CompositeExpression { | ||
1094 | if ($currentPredicate instanceof CompositeExpression && $currentPredicate->getType() === $type) { | ||
1095 | return $currentPredicate->with(...$predicates); | ||
1096 | } | ||
1097 | |||
1098 | if ($currentPredicate !== null) { | ||
1099 | array_unshift($predicates, $currentPredicate); | ||
1100 | } elseif (count($predicates) === 1) { | ||
1101 | return $predicates[0]; | ||
1102 | } | ||
1103 | |||
1104 | return new CompositeExpression($type, ...$predicates); | ||
1105 | } | ||
1106 | |||
1107 | /** | ||
1108 | * Specifies an ordering for the query results. | ||
1109 | * Replaces any previously specified orderings, if any. | ||
1110 | * | ||
1111 | * @param string $sort The ordering expression. | ||
1112 | * @param string $order The ordering direction. | ||
1113 | * | ||
1114 | * @return $this This QueryBuilder instance. | ||
1115 | */ | ||
1116 | public function orderBy(string $sort, ?string $order = null): self | ||
1117 | { | ||
1118 | $orderBy = $sort; | ||
1119 | |||
1120 | if ($order !== null) { | ||
1121 | $orderBy .= ' ' . $order; | ||
1122 | } | ||
1123 | |||
1124 | $this->orderBy = [$orderBy]; | ||
1125 | |||
1126 | $this->sql = null; | ||
1127 | |||
1128 | return $this; | ||
1129 | } | ||
1130 | |||
1131 | /** | ||
1132 | * Adds an ordering to the query results. | ||
1133 | * | ||
1134 | * @param string $sort The ordering expression. | ||
1135 | * @param string $order The ordering direction. | ||
1136 | * | ||
1137 | * @return $this This QueryBuilder instance. | ||
1138 | */ | ||
1139 | public function addOrderBy(string $sort, ?string $order = null): self | ||
1140 | { | ||
1141 | $orderBy = $sort; | ||
1142 | |||
1143 | if ($order !== null) { | ||
1144 | $orderBy .= ' ' . $order; | ||
1145 | } | ||
1146 | |||
1147 | $this->orderBy[] = $orderBy; | ||
1148 | |||
1149 | $this->sql = null; | ||
1150 | |||
1151 | return $this; | ||
1152 | } | ||
1153 | |||
1154 | /** | ||
1155 | * Resets the WHERE conditions for the query. | ||
1156 | * | ||
1157 | * @return $this This QueryBuilder instance. | ||
1158 | */ | ||
1159 | public function resetWhere(): self | ||
1160 | { | ||
1161 | $this->where = null; | ||
1162 | $this->sql = null; | ||
1163 | |||
1164 | return $this; | ||
1165 | } | ||
1166 | |||
1167 | /** | ||
1168 | * Resets the grouping for the query. | ||
1169 | * | ||
1170 | * @return $this This QueryBuilder instance. | ||
1171 | */ | ||
1172 | public function resetGroupBy(): self | ||
1173 | { | ||
1174 | $this->groupBy = []; | ||
1175 | $this->sql = null; | ||
1176 | |||
1177 | return $this; | ||
1178 | } | ||
1179 | |||
1180 | /** | ||
1181 | * Resets the HAVING conditions for the query. | ||
1182 | * | ||
1183 | * @return $this This QueryBuilder instance. | ||
1184 | */ | ||
1185 | public function resetHaving(): self | ||
1186 | { | ||
1187 | $this->having = null; | ||
1188 | $this->sql = null; | ||
1189 | |||
1190 | return $this; | ||
1191 | } | ||
1192 | |||
1193 | /** | ||
1194 | * Resets the ordering for the query. | ||
1195 | * | ||
1196 | * @return $this This QueryBuilder instance. | ||
1197 | */ | ||
1198 | public function resetOrderBy(): self | ||
1199 | { | ||
1200 | $this->orderBy = []; | ||
1201 | $this->sql = null; | ||
1202 | |||
1203 | return $this; | ||
1204 | } | ||
1205 | |||
1206 | /** @throws Exception */ | ||
1207 | private function getSQLForSelect(): string | ||
1208 | { | ||
1209 | if (count($this->select) === 0) { | ||
1210 | throw new QueryException('No SELECT expressions given. Please use select() or addSelect().'); | ||
1211 | } | ||
1212 | |||
1213 | return $this->connection->getDatabasePlatform() | ||
1214 | ->createSelectSQLBuilder() | ||
1215 | ->buildSQL( | ||
1216 | new SelectQuery( | ||
1217 | $this->distinct, | ||
1218 | $this->select, | ||
1219 | $this->getFromClauses(), | ||
1220 | $this->where !== null ? (string) $this->where : null, | ||
1221 | $this->groupBy, | ||
1222 | $this->having !== null ? (string) $this->having : null, | ||
1223 | $this->orderBy, | ||
1224 | new Limit($this->maxResults, $this->firstResult), | ||
1225 | $this->forUpdate, | ||
1226 | ), | ||
1227 | ); | ||
1228 | } | ||
1229 | |||
1230 | /** | ||
1231 | * @return array<string, string> | ||
1232 | * | ||
1233 | * @throws QueryException | ||
1234 | */ | ||
1235 | private function getFromClauses(): array | ||
1236 | { | ||
1237 | $fromClauses = []; | ||
1238 | $knownAliases = []; | ||
1239 | |||
1240 | foreach ($this->from as $from) { | ||
1241 | if ($from->alias === null || $from->alias === $from->table) { | ||
1242 | $tableSql = $from->table; | ||
1243 | $tableReference = $from->table; | ||
1244 | } else { | ||
1245 | $tableSql = $from->table . ' ' . $from->alias; | ||
1246 | $tableReference = $from->alias; | ||
1247 | } | ||
1248 | |||
1249 | $knownAliases[$tableReference] = true; | ||
1250 | |||
1251 | $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases); | ||
1252 | } | ||
1253 | |||
1254 | $this->verifyAllAliasesAreKnown($knownAliases); | ||
1255 | |||
1256 | return $fromClauses; | ||
1257 | } | ||
1258 | |||
1259 | /** | ||
1260 | * @param array<string, true> $knownAliases | ||
1261 | * | ||
1262 | * @throws QueryException | ||
1263 | */ | ||
1264 | private function verifyAllAliasesAreKnown(array $knownAliases): void | ||
1265 | { | ||
1266 | foreach ($this->join as $fromAlias => $joins) { | ||
1267 | if (! isset($knownAliases[$fromAlias])) { | ||
1268 | throw UnknownAlias::new($fromAlias, array_keys($knownAliases)); | ||
1269 | } | ||
1270 | } | ||
1271 | } | ||
1272 | |||
1273 | /** | ||
1274 | * Converts this instance into an INSERT string in SQL. | ||
1275 | */ | ||
1276 | private function getSQLForInsert(): string | ||
1277 | { | ||
1278 | return 'INSERT INTO ' . $this->table . | ||
1279 | ' (' . implode(', ', array_keys($this->values)) . ')' . | ||
1280 | ' VALUES(' . implode(', ', $this->values) . ')'; | ||
1281 | } | ||
1282 | |||
1283 | /** | ||
1284 | * Converts this instance into an UPDATE string in SQL. | ||
1285 | */ | ||
1286 | private function getSQLForUpdate(): string | ||
1287 | { | ||
1288 | $query = 'UPDATE ' . $this->table | ||
1289 | . ' SET ' . implode(', ', $this->set); | ||
1290 | |||
1291 | if ($this->where !== null) { | ||
1292 | $query .= ' WHERE ' . $this->where; | ||
1293 | } | ||
1294 | |||
1295 | return $query; | ||
1296 | } | ||
1297 | |||
1298 | /** | ||
1299 | * Converts this instance into a DELETE string in SQL. | ||
1300 | */ | ||
1301 | private function getSQLForDelete(): string | ||
1302 | { | ||
1303 | $query = 'DELETE FROM ' . $this->table; | ||
1304 | |||
1305 | if ($this->where !== null) { | ||
1306 | $query .= ' WHERE ' . $this->where; | ||
1307 | } | ||
1308 | |||
1309 | return $query; | ||
1310 | } | ||
1311 | |||
1312 | /** | ||
1313 | * Gets a string representation of this QueryBuilder which corresponds to | ||
1314 | * the final SQL query being constructed. | ||
1315 | * | ||
1316 | * @return string The string representation of this QueryBuilder. | ||
1317 | */ | ||
1318 | public function __toString(): string | ||
1319 | { | ||
1320 | return $this->getSQL(); | ||
1321 | } | ||
1322 | |||
1323 | /** | ||
1324 | * Creates a new named parameter and bind the value $value to it. | ||
1325 | * | ||
1326 | * This method provides a shortcut for {@see Statement::bindValue()} | ||
1327 | * when using prepared statements. | ||
1328 | * | ||
1329 | * The parameter $value specifies the value that you want to bind. If | ||
1330 | * $placeholder is not provided createNamedParameter() will automatically | ||
1331 | * create a placeholder for you. An automatic placeholder will be of the | ||
1332 | * name ':dcValue1', ':dcValue2' etc. | ||
1333 | * | ||
1334 | * Example: | ||
1335 | * <code> | ||
1336 | * $value = 2; | ||
1337 | * $q->eq( 'id', $q->createNamedParameter( $value ) ); | ||
1338 | * $stmt = $q->executeQuery(); // executed with 'id = 2' | ||
1339 | * </code> | ||
1340 | * | ||
1341 | * @link http://www.zetacomponents.org | ||
1342 | * | ||
1343 | * @param string|null $placeHolder The name to bind with. The string must start with a colon ':'. | ||
1344 | * | ||
1345 | * @return string the placeholder name used. | ||
1346 | */ | ||
1347 | public function createNamedParameter( | ||
1348 | mixed $value, | ||
1349 | string|ParameterType|Type|ArrayParameterType $type = ParameterType::STRING, | ||
1350 | ?string $placeHolder = null, | ||
1351 | ): string { | ||
1352 | if ($placeHolder === null) { | ||
1353 | $this->boundCounter++; | ||
1354 | $placeHolder = ':dcValue' . $this->boundCounter; | ||
1355 | } | ||
1356 | |||
1357 | $this->setParameter(substr($placeHolder, 1), $value, $type); | ||
1358 | |||
1359 | return $placeHolder; | ||
1360 | } | ||
1361 | |||
1362 | /** | ||
1363 | * Creates a new positional parameter and bind the given value to it. | ||
1364 | * | ||
1365 | * Attention: If you are using positional parameters with the query builder you have | ||
1366 | * to be very careful to bind all parameters in the order they appear in the SQL | ||
1367 | * statement , otherwise they get bound in the wrong order which can lead to serious | ||
1368 | * bugs in your code. | ||
1369 | * | ||
1370 | * Example: | ||
1371 | * <code> | ||
1372 | * $qb = $conn->createQueryBuilder(); | ||
1373 | * $qb->select('u.*') | ||
1374 | * ->from('users', 'u') | ||
1375 | * ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING)) | ||
1376 | * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING)) | ||
1377 | * </code> | ||
1378 | */ | ||
1379 | public function createPositionalParameter( | ||
1380 | mixed $value, | ||
1381 | string|ParameterType|Type|ArrayParameterType $type = ParameterType::STRING, | ||
1382 | ): string { | ||
1383 | $this->setParameter($this->boundCounter, $value, $type); | ||
1384 | $this->boundCounter++; | ||
1385 | |||
1386 | return '?'; | ||
1387 | } | ||
1388 | |||
1389 | /** | ||
1390 | * @param array<string, true> $knownAliases | ||
1391 | * | ||
1392 | * @throws QueryException | ||
1393 | */ | ||
1394 | private function getSQLForJoins(string $fromAlias, array &$knownAliases): string | ||
1395 | { | ||
1396 | $sql = ''; | ||
1397 | |||
1398 | if (! isset($this->join[$fromAlias])) { | ||
1399 | return $sql; | ||
1400 | } | ||
1401 | |||
1402 | foreach ($this->join[$fromAlias] as $join) { | ||
1403 | if (array_key_exists($join->alias, $knownAliases)) { | ||
1404 | throw NonUniqueAlias::new($join->alias, array_keys($knownAliases)); | ||
1405 | } | ||
1406 | |||
1407 | $sql .= ' ' . $join->type . ' JOIN ' . $join->table . ' ' . $join->alias; | ||
1408 | |||
1409 | if ($join->condition !== null) { | ||
1410 | $sql .= ' ON ' . $join->condition; | ||
1411 | } | ||
1412 | |||
1413 | $knownAliases[$join->alias] = true; | ||
1414 | } | ||
1415 | |||
1416 | foreach ($this->join[$fromAlias] as $join) { | ||
1417 | $sql .= $this->getSQLForJoins($join->alias, $knownAliases); | ||
1418 | } | ||
1419 | |||
1420 | return $sql; | ||
1421 | } | ||
1422 | |||
1423 | /** | ||
1424 | * Deep clone of all expression objects in the SQL parts. | ||
1425 | */ | ||
1426 | public function __clone() | ||
1427 | { | ||
1428 | foreach ($this->from as $key => $from) { | ||
1429 | $this->from[$key] = clone $from; | ||
1430 | } | ||
1431 | |||
1432 | foreach ($this->join as $fromAlias => $joins) { | ||
1433 | foreach ($joins as $key => $join) { | ||
1434 | $this->join[$fromAlias][$key] = clone $join; | ||
1435 | } | ||
1436 | } | ||
1437 | |||
1438 | if (is_object($this->where)) { | ||
1439 | $this->where = clone $this->where; | ||
1440 | } | ||
1441 | |||
1442 | if (is_object($this->having)) { | ||
1443 | $this->having = clone $this->having; | ||
1444 | } | ||
1445 | |||
1446 | foreach ($this->params as $name => $param) { | ||
1447 | if (! is_object($param)) { | ||
1448 | continue; | ||
1449 | } | ||
1450 | |||
1451 | $this->params[$name] = clone $param; | ||
1452 | } | ||
1453 | } | ||
1454 | |||
1455 | /** | ||
1456 | * Enables caching of the results of this query, for given amount of seconds | ||
1457 | * and optionally specified which key to use for the cache entry. | ||
1458 | * | ||
1459 | * @return $this | ||
1460 | */ | ||
1461 | public function enableResultCache(QueryCacheProfile $cacheProfile): self | ||
1462 | { | ||
1463 | $this->resultCacheProfile = $cacheProfile; | ||
1464 | |||
1465 | return $this; | ||
1466 | } | ||
1467 | |||
1468 | /** | ||
1469 | * Disables caching of the results of this query. | ||
1470 | * | ||
1471 | * @return $this | ||
1472 | */ | ||
1473 | public function disableResultCache(): self | ||
1474 | { | ||
1475 | $this->resultCacheProfile = null; | ||
1476 | |||
1477 | return $this; | ||
1478 | } | ||
1479 | } | ||