Passed
Branch dev (f56f10)
by Wilmer
04:41 queued 01:34
created

Command::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Command;
6
7
use JsonException;
8
use PDO;
9
use PDOStatement;
10
use Psr\Log\LogLevel;
11
use Throwable;
12
use Yiisoft\Cache\CacheInterface;
13
use Yiisoft\Cache\Dependency\Dependency;
14
use Yiisoft\Db\AwareTrait\LoggerAwareTrait;
15
use Yiisoft\Db\AwareTrait\ProfilerAwareTrait;
16
use Yiisoft\Db\Cache\QueryCache;
17
use Yiisoft\Db\Connection\ConnectionInterface;
18
use Yiisoft\Db\Exception\Exception;
19
use Yiisoft\Db\Exception\InvalidArgumentException;
20
use Yiisoft\Db\Exception\InvalidConfigException;
21
use Yiisoft\Db\Exception\NotSupportedException;
22
use Yiisoft\Db\Expression\Expression;
23
use Yiisoft\Db\Pdo\PdoValue;
24
use Yiisoft\Db\Query\Data\DataReader;
25
use Yiisoft\Db\Query\QueryInterface;
26
use Yiisoft\Db\Transaction\TransactionInterface;
27
28
use function array_map;
29
use function call_user_func_array;
30
use function explode;
31
use function get_resource_type;
32
use function is_array;
33
use function is_bool;
34
use function is_object;
35
use function is_resource;
36
use function is_string;
37
use function stream_get_contents;
38
use function strncmp;
39
use function strtr;
40
41
/**
42
 * Command represents a SQL statement to be executed against a database.
43
 *
44
 * A command object is usually created by calling {@see ConnectionInterface::createCommand()}.
45
 *
46
 * The SQL statement it represents can be set via the {@see sql} property.
47
 *
48
 * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call {@see execute()}.
49
 * To execute a SQL statement that returns a result data set (such as SELECT), use {@see queryAll()},
50
 * {@see queryOne()}, {@see queryColumn()}, {@see queryScalar()}, or {@see query()}.
51
 *
52
 * For example,
53
 *
54
 * ```php
55
 * $users = $connectionInterface->createCommand('SELECT * FROM user')->queryAll();
56
 * ```
57
 *
58
 * Command supports SQL statement preparation and parameter binding.
59
 *
60
 * Call {@see bindValue()} to bind a value to a SQL parameter;
61
 * Call {@see bindParam()} to bind a PHP variable to a SQL parameter.
62
 *
63
 * When binding a parameter, the SQL statement is automatically prepared. You may also call {@see prepare()} explicitly
64
 * to prepare a SQL statement.
65
 *
66
 * Command also supports building SQL statements by providing methods such as {@see insert()}, {@see update()}, etc.
67
 *
68
 * For example, the following code will create and execute an INSERT SQL statement:
69
 *
70
 * ```php
71
 * $connectionInterface->createCommand()->insert('user', [
72
 *     'name' => 'Sam',
73
 *     'age' => 30,
74
 * ])->execute();
75
 * ```
76
 *
77
 * To build SELECT SQL statements, please use {@see QueryInterface} instead.
78
 *
79
 * For more details and usage information on Command, see the [guide article on Database Access Objects](guide:db-dao).
80
 *
81
 * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in
82
 * {@see sql}.
83
 * @property string $sql The SQL statement to be executed.
84
 */
