Passed
Pull Request — master (#384)
by Wilmer
02:50
created

Command::getParams()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
eloc 9
c 1
b 1
f 0
nc 4
nop 1
dl 0
loc 20
ccs 6
cts 8
cp 0.75
crap 4.25
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Command;
6
7
use Closure;
8
use DateInterval;
9
use JsonException;
10
use Psr\Log\LoggerAwareTrait;
11
use Psr\Log\LogLevel;
12
use Throwable;
13
use Yiisoft\Cache\CacheInterface;
14
use Yiisoft\Cache\Dependency\Dependency;
15
use Yiisoft\Db\Cache\QueryCache;
16
use Yiisoft\Db\Connection\ConnectionInterface;
17
use Yiisoft\Db\Exception\Exception;
18
use Yiisoft\Db\Exception\InvalidArgumentException;
19
use Yiisoft\Db\Exception\InvalidConfigException;
20
use Yiisoft\Db\Exception\NotSupportedException;
21
use Yiisoft\Db\Expression\Expression;
22
use Yiisoft\Db\Query\Data\DataReaderInterface;
23
use Yiisoft\Db\Query\QueryInterface;
24
use Yiisoft\Db\Transaction\TransactionInterface;
25
use Yiisoft\Profiler\ProfilerAwareInterface;
26
use Yiisoft\Profiler\ProfilerAwareTrait;
27
28
use function explode;
29
use function get_resource_type;
30
use function is_array;
31
use function is_bool;
32
use function is_object;
33
use function is_resource;
34
use function is_string;
35
use function stream_get_contents;
36
use function strncmp;
37
use function strtr;
38
39
/**
40
 * Command represents a SQL statement to be executed against a database.
41
 *
42
 * A command object is usually created by calling {@see ConnectionInterface::createCommand()}.
43
 *
44
 * The SQL statement it represents can be set via the {@see sql} property.
45
 *
46
 * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call {@see execute()}.
47
 * To execute a SQL statement that returns a result data set (such as SELECT), use {@see queryAll()}, {@see queryOne()},
48
 * {@see queryColumn()}, {@see queryScalar()}, or {@see query()}.
49
 *
50
 * For example,
51
 *
52
 * ```php
53
 * $users = $connectionInterface->createCommand('SELECT * FROM user')->queryAll();
54
 * ```
55
 *
56
 * Command supports SQL statement preparation and parameter binding.
57
 *
58
 * Call {@see bindValue()} to bind a value to a SQL parameter;
59
 * Call {@see bindParam()} to bind a PHP variable to a SQL parameter.
60
 *
61
 * When binding a parameter, the SQL statement is automatically prepared. You may also call {@see prepare()} explicitly
62
 * to prepare a SQL statement.
63
 *
64
 * Command also supports building SQL statements by providing methods such as {@see insert()}, {@see update()}, etc.
65
 *
66
 * For example, the following code will create and execute an INSERT SQL statement:
67
 *
68
 * ```php
69
 * $connectionInterface->createCommand()->insert(
70
 *     'user',
71
 *     ['name' => 'Sam', 'age' => 30],
72
 * )->execute();
73
 * ```
74
 *
75
 * To build SELECT SQL statements, please use {@see QueryInterface} instead.
76
 */
77
abstract class Command implements CommandInterface, ProfilerAwareInterface
78
{
79
    use LoggerAwareTrait;
80
    use ProfilerAwareTrait;
81
82
    protected string|null $isolationLevel = null;
83
    protected string|null $refreshTableName = null;
84
    protected Closure|null $retryHandler = null;
85
    protected int|null $queryCacheDuration = null;
86
    private string $sql = '';
87
    protected Dependency|null $queryCacheDependency = null;
88
    /** * @var ParamInterface[] */
89
    protected array $params = [];
90
91
    public function __construct(protected QueryCache $queryCache)
92
    {
93
    }
94
95
    public function addCheck(string $name, string $table, string $expression): static
96
    {
97
        $sql = $this->queryBuilder()->addCheck($name, $table, $expression);
98
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
99
    }
100
101
    public function addColumn(string $table, string $column, string $type): static
102
    {
103
        $sql = $this->queryBuilder()->addColumn($table, $column, $type);
104
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
105
    }
106
107
    /**
108
     * @throws \Exception
109
     */
110
    public function addCommentOnColumn(string $table, string $column, string $comment): static
111
    {
112
        $sql = $this->queryBuilder()->addCommentOnColumn($table, $column, $comment);
113
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
114
    }
115
116
    /**
117
     * @throws \Exception
118
     */
119
    public function addCommentOnTable(string $table, string $comment): static
120
    {
121
        $sql = $this->queryBuilder()->addCommentOnTable($table, $comment);
122 1779
        return $this->setSql($sql);
123
    }
124 1779
125 1779
    /**
126 1779
     * @throws Exception|NotSupportedException
127 1779
     */
128
    public function addDefaultValue(string $name, string $table, string $column, mixed $value): static
129
    {
130
        $sql = $this->queryBuilder()->addDefaultValue($name, $table, $column, $value);
131
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
132
    }
133
134
    /**
135
     * @throws Exception|InvalidArgumentException
136
     */
137
    public function addForeignKey(
138
        string $name,
139 10
        string $table,
140
        array|string $columns,
141 10
        string $refTable,
142 10
        array|string $refColumns,
143 10
        string $delete = null,
144
        string $update = null
145
    ): static {
146
        $sql = $this->queryBuilder()->addForeignKey(
147
            $name,
148
            $table,
149
            $columns,
150
            $refTable,
151 5
            $refColumns,
152
            $delete,
153 5
            $update
154 5
        );
155
156
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
157
    }
158
159
    public function addPrimaryKey(string $name, string $table, array|string $columns): static
160
    {
161
        $sql = $this->queryBuilder()->addPrimaryKey($name, $table, $columns);
162 1749
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
163
    }
164 1749
165
    public function addUnique(string $name, string $table, array|string $columns): static
166
    {
167
        $sql = $this->queryBuilder()->addUnique($name, $table, $columns);
168
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
169
    }
170
171
    public function alterColumn(string $table, string $column, string $type): static
172
    {
173
        $sql = $this->queryBuilder()->alterColumn($table, $column, $type);
174
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
175
    }
176
177
    /**
178
     * @throws Exception|InvalidArgumentException
179
     */
180
    public function batchInsert(string $table, array $columns, iterable $rows): static
181 339
    {
182
        $table = $this->queryBuilder()->quoter()->quoteSql($table);
183 339
184 339
        /** @psalm-var string[] $columns */
185 339
        foreach ($columns as &$column) {
186 339
            $column = $this->queryBuilder()->quoter()->quoteSql($column);
187
        }
188
189 339
        unset($column);
190
191
        $params = [];
192
        $sql = $this->queryBuilder()->batchInsert($table, $columns, $rows, $params);
193
194
        $this->setRawSql($sql);
195
        $this->bindValues($params);
196
        return $this;
197
    }
198
199
    abstract public function bindValue(int|string $name, mixed $value, int $dataType = null): static;
200
201
    abstract public function bindValues(array $values): static;
202
203
    public function cache(int $duration = null, Dependency $dependency = null): static
204
    {
205 41
        $this->queryCacheDuration = $duration ?? $this->queryCache->getDuration();
206
        $this->queryCacheDependency = $dependency;
207 41
        return $this;
208 41
    }
209 41
210 41
    /**
211
     * @throws Exception|NotSupportedException
212
     */
213 41
    public function checkIntegrity(string $schema, string $table, bool $check = true): static
214
    {
215
        $sql = $this->queryBuilder()->checkIntegrity($schema, $table, $check);
216
        return $this->setSql($sql);
217
    }
218
219
    /**
220
     * @throws Exception|InvalidArgumentException
221
     */
222
    public function createIndex(
223
        string $name,
224
        string $table,
225 929
        array|string $columns,
226
        string $indexType = null,
227 929
        string $indexMethod = null
228 596
    ): static {
229
        $sql = $this->queryBuilder()->createIndex($name, $table, $columns, $indexType, $indexMethod);
230
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
231 652
    }
232
233 652
    public function createTable(string $table, array $columns, string $options = null): static
234 652
    {
235 20
        $sql = $this->queryBuilder()->createTable($table, $columns, $options);
236
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
237
    }
238 652
239 565
    /**
240 290
     * @throws Exception|InvalidConfigException|NotSupportedException
241 10
     */
242 283
    public function createView(string $viewName, QueryInterface|string $subquery): static
243 135
    {
244 171
        $sql = $this->queryBuilder()->createView($viewName, $subquery);
245 171
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
246
    }
247
248
    /**
249 652
     * @throws Exception|InvalidArgumentException
250 652
     */
251
    public function delete(string $table, array|string $condition = '', array $params = []): static
252
    {
253
        $sql = $this->queryBuilder()->delete($table, $condition, $params);
254
        return $this->setSql($sql)->bindValues($params);
255
    }
256
257
    public function dropCheck(string $name, string $table): static
258
    {
259
        $sql = $this->queryBuilder()->dropCheck($name, $table);
260
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
261
    }
262
263
    public function dropColumn(string $table, string $column): static
264
    {
265
        $sql = $this->queryBuilder()->dropColumn($table, $column);
266
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
267
    }
268
269
    public function dropCommentFromColumn(string $table, string $column): static
270
    {
271
        $sql = $this->queryBuilder()->dropCommentFromColumn($table, $column);
272
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
273 1735
    }
274
275 1735
    public function dropCommentFromTable(string $table): static
276 59
    {
277
        $sql = $this->queryBuilder()->dropCommentFromTable($table);
278 59
        return $this->setSql($sql);
279
    }
280
281 1735
    /**
282
     * @throws Exception|NotSupportedException
283 1735
     */
284
    public function dropDefaultValue(string $name, string $table): static
285 26
    {
286
        $sql = $this->queryBuilder()->dropDefaultValue($name, $table);
287
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
288 1735
    }
289 1710
290
    public function dropForeignKey(string $name, string $table): static
291 427
    {
292
        $sql = $this->queryBuilder()->dropForeignKey($name, $table);
293
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
294
    }
295 1735
296 1735
    public function dropIndex(string $name, string $table): static
297 3
    {
298 3
        $sql = $this->queryBuilder()->dropIndex($name, $table);
299 3
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
300
    }
301 3
302
    public function dropPrimaryKey(string $name, string $table): static
303 1735
    {
304
        $sql = $this->queryBuilder()->dropPrimaryKey($name, $table);
305
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
306
    }
307
308
    public function dropTable(string $table): static
309
    {
310 375
        $sql = $this->queryBuilder()->dropTable($table);
311
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
312 375
    }
313 375
314
    public function dropUnique(string $name, string $table): static
315
    {
316
        $sql = $this->queryBuilder()->dropUnique($name, $table);
317
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
318
    }
319
320
    public function dropView(string $viewName): static
321
    {
322
        $sql = $this->queryBuilder()->dropView($viewName);
323
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
324
    }
325
326
    public function getParams(bool $asValues = true): array
327
    {
328
        if (!$asValues) {
329
            return $this->params;
330
        }
331
332
        $buildParams = [];
333 2
334
        /** @psalm-var ParamInterface|array $value */
335 2
        foreach ($this->params as $name => $value) {
336
            if ($value instanceof ParamInterface) {
337 2
                /** @var mixed */
338 2
                $buildParams[$name] = $value->getValue();
339
            } else {
340
                /** @var mixed */
341 2
                $buildParams[$name] = $value;
342 2
            }
343
        }
344
345
        return $buildParams;
346
    }
347
348
    /**
349 2
     * @throws \Exception
350
     */
351 2
    public function getRawSql(): string
352
    {
353
        if (empty($this->params)) {
354
            return $this->sql;
355
        }
356
357
        $params = [];
358
359 1408
        /** @var mixed $value */
360
        foreach ($this->params as $name => $value) {
361 1408
            if (is_string($name) && strncmp(':', $name, 1)) {
362 1017
                $name = ':' . $name;
363
            }
364
365 1408
            if ($value instanceof ParamInterface) {
366 1408
                /** @var mixed */
367
                $value = $value->getValue();
368
            }
369
370
            if (is_string($value)) {
371
                /** @var mixed */
372
                $params[$name] = $this->queryBuilder()->quoter()->quoteValue($value);
373
            } elseif (is_bool($value)) {
374
                /** @var string */
375
                $params[$name] = $value ? 'TRUE' : 'FALSE';
376
            } elseif ($value === null) {
377
                $params[$name] = 'NULL';
378
            } elseif ((!is_object($value) && !is_resource($value)) || $value instanceof Expression) {
379
                /** @var mixed */
380
                $params[$name] = $value;
381
            }
382 7
        }
383
384 7
        if (!isset($params[1])) {
385 7
            return strtr($this->sql, $params);
386
        }
387
388 7
        $sql = '';
389
390 7
        foreach (explode('?', $this->sql) as $i => $part) {
391
            $sql .= (string) $params[$i] . $part;
392 7
        }
393
394
        return $sql;
395
    }
396
397
    public function getSql(): string
398
    {
399
        return $this->sql;
400
    }
401
402
    /**
403
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
404
     */
405
    public function insert(string $table, QueryInterface|array $columns): static
406
    {
407
        $params = [];
408
        $sql = $this->queryBuilder()->insert($table, $columns, $params);
409
        return $this->setSql($sql)->bindValues($params);
410
    }
411 1779
412
    abstract public function insertEx(string $table, array $columns): bool|array;
413 1779
414 1387
    public function noCache(): static
415
    {
416
        $this->queryCacheDuration = -1;
417 1353
        return $this;
418
    }
419 1353
420 1353
    /**
421
     * Executes the SQL statement.
422
     *
423 1353
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
424 2
     * No result set will be returned.
425 2
     *
426
     * @throws Throwable
427 1353
     * @throws Exception execution failed.
428
     *
429 1353
     * @return int number of rows affected by the execution.
430 1353
     *
431
     * @psalm-suppress MixedReturnStatement
432
     * @psalm-suppress MixedInferredReturnType
433
     */
434 1353
    public function execute(): int
435
    {
436
        $sql = $this->getSql();
437
438
        if ($sql === '') {
439
            return 0;
440
        }
441
442
        return $this->queryInternal((int) static::QUERY_MODE_NONE);
443
    }
444
445
    /**
446
     * @psalm-suppress MixedReturnStatement
447 35
     * @psalm-suppress MixedInferredReturnType
448
     */
449 35
    public function query(): DataReaderInterface
450
    {
451
        return $this->queryInternal((int) static::QUERY_MODE_CURSOR);
452
    }
453
454
    public function queryAll(): array
455
    {
456
        /** @psalm-var array<array-key, array<mixed>>|null */
457
        $results = $this->queryInternal((int) static::QUERY_MODE_ALL);
458
459
        return $results ?? [];
460
    }
461
462
    public function queryColumn(): array
463
    {
464
        /** @psalm-var mixed */
465 1589
        $results = $this->queryInternal(self::QUERY_MODE_COLUMN);
466
467 1589
        return is_array($results) ? $results : [];
468
    }
469
470
    public function queryOne(): array|null
471
    {
472
        /** @psalm-var mixed */
473
        $results = $this->queryInternal(self::QUERY_MODE_ROW);
474
475
        return is_array($results) ? $results : null;
476
    }
477
478
    /**
479
     * @psalm-suppress MixedReturnStatement
480
     * @psalm-suppress MixedInferredReturnType
481
     */
482
    public function queryScalar(): bool|string|null|int|float
483
    {
484
        $firstRow = $this->queryInternal((int) static::QUERY_MODE_ROW);
485 471
486
        if (!is_array($firstRow)) {
487 471
            return false;
488
        }
489
490
        /** @var mixed */
491
        $result = current($firstRow);
492
493
        if (is_resource($result) && get_resource_type($result) === 'stream') {
494
            return stream_get_contents($result);
495
        }
496
497
        return $result;
498
    }
499
500 450
    public function renameColumn(string $table, string $oldName, string $newName): static
501
    {
502 450
        $sql = $this->queryBuilder()->renameColumn($table, $oldName, $newName);
503
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
504 450
    }
505
506
    public function renameTable(string $table, string $newName): static
507
    {
508 450
        $sql = $this->queryBuilder()->renameTable($table, $newName);
509
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
510
    }
511
512
    /**
513
     * @throws Exception|NotSupportedException
514
     */
515
    public function resetSequence(string $table, int|string $value = null): static
516
    {
517
        $sql = $this->queryBuilder()->resetSequence($table, $value);
518
        return $this->setSql($sql);
519
    }
520
521 43
    public function setRawSql(string $sql): static
522
    {
523 43
        if ($sql !== $this->sql) {
524
            $this->cancel();
525
            $this->reset();
526
            $this->sql = $sql;
527
        }
528
529
        return $this;
530
    }
531
532
    public function setSql(string $sql): static
533
    {
534
        $this->cancel();
535
        $this->reset();
536
        $this->sql = $this->queryBuilder()->quoter()->quoteSql($sql);
537
538
        return $this;
539
    }
540
541
    public function truncateTable(string $table): static
542
    {
543
        $sql = $this->queryBuilder()->truncateTable($table);
544
        return $this->setSql($sql);
545
    }
546
547
    /**
548 152
     * @throws Exception|InvalidArgumentException
549
     */
550 152
    public function update(string $table, array $columns, array|string $condition = '', array $params = []): static
551 152
    {
552 137
        $sql = $this->queryBuilder()->update($table, $columns, $condition, $params);
553
        return $this->setSql($sql)->bindValues($params);
554
    }
555
556
    /**
557
     * @throws Exception|InvalidConfigException|JsonException|NotSupportedException
558
     */
559
    public function upsert(
560
        string $table,
561
        QueryInterface|array $insertColumns,
562
        bool|array $updateColumns = true,
563
        array $params = []
564
    ): static {
565
        $sql = $this->queryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);
566
        return $this->setSql($sql)->bindValues($params);
567
    }
568
569
    /**
570
     * Returns the cache key for the query.
571
     *
572
     * @param string $rawSql the raw SQL with parameter values inserted into the corresponding placeholders.
573
     *
574
     * @throws JsonException
575
     *
576
     * @return array the cache key.
577
     */
578
    abstract protected function getCacheKey(int $queryMode, string $rawSql): array;
579
580 41
    /**
581
     * @param int $queryMode - one from modes QUERY_MODE_*
582 41
     *
583
     * @throws Exception|Throwable
584 41
     */
585
    protected function queryInternal(int $queryMode): mixed
586 41
    {
587
        if ($queryMode === static::QUERY_MODE_NONE || $queryMode === static::QUERY_MODE_CURSOR) {
588 41
            return $this->queryWithoutCache($this->getRawSql(), $queryMode);
589
        }
590 41
591 41
        return $this->queryWithCache($queryMode);
592
    }
593 41
594
    /**
595
     * Performs the actual DB query of a SQL statement.
596
     *
597
     * @param int $queryMode -  one from modes QUERY_MODE_*
598
     *
599
     * @throws Exception|Throwable If the query causes any problem.
600
     *
601
     * @return mixed The method execution result.
602
     */
603
    protected function queryWithCache(int $queryMode): mixed
604
    {
605
        $rawSql = $this->getRawSql();
606
607
        $cacheKey = $this->getCacheKey($queryMode, $rawSql);
608
        /** @psalm-var array{CacheInterface, DateInterval|int|null, Dependency|null} */
609
        $info = $this->queryCache->info($this->queryCacheDuration, $this->queryCacheDependency);
610
        /** @var mixed */
611
        $cacheResult = $this->getFromCacheInfo($info, $cacheKey);
612
613
        if ($cacheResult) {
614
            $this->logger?->log(LogLevel::DEBUG, 'Get query result from cache', [self::class . '::query']);
615
            return $cacheResult;
616
        }
617
618
        /** @var mixed */
619
        $result = $this->queryWithoutCache($rawSql, $queryMode);
620
        $this->setToCacheInfo($info, $cacheKey, $result);
621
622
        return $result;
623
    }
624 30
625
    protected function queryWithoutCache(string $rawSql, int $queryMode): mixed
626 30
    {
627 30
        $isReadMode = $this->isReadMode($queryMode);
628
        $logCategory = self::class . '::' . ($isReadMode ? 'query' : 'execute');
629
630
        $this->logQuery($rawSql, $logCategory);
631
632
        $this->prepare($isReadMode);
633
        try {
634
            $this->profiler?->begin($rawSql, [$logCategory]);
635
636
            $this->internalExecute($rawSql);
637
638
            /** @var mixed */
639
            $result = $this->internalGetQueryResult($queryMode);
640
641
            $this->profiler?->end($rawSql, [$logCategory]);
642
643
            if (!$isReadMode) {
644
                $this->refreshTableSchema();
645
            }
646
        } catch (Exception $e) {
647
            $this->profiler?->end($rawSql, [$logCategory]);
648
            throw $e;
649
        }
650
651
        return $result;
652
    }
653
654
    /**
655
     * Executes a prepared statement.
656
     *
657
     * @param string|null $rawSql the rawSql if it has been created.
658
     *
659
     * @throws Exception|Throwable
660
     */
661
    abstract protected function internalExecute(string|null $rawSql): void;
662
663 77
    abstract protected function internalGetQueryResult(int $queryMode): mixed;
664
665 77
    /**
666 77
     * Logs the current database query if query logging is enabled and returns the profiling token if profiling is
667
     * enabled.
668
     */
669
    protected function logQuery(string $rawSql, string $category): void
670
    {
671
        $this->logger?->log(LogLevel::INFO, $rawSql, [$category]);
672
    }
673
674
    /**
675
     * Refreshes table schema, which was marked by {@see requireTableSchemaRefresh()}.
676
     */
677
    protected function refreshTableSchema(): void
678
    {
679
        if ($this->refreshTableName !== null) {
680
            $this->queryBuilder()->schema()->refreshTableSchema($this->refreshTableName);
681
        }
682
    }
683
684
    /**
685
     * Marks a specified table schema to be refreshed after command execution.
686
     *
687
     * @param string $name Name of the table, which schema should be refreshed.
688
     */
689
    protected function requireTableSchemaRefresh(string $name): static
690
    {
691
        $this->refreshTableName = $name;
692
        return $this;
693
    }
694
695
    /**
696 42
     * Marks the command to be executed in transaction.
697
     *
698 42
     * @param string|null $isolationLevel The isolation level to use for this transaction.
699 42
     *
700
     * {@see TransactionInterface::begin()} for details.
701
     */
702
    protected function requireTransaction(string $isolationLevel = null): static
703
    {
704
        $this->isolationLevel = $isolationLevel;
705
        return $this;
706
    }
707
708
    protected function reset(): void
709
    {
710
        $this->sql = '';
711
        $this->params = [];
712
        $this->refreshTableName = null;
713
        $this->isolationLevel = null;
714
        $this->retryHandler = null;
715
    }
716
717
    /**
718
     * Sets a Closure (e.g. anonymous function) that is called when {@see Exception} is thrown when executing the
719
     * command. The signature of the Closure should be:.
720
     *
721
     * ```php
722 58
     * function (Exceptions $e, $attempt)
723
     * {
724 58
     *     // return true or false (whether to retry the command or rethrow $e)
725 58
     * }
726
     * ```
727
     *
728
     * The Closure will receive a database exception thrown and a current attempt (to execute the command) number
729
     * starting from 1.
730
     *
731
     * @param Closure|null $handler A PHP callback to handle database exceptions.
732
     */
733
    protected function setRetryHandler(Closure|null $handler): static
734
    {
735
        $this->retryHandler = $handler;
736 10
        return $this;
737
    }
738 10
739 10
    /**
740
     * @psalm-param array{CacheInterface, DateInterval|int|null, Dependency|null} $info
741
     */
742
    private function getFromCacheInfo(array|null $info, array $cacheKey): mixed
743
    {
744
        if (!is_array($info)) {
0 ignored issues
show
introduced by
The condition is_array($info) is always true.
Loading history...
745
            return null;
746
        }
747
748
        $cache = $info[0];
749 12
750
        /** @var mixed */
751 12
        $result = $cache->getOrSet(
752 12
            $cacheKey,
753
            static fn () => null,
754
        );
755
756
        if (is_array($result) && isset($result[0])) {
757
            $this->logger?->log(LogLevel::DEBUG, 'Query result served from cache', [self::class . '::query']);
758
759
            return $result[0];
760
        }
761
762 5
        return null;
763
    }
764 5
765 5
    /**
766
     * @psalm-param array{CacheInterface, DateInterval|int|null, Dependency|null} $info
767
     */
768
    private function setToCacheInfo(array|null $info, array $cacheKey, mixed $result): void
769
    {
770
        if (!is_array($info)) {
0 ignored issues
show
introduced by
The condition is_array($info) is always true.
Loading history...
771
            return;
772
        }
773
774
        $cache = $info[0];
775
776
        $cache->getOrSet(
777
            $cacheKey,
778
            static fn (): array => [$result],
779
            $info[1],
780 2
            $info[2]
781
        );
782 2
783 2
        $this->logger?->log(LogLevel::DEBUG, 'Saved query result in cache', [self::class . '::query']);
784
    }
785
786
    private function isReadMode(int $queryMode): bool
787
    {
788
        return $queryMode !== static::QUERY_MODE_NONE;
789
    }
790
}
791