Completed
Push — master ( 853083...2deeb8 )
by Jared
01:46
created

DatabaseDriver   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 10
dl 0
loc 318
rs 9.1199
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setConnectionManager() 0 6 1
A getConnectionManager() 0 4 1
A setConnection() 0 6 1
A getConnection() 0 20 5
A createModel() 0 14 2
A getCreatedId() 0 8 2
A loadModel() 0 20 3
A updateModel() 0 19 3
A deleteModel() 0 13 2
A queryModels() 0 23 2
A count() 0 19 2
A sum() 0 19 2
A average() 0 19 2
A max() 0 19 2
A min() 0 19 2
A startTransaction() 0 15 3
A rollBackTransaction() 0 10 3
A commitTransaction() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like DatabaseDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DatabaseDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @see http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
12
namespace Pulsar\Driver;
13
14
use JAQB\ConnectionManager;
15
use JAQB\Exception\JAQBException;
16
use JAQB\QueryBuilder;
17
use PDOException;
18
use PDOStatement;
19
use Pulsar\Exception\DriverException;
20
use Pulsar\Model;
21
use Pulsar\Query;
22
23
/**
24
 * Driver for storing models in a database using PDO.
25
 */
26
final class DatabaseDriver extends AbstractDriver
27
{
28
    /**
29
     * @var ConnectionManager
30
     */
31
    private $connections;
32
33
    /**
34
     * @var QueryBuilder
35
     */
36
    private $connection;
37
38
    /**
39
     * @var int
40
     */
41
    private $transactionNestingLevel = 0;
42
43
    /**
44
     * Sets the connection manager.
45
     *
46
     * @return $this
47
     */
48
    public function setConnectionManager(ConnectionManager $manager)
49
    {
50
        $this->connections = $manager;
51
52
        return $this;
53
    }
54
55
    /**
56
     * Gets the connection manager.
57
     */
58
    public function getConnectionManager(): ConnectionManager
59
    {
60
        return $this->connections;
61
    }
62
63
    /**
64
     * Sets the default database connection.
65
     *
66
     * @return $this
67
     */
68
    public function setConnection(QueryBuilder $db)
69
    {
70
        $this->connection = $db;
71
72
        return $this;
73
    }
74
75
    /**
76
     * Gets the database connection.
77
     *
78
     * @param string|null $id connection ID
79
     *
80
     * @throws DriverException when the connection has not been set yet
81
     */
82
    public function getConnection(?string $id): QueryBuilder
83
    {
84
        if ($this->connections) {
85
            try {
86
                if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
87
                    return $this->connections->get($id);
88
                } else {
89
                    return $this->connections->getDefault();
90
                }
91
            } catch (JAQBException | PDOException $e) {
92
                throw new DriverException($e->getMessage(), $e->getCode(), $e);
93
            }
94
        }
95
96
        if (!$this->connection) {
97
            throw new DriverException('The database driver has not been given a connection!');
98
        }
99
100
        return $this->connection;
101
    }
102
103
    public function createModel(Model $model, array $parameters)
104
    {
105
        $values = $this->serialize($parameters);
106
        $tablename = $model->getTablename();
107
        $db = $this->getConnection($model->getConnection());
108
109
        try {
110
            return $db->insert($values)
111
                    ->into($tablename)
112
                    ->execute() instanceof PDOStatement;
113
        } catch (PDOException $original) {
114
            throw new DriverException('An error occurred in the database driver when creating the '.$model::modelName().': '.$original->getMessage(), $original->getCode(), $original);
115
        }
116
    }
117
118
    public function getCreatedId(Model $model, string $propertyName)
119
    {
120
        try {
121
            return $this->getConnection($model->getConnection())->lastInsertId();
122
        } catch (PDOException $original) {
123
            throw new DriverException('An error occurred in the database driver when getting the ID of the new '.$model::modelName().': '.$original->getMessage(), $original->getCode(), $original);
124
        }
125
    }
126
127
    public function loadModel(Model $model): ?array