85
abstract class Command implements CommandInterface
86
{
87
    use LoggerAwareTrait;
88
    use ProfilerAwareTrait;
89
90
    protected ?string $isolationLevel = null;
91
    protected array $params = [];
92
    protected ?PDOStatement $pdoStatement = null;
93
    protected array $pendingParams = [];
94
    protected ?string $refreshTableName = null;
95
    /** @var callable|null */
96
    protected $retryHandler = null;
97
    private int $fetchMode = PDO::FETCH_ASSOC;
98
    private ?int $queryCacheDuration = null;
99
    private ?string $sql = null;
100
    private ?Dependency $queryCacheDependency = null;
101
102
    public function __construct(private QueryCache $queryCache)
103
    {
104
    }
105
106
    /**
107
     * Returns the cache key for the query.
108
     *
109
     * @param string $method method of PDOStatement to be called.
110
     * @param int|null $fetchMode the result fetch mode.
111
     * Please refer to [PHP manual](https://secure.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid
112
     * fetch modes.
113
     * @param string $rawSql the raw SQL with parameter values inserted into the corresponding placeholders.
114
     *
115
     * @throws JsonException
116
     *
117
     * @return array the cache key.
118
     */
119
    abstract protected function getCacheKey(string $method, ?int $fetchMode, string $rawSql): array;
120
121
    /**
122
     * Executes a prepared statement.
123
     *
124
     * It's a wrapper around {@see PDOStatement::execute()} to support transactions and retry handlers.
125
     *
126
     * @param string|null $rawSql the rawSql if it has been created.
127
     *
128
     * @throws Exception|Throwable
129
     */
130
    abstract protected function internalExecute(?string $rawSql): void;
131
132
    public function addCheck(string $name, string $table, string $expression): self
133
    {
134
        $sql = $this->queryBuilder()->addCheck($name, $table, $expression);
135
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
136
    }
137
138
    public function addColumn(string $table, string $column, string $type): self
139
    {
140
        $sql = $this->queryBuilder()->addColumn($table, $column, $type);
141
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
142
    }
143
144
    /**
145
     * @throws \Exception
146
     */
147
    public function addCommentOnColumn(string $table, string $column, string $comment): self
148
    {
149
        $sql = $this->queryBuilder()->addCommentOnColumn($table, $column, $comment);
150
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
151
    }
152
153
    /**
154
     * @throws \Exception
155
     */
156
    public function addCommentOnTable(string $table, string $comment): self
157
    {
158
        $sql = $this->queryBuilder()->addCommentOnTable($table, $comment);
159
        return $this->setSql($sql);
160
    }
161
162
    /**
163
     * @throws Exception|NotSupportedException
164
     */
165
    public function addDefaultValue(string $name, string $table, string $column, mixed $value): self
166
    {
167
        $sql = $this->queryBuilder()->addDefaultValue($name, $table, $column, $value);
168
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
169
    }
170
171
    /**
172
     * @throws Exception|InvalidArgumentException
173
     */
174
    public function addForeignKey(
175
        string $name,
176
        string $table,
177
        array|string $columns,
178
        string $refTable,
179
        array|string $refColumns,
180
        ?string $delete = null,
181
        ?string $update = null
182
    ): self {
183
        $sql = $this->queryBuilder()->addForeignKey(
184
            $name,
185
            $table,
186
            $columns,
187
            $refTable,
188
            $refColumns,
189
            $delete,
190
            $update
191
        );
192
193
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
194
    }
195
196
    public function addPrimaryKey(string $name, string $table, array|string $columns): self
197
    {
198
        $sql = $this->queryBuilder()->addPrimaryKey($name, $table, $columns);
199
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
200
    }
201
202
    public function addUnique(string $name, string $table, array|string $columns): self
203
    {
204
        $sql = $this->queryBuilder()->addUnique($name, $table, $columns);
205
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
206
    }
207
208
    public function alterColumn(string $table, string $column, string $type): self
209
    {
210
        $sql = $this->queryBuilder()->alterColumn($table, $column, $type);
211
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
212
    }
213
214
    /**
215
     * @throws Exception|InvalidArgumentException
216
     */
217
    public function batchInsert(string $table, array $columns, iterable $rows): self
218
    {
219
        $table = $this->queryBuilder()->quoter()->quoteSql($table);
220
        $columns = array_map(fn ($column) => $this->queryBuilder()->quoter()->quoteSql($column), $columns);
221
        $params = [];
222
        $sql = $this->queryBuilder()->batchInsert($table, $columns, $rows, $params);
223
224
        $this->setRawSql($sql);
225
        $this->bindValues($params);
226
227
        return $this;
228
    }
229
230
    public function bindParam(int|string $name, mixed &$value, ?int $dataType = null, ?int $length = null, mixed $driverOptions = null): self
231
    {
232
        $this->prepare();
233
234
        if ($dataType === null) {
235
            $dataType = $this->queryBuilder()->schema()->getPdoType($value);
236
        }
237
238
        if ($length === null) {
239
            $this->pdoStatement->bindParam($name, $value, $dataType);
0 ignored issues
show
Bug introduced by
The method bindParam() does not exist on null. ( Ignorable by Annotation )

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

239
            $this->pdoStatement->/** @scrutinizer ignore-call */ 
240
                                 bindParam($name, $value, $dataType);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
240
        } elseif ($driverOptions === null) {
241
            $this->pdoStatement->bindParam($name, $value, $dataType, $length);
242
        } else {
243
            $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
244
        }
245
246
        $this->params[$name] = &$value;
247
248
        return $this;
249
    }
250
251
    public function bindValue(int|string $name, mixed $value, ?int $dataType = null): self
252
    {
253
        if ($dataType === null) {
254
            $dataType = $this->queryBuilder()->schema()->getPdoType($value);
255
        }
256
257
        $this->pendingParams[$name] = [$value, $dataType];
258
259
        $this->params[$name] = $value;
260
261
        return $this;
262
    }
263
264
    public function bindValues(array $values): self
265
    {
266
        if (empty($values)) {
267
            return $this;
268
        }
269
270
        foreach ($values as $name => $value) {
271
            if (is_array($value)) { // TODO: Drop in Yii 2.1
272
                $this->pendingParams[$name] = $value;
273
                $this->params[$name] = $value[0];
274
            } elseif ($value instanceof PdoValue) {
275
                $this->pendingParams[$name] = [$value->getValue(), $value->getType()];
276
                $this->params[$name] = $value->getValue();
277
            } else {
278
                $type = $this->queryBuilder()->schema()->getPdoType($value);
279
280
                $this->pendingParams[$name] = [$value, $type];
281
                $this->params[$name] = $value;
282
            }
283
        }
284
285
        return $this;
286
    }
287
288
    public function cache(?int $duration = null, Dependency $dependency = null): self
289
    {
290
        $this->queryCacheDuration = $duration ?? $this->queryCache->getDuration();
291
        $this->queryCacheDependency = $dependency;
292
        return $this;
293
    }
294
295
    public function cancel(): void
296
    {
297
        $this->pdoStatement = null;
298
    }
299
300
    /**
301
     * @throws Exception|NotSupportedException
302
     */
303
    public function checkIntegrity(string $schema, string $table, bool $check = true): self
304
    {
305
        $sql = $this->queryBuilder()->checkIntegrity($schema, $table, $check);
306
        return $this->setSql($sql);
307
    }
308
309
    /**
310
     * @throws Exception|InvalidArgumentException
311
     */
312
    public function createIndex(string $name, string $table, array|string $columns, bool $unique = false): self
313
    {
314
        $sql = $this->queryBuilder()->createIndex($name, $table, $columns, $unique);
315
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
316
    }
317
318
    public function createTable(string $table, array $columns, ?string $options = null): self
319
    {
320
        $sql = $this->queryBuilder()->createTable($table, $columns, $options);
321
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
322
    }
323
324
    /**
325
     * @throws Exception|InvalidConfigException|NotSupportedException
326
     */
327
    public function createView(string $viewName, QueryInterface|string $subquery): self
328
    {
329
        $sql = $this->queryBuilder()->createView($viewName, $subquery);
330
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
331
    }
332
333
    /**
334
     * @throws Exception|InvalidArgumentException
335
     */
336
    public function delete(string $table, array|string $condition = '', array $params = []): self
337
    {
338
        $sql = $this->queryBuilder()->delete($table, $condition, $params);
339
        return $this->setSql($sql)->bindValues($params);
340
    }
341
342
    public function dropCheck(string $name, string $table): self
343
    {
344
        $sql = $this->queryBuilder()->dropCheck($name, $table);
345
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
346
    }
347
348
    public function dropColumn(string $table, string $column): self
349
    {
350
        $sql = $this->queryBuilder()->dropColumn($table, $column);
351
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
352
    }
353
354
    public function dropCommentFromColumn(string $table, string $column): self
355
    {
356
        $sql = $this->queryBuilder()->dropCommentFromColumn($table, $column);
357
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
358
    }
359
360
    public function dropCommentFromTable(string $table): self
361
    {
362
        $sql = $this->queryBuilder()->dropCommentFromTable($table);
363
        return $this->setSql($sql);
364
    }
365
366
    /**
367
     * @throws Exception|NotSupportedException
368
     */
369
    public function dropDefaultValue(string $name, string $table): self
370
    {
371
        $sql = $this->queryBuilder()->dropDefaultValue($name, $table);
372
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
373
    }
374
375
    public function dropForeignKey(string $name, string $table): self
376
    {
377
        $sql = $this->queryBuilder()->dropForeignKey($name, $table);
378
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
379
    }
380
381
    public function dropIndex(string $name, string $table): self
382
    {
383
        $sql = $this->queryBuilder()->dropIndex($name, $table);
384
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
385
    }
386
387
    public function dropPrimaryKey(string $name, string $table): self
388
    {
389
        $sql = $this->queryBuilder()->dropPrimaryKey($name, $table);
390
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
391
    }
392
393
    public function dropTable(string $table): self
394
    {
395
        $sql = $this->queryBuilder()->dropTable($table);
396
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
397
    }
398
399
    public function dropUnique(string $name, string $table): self
400
    {
401
        $sql = $this->queryBuilder()->dropUnique($name, $table);
402
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
403
    }
404
405
    public function dropView(string $viewName): self
406
    {
407
        $sql = $this->queryBuilder()->dropView($viewName);
408
        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
409
    }
410
411
    /**
412
     * Executes the SQL statement.
413
     *
414
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
415
     * No result set will be returned.
416
     *
417
     * @throws Throwable
418
     * @throws Exception execution failed.
419
     *
420
     * @return int number of rows affected by the execution.
421
     */
422
    public function execute(): int
423
    {
424
        $sql = $this->getSql();
425
426
        [, $rawSql] = $this->logQuery(__METHOD__);
427
428
        if ($sql === '') {
429
            return 0;
430
        }
431
432
        $this->prepare(false);
433
434
        try {
435
            $this->profiler?->begin((string)$rawSql, [__METHOD__]);
436
437
            $this->internalExecute($rawSql);
438
            $n = $this->pdoStatement->rowCount();
439
440
            $this->profiler?->end((string)$rawSql, [__METHOD__]);
441
442
            $this->refreshTableSchema();
443
444
            return $n;
445
        } catch (Exception $e) {
446
            $this->profiler?->end((string)$rawSql, [__METHOD__]);
447
            throw $e;
448
        }
449
    }
450
451
    /**
452
     * @throws Exception|NotSupportedException
453
     */
454
    public function executeResetSequence(string $table, mixed $value = null): self
455
    {
456
        return $this->resetSequence($table, $value);
457
    }
458
459
    public function getFetchMode(): int
460
    {
461
        return $this->fetchMode;
462
    }
463
464
    public function getParams(): array
465
    {
466
        return $this->params;
467
    }
468
469
    public function getPdoStatement(): ?PDOStatement
470
    {
471
        return $this->pdoStatement;
472
    }
473
474
    /**
475
     * @throws \Exception
476
     */
477
    public function getRawSql(): string
478
    {
479
        if (empty($this->params)) {
480
            return $this->sql;
481
        }
482
483
        $params = [];
484
485
        foreach ($this->params as $name => $value) {
486
            if (is_string($name) && strncmp(':', $name, 1)) {
487
                $name = ':' . $name;
488
            }
489
490
            if (is_string($value)) {
491
                $params[$name] = $this->queryBuilder()->quoter()->quoteValue($value);
492
            } elseif (is_bool($value)) {
493
                $params[$name] = ($value ? 'TRUE' : 'FALSE');
494
            } elseif ($value === null) {
495
                $params[$name] = 'NULL';
496
            } elseif ((!is_object($value) && !is_resource($value)) || $value instanceof Expression) {
497
                $params[$name] = $value;
498
            }
499
        }
500
501
        if (!isset($params[1])) {
502
            return strtr($this->sql, $params);
503
        }
504
505
        $sql = '';
506
507
        foreach (explode('?', $this->sql) as $i => $part) {
508
            $sql .= ($params[$i] ?? '') . $part;
509
        }
510
511
        return $sql;
512
    }
513
514
    public function getSql(): ?string
515
    {
516
        return $this->sql;
517
    }
518
519
    /**
520
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
521
     */
522
    public function insert(string $table, QueryInterface|array $columns): self
523
    {
524
        $params = [];
525
        $sql = $this->queryBuilder()->insert($table, $columns, $params);
526
        return $this->setSql($sql)->bindValues($params);
527
    }
528
529
    public function noCache(): self
530
    {
531
        $this->queryCacheDuration = -1;
532
        return $this;
533
    }
534
535
    public function query(): DataReader
536
    {
537
        return $this->queryInternal('');
538
    }
539
540
    public function queryAll(?int $fetchMode = null): array
541
    {
542
        return $this->queryInternal('fetchAll', $fetchMode);
543
    }
544
545
    public function queryColumn(): array
546
    {
547
        return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN);
548
    }
