Passed
Push — master ( 76b08e...5cd6b2 )
by Ryuichi
58:40 queued 56:20
created

DatabaseManager::transactional()   A

Complexity

Conditions 5
Paths 20

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0488

Importance

Changes 0
Metric Value
cc 5
eloc 16
c 0
b 0
f 0
nc 20
nop 2
dl 0
loc 21
ccs 14
cts 16
cp 0.875
crap 5.0488
rs 9.4222
1
<?php
2
3
namespace WebStream\Database;
4
5
use WebStream\DI\Injector;
0 ignored issues
show
Bug introduced by
The type WebStream\DI\Injector was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use WebStream\Container\Container;
0 ignored issues
show
Bug introduced by
The type WebStream\Container\Container was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use WebStream\Exception\Extend\DatabaseException;
0 ignored issues
show
Bug introduced by
The type WebStream\Exception\Extend\DatabaseException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use WebStream\Database\Driver\DatabaseDriver;
9
use Doctrine\DBAL\TransactionIsolationLevel;
10
11
/**
12
 * DatabaseManager
13
 * @author Ryuichi TANAKA.
14
 * @since 2013/12/07
15
 * @version 0.4
16
 */
17
class DatabaseManager
18
{
19
    use Injector;
20
21
    /**
22
     * @var ConnectionManager コネクションマネージャ
23
     */
24
    private $connectionManager;
25
26
    /**
27
     * @var DatabaseDriver データベースコネクション
28
     */
29
    private $connection;
30
31
    /**
32
     * @var bool 自動コミットフラグ
33
     */
34
    private $isAutoCommit;
35
36
    /**
37
     * @var Query クエリオブジェクト
38
     */
39
    private $query;
40
41
    /**
42
     * @var Logger ロガー
0 ignored issues
show
Bug introduced by
The type WebStream\Database\Logger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
43
     */
44
    private $logger;
45
46
    /**
47
     * constructor
48
     * @param Container 依存コンテナ
0 ignored issues
show
Documentation Bug introduced by
The doc comment 依存コンテナ at position 0 could not be parsed: Unknown type name '依存コンテナ' at position 0 in 依存コンテナ.
Loading history...
49
     */
50 52
    public function __construct(Container $container)
51
    {
52 52
        $this->connectionManager = new ConnectionManager($container);
53 52
        $this->logger = $container->logger;
54 52
        $this->isAutoCommit = false;
55 52
    }
56
57
    /**
58
     * destructor
59
     */
60 52
    public function __destruct()
61
    {
62 52
        $this->disconnect();
63 52
        $this->query = null;
64 52
    }
65
66
    /**
67
     * データベース接続する
68
     * すでに接続中であれば再接続はしない
69
     */
70 52
    public function connect()
71
    {
72
        try {
73 52
            $this->connection->connect();
74 52
            $this->query = new Query($this->connection);
75 52
            $this->query->inject('logger', $this->logger);
76
        } catch (\PDOException $e) {
77
            throw new DatabaseException($e);
78
        }
79 52
    }
80
81
    /**
82
     * データベース切断する
83
     */
84 52
    public function disconnect()
85
    {
86 52
        if ($this->connection === null) {
87 52
            return;
88
        }
89
90 52
        if ($this->inTransaction()) {
91
            // トランザクションが明示的に開始された状態でcommit/rollbackが行われていない場合
92
            // ログに警告を書き込み、強制的にロールバックする
93 16
            $this->connection->rollback();
94 16
            $this->logger->warn("Not has been executed commit or rollback after the transaction started.");
95
        }
96
97 52
        $this->connection->disconnect();
98 52
        $this->connection = null;
99 52
    }
100
101
    /**
102
     * トランザクションを開始する
103
     * @param int $isolationLevel トランザクション分離レベル
104
     */
105 16
    public function beginTransaction(int $isolationLevel)
106
    {
107
        // 既にトランザクションが開始されている場合、継続しているトランザクションを有効のままにする
108
        // トランザクションを破棄して再度開始する場合は明示的に破棄してから再呼び出しする
109 16
        if ($this->inTransaction()) {
110
            $this->logger->debug("Transaction already started.");
111
112
            return;
113
        }
114
115 16
        if (!$this->connection->beginTransaction()) {
116
            throw new DatabaseException("Failed to start transaction.");
117
        }
118
119 16
        $this->connection->setAutoCommit($this->isAutoCommit);
120
121
        if (
122 16
            $isolationLevel === TransactionIsolationLevel::READ_UNCOMMITTED ||
123 16
            $isolationLevel === TransactionIsolationLevel::READ_COMMITTED ||
124
            $isolationLevel === TransactionIsolationLevel::REPEATABLE_READ ||
125 16
            $isolationLevel === TransactionIsolationLevel::SERIALIZABLE
126
        ) {
127 16
            $this->connection->setTransactionIsolation($isolationLevel);
128
        } else {
129
            throw new DatabaseException("Invalid transaction isolation level: " . $isolationLevel);
130
        }
131
132 16
        $this->logger->debug("Transaction start.");
133 16
    }
134
135
    /**
136
     * コミットする
137
     */
138 8
    public function commit()
139
    {
140
        try {
141 8
            if ($this->connection !== null) {
142 8
                if ($this->inTransaction()) {
143 8
                    $this->connection->commit();
144 8
                    $this->logger->debug("Execute commit.");
145
                } else {
146 8
                    $this->logger->warn("Not executed commit because the transaction is not started.");
147
                }
148
            } else {
149 8
                throw new DatabaseException("Can't execute commit.");
150
            }
151
        } catch (\Exception $e) {
152
            $this->query = null;
153
            throw new DatabaseException($e);
154
        }
155 8
    }
156
157
    /**
158
     * ロールバックする
159
     */
160 8
    public function rollback()
161
    {
162
        try {
163 8
            if ($this->connection !== null) {
164 8
                if ($this->inTransaction()) {
165 8
                    $this->connection->rollback();
166 8
                    $this->logger->debug("Execute rollback.");
167
                } else {
168 8
                    $this->logger->warn("Not executed rollback because the transaction is not started.");
169
                }
170
            } else {
171 8
                throw new DatabaseException("Can't execute rollback.");
172
            }
173
        } catch (\Exception $e) {
174
            $this->query = null;
175
            throw new DatabaseException($e);
176
        }
177 8
    }
178
179
    /**
180
     * トランザクションスコープを使用する
181
     * @param  Closure $closure クロージャ
0 ignored issues
show
Bug introduced by
The type WebStream\Database\Closure was not found. Did you mean Closure? If so, make sure to prefix the type with \.
Loading history...
182
     * @return object 処理結果
183
     */
184 8
    public function transactional(\Closure $closure, $config = [])
185
    {
186 8
        if (!array_key_exists('isolationLevel', $config)) {
187 4
            $config['isolationLevel'] = TransactionIsolationLevel::READ_COMMITTED;
188
        }
189 8
        if (!array_key_exists('autoCommit', $config)) {
190 4
            $config['autoCommit'] = false;
191
        }
192
193 8
        $this->isAutoCommit = $config['autoCommit'];
194 8
        $this->beginTransaction($config['isolationLevel']);
195
        try {
196 8
            $result = $closure($this);
197 4
            $this->commit();
198 4
            return $result;
199 4
        } catch (DatabaseException $e) {
200
            $this->rollback();
201
            throw $e;
202 4
        } catch (\Throwable $e) {
203 4
            $this->rollback();
204 4
            throw new DatabaseException($e);
205
        }
206
    }
207
208
    /**
209
     * 自動コミットを有効化
210
     */
211 16
    public function enableAutoCommit()
212
    {
213 16
        $this->isAutoCommit = true;
214 16
    }
215
216
    /**
217
     * 自動コミットを無効化
218
     */
219 4
    public function disableAutoCommit()
220
    {
221 4
        $this->isAutoCommit = false;
222 4
    }
223
224
    /**
225
     * ロールバックが発生したかどうか
226
     * @return boolean ロールバックが発生したかどうか
227
     */
228
    public function isRollback()
229
    {
230
        return $this->isRollback;
231
    }
232
233
    /**
234
     * トランザクション内かどうか
235
     * @return boolean トランザクション内かどうか
236
     */
237 52
    public function inTransaction()
238
    {
239 52
        return $this->connection->inTransaction();
240
    }
241
242
    /**
243
     * DB接続されているか
244
     * @param boolean 接続有無
0 ignored issues
show
Documentation Bug introduced by
The doc comment 接続有無 at position 0 could not be parsed: Unknown type name '接続有無' at position 0 in 接続有無.
Loading history...
245
     */
246
    public function isConnected()
247
    {
248
        return $this->connection->isConnected();
249
    }
250
251
    /**
252
     * トランザクション分離レベルを返却する
253
     * @return int トランザクション分離レベル
254
     */
255
    public function getTransactionIsolation()
256
    {
257
        return $this->connection->getTransactionIsolation();
258
    }
259
260
    /**
261
     * データベース接続が可能かどうか
262
     * @param string Modelファイルパス
0 ignored issues
show
Documentation Bug introduced by
The doc comment Modelファイルパス at position 0 could not be parsed: Unknown type name 'Modelファイルパス' at position 0 in Modelファイルパス.
Loading history...
263
     * @return boolean 接続可否
264
     */
265 52
    public function loadConnection($filepath)
266
    {
267 52
        $connection = $this->connectionManager->getConnection($filepath);
268 52
        if ($connection !== null) {
269 52
            $this->connection = $connection;
270
        }
271
272 52
        return $this->connection !== null;
273
    }
274
275
    /**
276
     * クエリを設定する
277
     * @param string SQL
278
     * @param array<string> パラメータ
0 ignored issues
show
Documentation Bug introduced by
The doc comment パラメータ at position 0 could not be parsed: Unknown type name 'パラメータ' at position 0 in パラメータ.
Loading history...
279
     */
280 52
    public function query($sql, array $bind = [])
281
    {
282 52
        if ($this->query === null) {
283
            throw new DatabaseException("Query does not set because database connection failed.");
284
        }
285 52
        $this->query->setSql($sql);
286 52
        $this->query->setBind($bind);
287
288 52
        return $this->query;
289
    }
290
}
291