Passed
Push — master ( 070b5b...1968fb )
by Def
04:32 queued 02:15
created

AbstractConnection::beginTransaction()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 16
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Connection;
6
7
use Closure;
8
use Exception;
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
use Psr\Log\LogLevel;
12
use Throwable;
13
use Yiisoft\Db\Profiler\ProfilerAwareTrait;
14
use Yiisoft\Db\Query\BatchQueryResult;
15
use Yiisoft\Db\Query\BatchQueryResultInterface;
16
use Yiisoft\Db\Query\QueryInterface;
17
use Yiisoft\Db\Schema\TableSchemaInterface;
18
use Yiisoft\Db\Transaction\TransactionInterface;
19
20
/**
21
 * The AbstractConnection class represents a connection to a database. It provides methods for interacting with the
22
 * database, such as executing SQL queries and performing data manipulation.
23
 */
24
abstract class AbstractConnection implements ConnectionInterface, LoggerAwareInterface
25
{
26
    use LoggerAwareTrait;
27
    use ProfilerAwareTrait;
28
29
    protected TransactionInterface|null $transaction = null;
30
    private bool $enableSavepoint = true;
31
    private int $serverRetryInterval = 600;
0 ignored issues
show
introduced by
The private property $serverRetryInterval is not used, and could be removed.
Loading history...
32
    private string $tablePrefix = '';
33
34
    public function beginTransaction(string $isolationLevel = null): TransactionInterface
35
    {
36
        $this->open();
37
        $this->transaction = $this->getTransaction();
38
39
        if ($this->transaction === null) {
40
            $this->transaction = $this->createTransaction();
41
        }
42
43
        if ($this->logger !== null) {
44
            $this->transaction->setLogger($this->logger);
45
        }
46
47
        $this->transaction->begin($isolationLevel);
48
49
        return $this->transaction;
50
    }
51
52
    public function createBatchQueryResult(QueryInterface $query, bool $each = false): BatchQueryResultInterface
53
    {
54
        return new BatchQueryResult($query, $each);
55
    }
56
57
    public function getTablePrefix(): string
58
    {
59
        return $this->tablePrefix;
60
    }
61
62
    public function getTableSchema(string $name, bool $refresh = false): TableSchemaInterface|null
63
    {
64
        return $this->getSchema()->getTableSchema($name, $refresh);
65
    }
66
67
    public function getTransaction(): TransactionInterface|null
68
    {
69
        return $this->transaction && $this->transaction->isActive() ? $this->transaction : null;
70
    }
71
72
    public function isSavepointEnabled(): bool
73
    {
74
        return $this->enableSavepoint;
75
    }
76
77
    public function setEnableSavepoint(bool $value): void
78
    {
79
        $this->enableSavepoint = $value;
80
    }
81
82
    public function setTablePrefix(string $value): void
83
    {
84
        $this->tablePrefix = $value;
85
    }
86
87
    public function transaction(Closure $closure, string $isolationLevel = null): mixed
88
    {
89
        $transaction = $this->beginTransaction($isolationLevel);
90
        $level = $transaction->getLevel();
91
92
        try {
93
            /** @psalm-var mixed $result */
94
            $result = $closure($this);
95
96
            if ($transaction->isActive() && $transaction->getLevel() === $level) {
97
                $transaction->commit();
98
            }
99
        } catch (Throwable $e) {
100
            $this->rollbackTransactionOnLevel($transaction, $level);
101
102
            throw $e;
103
        }
104
105
        return $result;
106
    }
107
108
    /**
109
     * Rolls back given {@see TransactionInterface} object if it's still active and level match. In some cases rollback
110
     * can fail, so this method is fail-safe. Exceptions thrown from rollback will be caught and just logged with
111
     * {@see logger->log()}.
112
     *
113
     * @param TransactionInterface $transaction TransactionInterface object given from {@see beginTransaction()}.
114
     * @param int $level TransactionInterface level just after {@see beginTransaction()} call.
115
     */
116
    private function rollbackTransactionOnLevel(TransactionInterface $transaction, int $level): void
117
    {
118
        if ($transaction->isActive() && $transaction->getLevel() === $level) {
119
            /**
120
             * @link https://github.com/yiisoft/yii2/pull/13347
121
             */
122
            try {
123
                $transaction->rollBack();
124
            } catch (Exception $e) {
125
                $this->logger?->log(LogLevel::ERROR, (string) $e, [__METHOD__]);
126
                /** hide this exception to be able to continue throwing original exception outside */
127
            }
128
        }
129
    }
130
}
131