549
550
    public function queryOne(array|int $fetchMode = null): mixed
551
    {
552
        return $this->queryInternal('fetch', $fetchMode);
553
    }
554
555
    public function queryScalar(): bool|string|null|int
556
    {
557
        $result = $this->queryInternal('fetchColumn', 0);
558
559
        if (is_resource($result) && get_resource_type($result) === 'stream') {
560
            return stream_get_contents($result);
561
        }
562
563
        return $result;
564
    }
565
566
    public function renameColumn(string $table, string $oldName, string $newName): self
567
    {
568
        $sql = $this->queryBuilder()->renameColumn($table, $oldName, $newName);
569
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
570
    }
571
572
    public function renameTable(string $table, string $newName): self
573
    {
574
        $sql = $this->queryBuilder()->renameTable($table, $newName);
575
        return $this->setSql($sql)->requireTableSchemaRefresh($table);
576
    }
577
578
    /**
579
     * @throws Exception|NotSupportedException
580
     */
581
    public function resetSequence(string $table, mixed $value = null): self
582
    {
583
        $sql = $this->queryBuilder()->resetSequence($table, $value);
584
        return $this->setSql($sql);
585
    }
586
587
    public function setFetchMode(int $value): void
588
    {
589
        $this->fetchMode = $value;
590
    }