128
    {
129
        $tablename = $model->getTablename();
130
        $db = $this->getConnection($model->getConnection());
131
132
        try {
133
            $row = $db->select('*')
134
                ->from($tablename)
135
                ->where($model->ids())
136
                ->one();
137
        } catch (PDOException $original) {
138
            throw new DriverException('An error occurred in the database driver when loading an instance of '.$model::modelName().': '.$original->getMessage(), $original->getCode(), $original);
139
        }
140
141
        if (!is_array($row)) {
142
            return null;
143
        }
144
145
        return $row;
146
    }
147
148
    public function updateModel(Model $model, array $parameters): bool
149
    {
150
        if (0 == count($parameters)) {
151
            return true;
152
        }
153
154
        $values = $this->serialize($parameters);
155
        $tablename = $model->getTablename();
156
        $db = $this->getConnection($model->getConnection());
157
158
        try {
159
            return $db->update($tablename)
160
                    ->values($values)
161
                    ->where($model->ids())
162
                    ->execute() instanceof PDOStatement;
163
        } catch (PDOException $original) {
164
            throw new DriverException('An error occurred in the database driver when updating the '.$model::modelName().': '.$original->getMessage(), $original->getCode(), $original);
165
        }
166
    }
167
168
    public function deleteModel(Model $model): bool
169
    {
170
        $tablename = $model->getTablename();
171
        $db = $this->getConnection($model->getConnection());
172
173
        try {
174
            return $db->delete($tablename)
175
                    ->where($model->ids())
176
                    ->execute() instanceof PDOStatement;
177
        } catch (PDOException $original) {
178
            throw new DriverException('An error occurred in the database driver while deleting the '.$model::modelName().': '.$original->getMessage(), $original->getCode(), $original);
179
        }
180
    }
181
182
    public function queryModels(Query $query): array
183
    {
184
        $modelClass = $query->getModel();
185
        $model = new $modelClass();
186
        $tablename = $model->getTablename();
187
188
        // build a DB query from the model query
189
        $dbQuery = $this->getConnection($model->getConnection())
190
            ->select($this->prefixSelect('*', $tablename))
191
            ->from($tablename)
192
            ->where($this->prefixWhere($query->getWhere(), $tablename))
193
            ->limit($query->getLimit(), $query->getStart())
194
            ->orderBy($this->prefixSort($query->getSort(), $tablename));
195
196
        // join conditions
197
        $this->addJoins($query, $tablename, $dbQuery);
198
199
        try {
200
            return $dbQuery->all();
201
        } catch (PDOException $original) {
202
            throw new DriverException('An error occurred in the database driver while performing the '.$model::modelName().' query: '.$original->getMessage(), $original->getCode(), $original);
203
        }
204
    }
205
206
    public function count(Query $query): int
207
    {
208
        $modelClass = $query->getModel();
209
        $model = new $modelClass();
210
        $tablename = $model->getTablename();
211
        $db = $this->getConnection($model->getConnection());
212
213
        $dbQuery = $db->select()
214
            ->count()
215
            ->from($tablename)
216
            ->where($this->prefixWhere($query->getWhere(), $tablename));
217
        $this->addJoins($query, $tablename, $dbQuery);
218
219
        try {
220
            return (int) $dbQuery->scalar();
221
        } catch (PDOException $original) {
222
            throw new DriverException('An error occurred in the database driver while getting the number of '.$model::modelName().' objects: '.$original->getMessage(), $original->getCode(), $original);
223
        }
224
    }
225
226
    public function sum(Query $query, string $field)
227
    {
228
        $modelClass = $query->getModel();
229
        $model = new $modelClass();
230
        $tablename = $model->getTablename();
231
        $db = $this->getConnection($model->getConnection());
232
233
        $dbQuery = $db->select()
234
            ->sum($this->prefixColumn($field, $tablename))
235
            ->from($tablename)
236
            ->where($this->prefixWhere($query->getWhere(), $tablename));
237
        $this->addJoins($query, $tablename, $dbQuery);
238
239
        try {
240
            return (int) $dbQuery->scalar();
241
        } catch (PDOException $original) {
242
            throw new DriverException('An error occurred in the database driver while getting the sum of '.$model::modelName().' '.$field.': '.$original->getMessage(), $original->getCode(), $original);
243
        }
244
    }
