DatabaseManager::transactional()   A
last analyzed

Complexity

Conditions 5
Paths 20

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 20
nop 2
dl 0
loc 21
rs 9.4222
c 0
b 0
f 0
1
<?php
2
namespace WebStream\Database;
3
4
use WebStream\DI\Injector;
5
use WebStream\Container\Container;
6
use WebStream\Exception\Extend\DatabaseException;
7
use Doctrine\DBAL\Connection;
0 ignored issues
show
Bug introduced by
The type Doctrine\DBAL\Connection 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
9
/**
10
 * DatabaseManager
11
 * @author Ryuichi TANAKA.
12
 * @since 2013/12/07
13
 * @version 0.4
14
 */
15
class DatabaseManager
16
{
17
    use Injector;
18
19
    /**
20
     * @var ConnectionManager コネクションマネージャ
21
     */
22
    private $connectionManager;
23
24
    /**
25
     * @var DatabaseDriver データベースコネクション
0 ignored issues
show
Bug introduced by
The type WebStream\Database\DatabaseDriver 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...
26
     */
27
    private $connection;
28
29
    /**
30
     * @var bool 自動コミットフラグ
31
     */
32
    private $isAutoCommit;
33
34
    /**
35
     * @var Query クエリオブジェクト
36
     */
37
    private $query;
38
39
    /**
40
     * @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...
41
     */
42
    private $logger;
43
44
    /**
45
     * constructor
46
     * @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...
47
     */
48
    public function __construct(Container $container)
49
    {
50
        $this->connectionManager = new ConnectionManager($container);
51
        $this->logger = $container->logger;
0 ignored issues
show
Bug Best Practice introduced by
The property logger does not exist on WebStream\Container\Container. Since you implemented __get, consider adding a @property annotation.
Loading history...
Documentation Bug introduced by
It seems like $container->logger can also be of type string. However, the property $logger is declared as type WebStream\Database\Logger. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

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