591
592
    public function setParams(array $value): void
593
    {
594
        $this->params = $value;
595
    }
596
597
    public function setRawSql(string $sql): self
598
    {
599
        if ($sql !== $this->sql) {
600
            $this->cancel();
601
            $this->reset();
602
            $this->sql = $sql;
603
        }
604
605
        return $this;
606
    }
607
608
    public function setSql(string $sql): self
609
    {
610
        $this->cancel();
611
        $this->reset();
612
        $this->sql = $this->queryBuilder()->quoter()->quoteSql($sql);
613
614
        return $this;
615
    }
616
617
    public function truncateTable(string $table): self
618
    {
619
        $sql = $this->queryBuilder()->truncateTable($table);
620
        return $this->setSql($sql);
621
    }
622
623
    /**
624
     * @throws Exception|InvalidArgumentException
625
     */
626
    public function update(string $table, array $columns, array|string $condition = '', array $params = []): self
627
    {
628
        $sql = $this->queryBuilder()->update($table, $columns, $condition, $params);
629
        return $this->setSql($sql)->bindValues($params);
630
    }
631
632
    /**
633
     * @throws Exception|InvalidConfigException|JsonException|NotSupportedException
634
     */
635
    public function upsert(
636
        string $table,
637
        QueryInterface|array $insertColumns,
638
        bool|array $updateColumns = true,
639
        array $params = []
640
    ): self {
641
        $sql = $this->queryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);