245
246
    public function average(Query $query, string $field)
247
    {
248
        $modelClass = $query->getModel();
249
        $model = new $modelClass();
250
        $tablename = $model->getTablename();
251
        $db = $this->getConnection($model->getConnection());
252
253
        $dbQuery = $db->select()
254
            ->average($this->prefixColumn($field, $tablename))
255
            ->from($tablename)
256
            ->where($this->prefixWhere($query->getWhere(), $tablename));
257
        $this->addJoins($query, $tablename, $dbQuery);
258
259
        try {
260
            return (int) $dbQuery->scalar();
261
        } catch (PDOException $original) {
262
            throw new DriverException('An error occurred in the database driver while getting the average of '.$model::modelName().' '.$field.': '.$original->getMessage(), $original->getCode(), $original);
263
        }
264
    }
265
266
    public function max(Query $query, string $field)
267
    {
268
        $modelClass = $query->getModel();
269
        $model = new $modelClass();
270
        $tablename = $model->getTablename();
271
        $db = $this->getConnection($model->getConnection());
272
273
        $dbQuery = $db->select()
274
            ->max($this->prefixColumn($field, $tablename))
275
            ->from($tablename)
276
            ->where($this->prefixWhere($query->getWhere(), $tablename));
277
        $this->addJoins($query, $tablename, $dbQuery);
278
279
        try {
280
            return (int) $dbQuery->scalar();
281
        } catch (PDOException $original) {
282
            throw new DriverException('An error occurred in the database driver while getting the max of '.$model::modelName().' '.$field.': '.$original->getMessage(), $original->getCode(), $original);
283
        }
284
    }
285
286
    public function min(Query $query, string $field)
287
    {
288
        $modelClass = $query->getModel();
289
        $model = new $modelClass();
290
        $tablename = $model->getTablename();
291
        $db = $this->getConnection($model->getConnection());
292
293
        $dbQuery = $db->select()
294
            ->min($this->prefixColumn($field, $tablename))
295
            ->from($tablename)
296
            ->where($this->prefixWhere($query->getWhere(), $tablename));
297
        $this->addJoins($query, $tablename, $dbQuery);
298
299
        try {
300
            return (int) $dbQuery->scalar();
301
        } catch (PDOException $original) {
302
            throw new DriverException('An error occurred in the database driver while getting the min of '.$model::modelName().' '.$field.': '.$original->getMessage(), $original->getCode(), $original);
303
        }
304
    }
305
306
    public function startTransaction(?string $connection): void
307
    {
308
        if (0 == $this->transactionNestingLevel) {
309
            $db = $this->getConnection($connection);
310
            if ($db->inTransaction()) {
311
                $this->transactionNestingLevel += 2;
312
313
                return;
314
            }
315
316
            $db->beginTransaction();
317
        }
318
319
        ++$this->transactionNestingLevel;
320
    }
321
322
    public function rollBackTransaction(?string $connection): void
323
    {
324
        if (0 == $this->transactionNestingLevel) {
325
            throw new DriverException('No active transaction');
326
        } elseif (1 == $this->transactionNestingLevel) {
327
            $this->getConnection($connection)->rollBack();
328
        }
329
330
        --$this->transactionNestingLevel;
331
    }
332
333
    public function commitTransaction(?string $connection): void
334
    {
335
        if (0 == $this->transactionNestingLevel) {
336
            throw new DriverException('No active transaction');
337
        } elseif (1 == $this->transactionNestingLevel) {
338
            $this->getConnection($connection)->commit();
339
        }
340
341
        --$this->transactionNestingLevel;
342
    }
343
}
344