Passed
Pull Request — master (#385)
by Wilmer
02:33
created

Connection::rollbackTransactionOnLevel()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
rs 10
ccs 0
cts 0
cp 0
cc 4
nc 3
nop 2
crap 20
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Connection;
6
7
use Closure;
8
use Psr\Log\LoggerAwareInterface;
9
use Psr\Log\LoggerAwareTrait;
10
use Psr\Log\LogLevel;
11
use Throwable;
12
use Yiisoft\Cache\Dependency\Dependency;
13
use Yiisoft\Db\Cache\QueryCache;
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
use Yiisoft\Profiler\ProfilerAwareInterface;
20
use Yiisoft\Profiler\ProfilerAwareTrait;
21
22
/**
23
 * Connection is the base class for all database connection classes.
24
 */
25
abstract class Connection implements ConnectionInterface, LoggerAwareInterface, ProfilerAwareInterface
26
{
27
    use LoggerAwareTrait;
28
    use ProfilerAwareTrait;
29
30
    protected TransactionInterface|null $transaction = null;
31
    private bool $enableSavepoint = true;
32
    private int $serverRetryInterval = 600;
0 ignored issues
show
introduced by
The private property $serverRetryInterval is not used, and could be removed.
Loading history...
33
    private string $tablePrefix = '';
34
35
    public function __construct(private QueryCache $queryCache)
36
    {
37
    }
38
39
    public function beginTransaction(string $isolationLevel = null): TransactionInterface
40
    {
41
        $this->open();
42
        $this->transaction = $this->getTransaction();
43
44
        if ($this->transaction === null) {
45
            $this->transaction = $this->createTransaction();
46
        }
47
48
        if ($this->logger !== null) {
49
            $this->transaction->setLogger($this->logger);
50
        }
51
52
        $this->transaction->begin($isolationLevel);
53
54
        return $this->transaction;
55
    }
56
57
    public function cache(Closure $closure, int $duration = null, Dependency $dependency = null): mixed
58
    {
59
        $this->queryCache->setInfo(
60
            [$duration ?? $this->queryCache->getDuration(), $dependency]
61
        );
62
        /** @var mixed */
63
        $result = $closure($this);
64
        $this->queryCache->removeLastInfo();
65
66
        return $result;
67
    }
68
69
    public function createBatchQueryResult(QueryInterface $query, bool $each = false): BatchQueryResultInterface
70
    {
71
        return new BatchQueryResult($query, $each);
72
    }
73
74
    public function getTablePrefix(): string
75
    {
76
        return $this->tablePrefix;
77
    }
78
79
    public function getTableSchema(string $name, bool $refresh = false): TableSchemaInterface|null
80
    {
81
        return $this->getSchema()->getTableSchema($name, $refresh);
82
    }
83
84
    public function getTransaction(): TransactionInterface|null
85
    {
86
        return $this->transaction && $this->transaction->isActive() ? $this->transaction : null;
87
    }
88
89
    public function isSavepointEnabled(): bool
90
    {
91
        return $this->enableSavepoint;
92
    }
93
94
    public function noCache(Closure $closure): mixed
95
    {
96
        $queryCache = $this->queryCache;
97
        $queryCache->setInfo(false);
98
        /** @var mixed */
99
        $result = $closure($this);
100
        $queryCache->removeLastInfo();
101
102
        return $result;
103
    }
104
105
    public function notProfiler(): void
106
    {
107
        $this->profiler = null;
108
    }
109
110
    public function queryCacheEnable(bool $value): void
111
    {
112
        $this->queryCache->setEnable($value);
113
    }
114
115
    public function setEnableSavepoint(bool $value): void
116
    {
117
        $this->enableSavepoint = $value;
118
    }
119
120
    public function setTablePrefix(string $value): void
121
    {
122
        $this->tablePrefix = $value;
123
    }
124
125
    public function transaction(Closure $closure, string $isolationLevel = null): mixed
126
    {
127
        $transaction = $this->beginTransaction($isolationLevel);
128
129
        $level = $transaction->getLevel();
130
131
        try {
132
            /** @var mixed */
133
            $result = $closure($this);
134
135
            if ($transaction->isActive() && $transaction->getLevel() === $level) {
136
                $transaction->commit();
137
            }
138
        } catch (Throwable $e) {
139
            $this->rollbackTransactionOnLevel($transaction, $level);
140
141
            throw $e;
142
        }
143
144
        return $result;
145
    }
146
147
    /**
148
     * Rolls back given {@see TransactionInterface} object if it's still active and level match. In some cases rollback
149
     * can fail, so this method is fail-safe. Exceptions thrown from rollback will be caught and just logged with
150
     * {@see logger->log()}.
151
     *
152
     * @param TransactionInterface $transaction TransactionInterface object given from {@see beginTransaction()}.
153
     * @param int $level TransactionInterface level just after {@see beginTransaction()} call.
154
     */
155
    private function rollbackTransactionOnLevel(TransactionInterface $transaction, int $level): void
156
    {
157
        if ($transaction->isActive() && $transaction->getLevel() === $level) {
158
            /**
159
             * {@see https://github.com/yiisoft/yii2/pull/13347}
160
             */
161
            try {
162
                $transaction->rollBack();
163
            } catch (\Exception $e) {
164
                $this->logger?->log(LogLevel::ERROR, (string) $e, [__METHOD__]);
165
                /** hide this exception to be able to continue throwing original exception outside */
166
            }
167
        }
168
    }
169
}
170