642
        return $this->setSql($sql)->bindValues($params);
643
    }
644
645
    /**
646
     * Binds pending parameters that were registered via {@see bindValue()} and {@see bindValues()}.
647
     *
648
     * Note that this method requires an active {@see pdoStatement}.
649
     */
650
    protected function bindPendingParams(): void
651
    {
652
        foreach ($this->pendingParams as $name => $value) {
653
            $this->pdoStatement->bindValue($name, $value[0], $value[1]);
654
        }
655
656
        $this->pendingParams = [];
657
    }
658
659
    /**
660
     * Logs the current database query if query logging is enabled and returns the profiling token if profiling is
661
     * enabled.
662
     *
663
     * @param string $category The log category.
664
     *
665
     * @throws \Exception
666
     *
667
     * @return array Two elements, the first is boolean of whether profiling is enabled or not. The second is
668
     * the rawSql if it has been created.
669
     */
670
    protected function logQuery(string $category): array
671
    {
672
        if ($this->logger !== null) {
673
            $rawSql = $this->getRawSql();
674
            $this->logger->log(LogLevel::INFO, $rawSql, [$category]);
675
        }
676
677
        if ($this->profiler === null) {
678
            return [false, $rawSql ?? null];
679
        }
680
681
        return [true, $rawSql ?? $this->getRawSql()];
682
    }
683
684
    /**
685
     * Performs the actual DB query of a SQL statement.
686
     *
687
     * @param string $method Method of PDOStatement to be called.
688
     * @param array|int|null $fetchMode The result fetch mode.
689
     *
690
     * Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid fetch
691
     * modes. If this parameter is null, the value set in {@see fetchMode} will be used.
692
     *
693
     * @throws Exception|Throwable If the query causes any problem.
694
     *
695
     * @return mixed The method execution result.
696
     */
697
    protected function queryInternal(string $method, array|int $fetchMode = null): mixed
698
    {
699
        [, $rawSql] = $this->logQuery(__CLASS__ . '::query');
700
701
        if ($method !== '') {
702
            $info = $this->queryCache->info($this->queryCacheDuration, $this->queryCacheDependency);
703
704
            if (is_array($info)) {
705
                /* @var $cache CacheInterface */
706
                $cache = $info[0];
707
                $rawSql = $rawSql ?: $this->getRawSql();
708
                $cacheKey = $this->getCacheKey($method, $fetchMode, $rawSql);
0 ignored issues
show
Bug introduced by
It seems like $fetchMode can also be of type array; however, parameter $fetchMode of Yiisoft\Db\Command\Command::getCacheKey() does only seem to accept integer|null, 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

708
                $cacheKey = $this->getCacheKey($method, /** @scrutinizer ignore-type */ $fetchMode, $rawSql);
Loading history...
709
                $result = $cache->getOrSet(
710
                    $cacheKey,
711
                    static fn () => null,
712
                );
713
714
                if (is_array($result) && isset($result[0])) {
715
                    $this->logger?->log(LogLevel::DEBUG, 'Query result served from cache', [__CLASS__ . '::query']);
716
717
                    return $result[0];
718
                }
719
            }
720
        }
721
722
        $this->prepare(true);
723
724
        try {
725
            $this->profiler?->begin((string)$rawSql, [__CLASS__ . '::query']);
726
727
            $this->internalExecute($rawSql);
728
729
            if ($method === '') {
730
                $result = new DataReader($this);
731
            } else {
732
                if ($fetchMode === null) {
733
                    $fetchMode = $this->fetchMode;
734
                }
735
736
                $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
737
738
                $this->pdoStatement->closeCursor();
739
            }
740
741
            $this->profiler?->end((string)$rawSql, [__CLASS__ . '::query']);
742
        } catch (Exception $e) {
743
            $this->profiler?->end((string)$rawSql, [__CLASS__ . '::query']);
744
            throw $e;
745
        }
746
747
        if (isset($cache, $cacheKey, $info)) {
748
            $cache->getOrSet(
749
                $cacheKey,
750
                static fn (): array => [$result],
751
                $info[1],
752
                $info[2]
753
            );
754
755
            $this->logger?->log(LogLevel::DEBUG, 'Saved query result in cache', [__CLASS__ . '::query']);
756
        }
757
758
        return $result;
759
    }
760
761
    /**
762
     * Refreshes table schema, which was marked by {@see requireTableSchemaRefresh()}.
763
     */
764
    protected function refreshTableSchema(): void
765
    {
766
        if ($this->refreshTableName !== null) {
767
            $this->queryBuilder()->schema()->refreshTableSchema($this->refreshTableName);
768
        }
769
    }
770
771
    /**
772
     * Marks a specified table schema to be refreshed after command execution.
773
     *
774
     * @param string $name Name of the table, which schema should be refreshed.
775
     *
776
     * @return static
777
     */
778
    protected function requireTableSchemaRefresh(string $name): self
779
    {
780
        $this->refreshTableName = $name;
781
        return $this;
782
    }
783
784
    /**
785
     * Marks the command to be executed in transaction.
786
     *
787
     * @param string|null $isolationLevel The isolation level to use for this transaction.
788
     *
789
     * {@see TransactionInterface::begin()} for details.
790
     *
791
     * @return static
792
     */
793
    protected function requireTransaction(?string $isolationLevel = null): self
794
    {
795
        $this->isolationLevel = $isolationLevel;
796
        return $this;
797
    }
798
799
    protected function reset(): void
800
    {
801
        $this->sql = null;
802
        $this->pendingParams = [];
803
        $this->params = [];
804
        $this->refreshTableName = null;
805
        $this->isolationLevel = null;
806
        $this->retryHandler = null;
807
    }
808
809
    /**
810
     * Sets a callable (e.g. anonymous function) that is called when {@see Exception} is thrown when executing the
811
     * command. The signature of the callable should be:.
812
     *
813
     * ```php
814
     * function (Exceptions $e, $attempt)
815
     * {
816
     *     // return true or false (whether to retry the command or rethrow $e)
817
     * }
818
     * ```
819
     *
820
     * The callable will receive a database exception thrown and a current attempt (to execute the command) number
821
     * starting from 1.
822
     *
823
     * @param callable|null $handler A PHP callback to handle database exceptions.
824
     *
825
     * @return static
826
     */
827
    protected function setRetryHandler(?callable $handler): self
828
    {
829
        $this->retryHandler = $handler;
830
        return $this;
831
    }
832
}
833