Completed
Push — master ( 767a3d...4cddac )
by Agel_Nash
03:55
created

IlluminateDriver   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 426
Duplicated Lines 0 %

Test Coverage

Coverage 98.05%

Importance

Changes 0
Metric Value
wmc 67
dl 0
loc 426
ccs 151
cts 154
cp 0.9805
rs 3.04
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 39 5
A useEloquent() 0 20 5
A isResult() 0 3 1
A execute() 0 3 2
A fieldName() 0 4 3
A getRow() 0 22 5
A getInsertId() 0 3 1
A getAffectedRows() 0 3 1
A getConnect() 0 9 3
A getElapsedTime() 0 9 2
A prepare() 0 16 2
A isConnected() 0 3 2
A disconnect() 0 11 2
A escape() 0 9 2
A getCapsule() 0 3 1
A connect() 0 23 3
B query() 0 21 9
A numFields() 0 3 2
A setCharset() 0 9 2
A getVersion() 0 3 1
A getLastErrorNo() 0 6 3
A isSelectQuery() 0 3 1
A getLastError() 0 4 3
A getRecordCount() 0 3 2
A hasConnectionName() 0 4 1
A selectDb() 0 3 1
A saveAffectedRows() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like IlluminateDriver 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.

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 IlluminateDriver, and based on these observations, apply Extract Interface, too.

1
<?php namespace AgelxNash\Modx\Evo\Database\Drivers;
2
3
use AgelxNash\Modx\Evo\Database\Exceptions;
4
use Illuminate\Database\Capsule\Manager as Capsule;
5
use Illuminate\Database\Connection;
6
use PDOStatement;
7
use Illuminate\Events\Dispatcher;
8
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
9
use Illuminate\Container\Container;
10
use ReflectionClass;
11
use PDO;
12
13
/**
14
 * @property Connection $conn
15
 */
16
class IlluminateDriver extends AbstractDriver
17
{
18
    /**
19
     * @var string
20
     */
21
    protected $connection = 'default';
22
23
    /**
24
     * @var Capsule
25
     */
26
    protected $capsule;
27
28
    /**
29
     * @var int
30
     */
31
    protected $affectedRows = 0;
32
33
    /**
34
     * @var array
35
     */
36
    protected $lastError = [];
37
38
    /**
39
     * @var string
40
     */
41
    protected $lastErrorNo = '';
42
43
    /**
44
     * @var string
45
     */
46
    protected $driver = 'mysql';
47
48
    private $elapsedTimeMethod;
49
50
    /**
51
     * {@inheritDoc}
52
     * @throws \ReflectionException
53
     */
54 29
    public function __construct(array $config = [], $connection = 'default')
55
    {
56 29
        $reflection = new ReflectionClass(Capsule::class);
57 29
        $property = $reflection->getProperty('instance');
58 29
        $property->setAccessible(true);
59
        /**
60
         * @var Capsule|null $capsule
61
         */
62 29
        $capsule = $property->getValue(Capsule::class);
0 ignored issues
show
Bug introduced by
Illuminate\Database\Capsule\Manager::class of type string is incompatible with the type object expected by parameter $object of ReflectionProperty::getValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
        $capsule = $property->getValue(/** @scrutinizer ignore-type */ Capsule::class);
Loading history...
63 29
        if ($capsule === null) {
64 1
            $this->capsule = new Capsule;
65
66 1
            $this->getCapsule()->setAsGlobal();
67
        } else {
68 28
            $this->capsule = $capsule;
69
        }
70
71 29
        if ($this->hasConnectionName($connection)) {
72 28
            if (empty($config)) {
73 2
                $config = $this->getCapsule()->getConnection($connection)->getConfig();
74 2
                unset($config['name'], $config['driver']);
75
            } else {
76 26
                $diff = array_diff_assoc(
77 26
                    array_merge(['driver' => $this->driver], $config),
78 26
                    $this->getCapsule()->getConnection($connection)->getConfig()
79
                );
80 26
                if (array_intersect(['driver', 'host', 'database', 'password', 'username'], array_keys($diff))) {
81 2
                    throw new Exceptions\ConnectException(
82 2
                        sprintf('The connection name "%s" is already used', $connection)
83
                    );
84
                }
85
            }
86
        }
87
88 28
        $this->connection = $connection;
89
90 28
        $this->useEloquent();
91
92 28
        $this->setConfig($config);
93 28
    }
94
95
    /**
96
     * Get the elapsed time since a given starting point.
97
     *
98
     * @param  int    $start
99
     * @return float
100
     */
101 25
    protected function getElapsedTime($start)
102
    {
103 25
        if ($this->elapsedTimeMethod === null) {
104 25
            $reflection = new ReflectionClass($this->getConnect());
105 25
            $this->elapsedTimeMethod = $reflection->getMethod('getElapsedTime');
106 25
            $this->elapsedTimeMethod->setAccessible(true);
107
        }
108
109 25
        return $this->elapsedTimeMethod->invoke($this->getConnect(), $start);
110
    }
111
    /**
112
     * {@inheritDoc}
113
     * @return Connection
114
     */
115 28
    public function getConnect()
116
    {
117 28
        if (! $this->isConnected()) {
118 28
            $this->connect();
119 28
            if (! $this->conn->getPdo() instanceof PDO) {
0 ignored issues
show
introduced by
$this->conn->getPdo() is always a sub-type of PDO.
Loading history...
Bug introduced by
The method getPdo() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

119
            if (! $this->conn->/** @scrutinizer ignore-call */ getPdo() instanceof PDO) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
120 2
                $this->conn->reconnect();
121
            }
122
        }
123 28
        return $this->conn;
124
    }
125
126
    /**
127
     * @return bool
128
     */
129 28
    public function isConnected()
130
    {
131 28
        return ($this->conn instanceof Connection && $this->conn->getDatabaseName());
132
    }
133
134
    /**
135
     * {@inheritDoc}
136
     */
137 1
    public function getLastError()
138
    {
139 1
        $error = $this->getConnect()->getPdo()->errorInfo();
140 1
        return empty($error[2]) ? (empty($this->lastError[2]) ? '' : $this->lastError[2]) : $error[2];
141
    }
142
143
    /**
144
     * {@inheritDoc}
145
     */
146 3
    public function getLastErrorNo()
147
    {
148 3
        $error = $this->getConnect()->getPdo()->errorInfo();
149 3
        $out = empty($error[0]) || $error[0] === '00000' ? $this->lastErrorNo : $error[0];
150
151 3
        return $out;
152
    }
153
154
    /**
155
     * {@inheritDoc}
156
     */
157 28
    public function connect()
158
    {
159
        try {
160 28
            if (! $this->hasConnectionName($this->connection)) {
161 1
                $this->getCapsule()->addConnection([
162 1
                    'driver'    => $this->driver,
163 1
                    'host'      => $this->getConfig('host'),
164 1
                    'database'  => $this->getConfig('database'),
165 1
                    'username'  => $this->getConfig('username'),
166 1
                    'password'  => $this->getConfig('password'),
167 1
                    'charset'   => $this->getConfig('charset'),
168 1
                    'collation' => $this->getConfig('collation'),
169 1
                    'prefix'    => $this->getConfig('prefix'),
170 1
                ], $this->connection);
171
            }
172
173 28
            $this->conn = $this->getCapsule()->getConnection($this->connection);
174
        } catch (\Exception $exception) {
175
            $this->conn = null;
176
            throw new Exceptions\ConnectException($exception->getMessage(), $exception->getCode());
177
        }
178
179 28
        return $this->conn;
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185 2
    public function disconnect()
186
    {
187 2
        if ($this->isConnected()) {
188 2
            $this->conn->disconnect();
189
        }
190
191 2
        $this->conn = null;
192 2
        $this->lastErrorNo = '';
193 2
        $this->lastError = [];
194
195 2
        return true;
196
    }
197
198
    /**
199
     * {@inheritDoc}
200
     */
201 25
    public function isResult($result)
202
    {
203 25
        return $result instanceof PDOStatement;
204
    }
205
206
    /**
207
     * @param PDOStatement $result
208
     * {@inheritDoc}
209
     */
210 1
    public function numFields($result)
211
    {
212 1
        return $this->isResult($result) ? $result->columnCount() : 0;
213
    }
214
215
    /**
216
     * @param PDOStatement $result
217
     * {@inheritDoc}
218
     */
219 1
    public function fieldName($result, $col = 0)
220
    {
221 1
        $field = $this->isResult($result) ? $result->getColumnMeta($col) : [];
222 1
        return isset($field['name']) ? $field['name'] : null;
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228 25
    public function setCharset($charset, $method = null)
229
    {
230 25
        if ($method === null) {
231 1
            $method = $this->getConfig('method');
232
        }
233
234 25
        $query = $method . ' ' . $charset;
235
236 25
        return (bool)$this->query($query);
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242 1
    public function selectDb($name)
243
    {
244 1
        return $this->getConnect()->getPdo()->exec('USE ' . $name) === 0;
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 1
    public function escape($data)
251
    {
252
        /**
253
         * It's not secure
254
         * But need for backward compatibility
255
         */
256
257 1
        $quote = $this->getConnect()->getPdo()->quote($data);
258 1
        return strpos($quote, '\'') === 0 ? mb_substr($quote, 1, -1) : $quote;
259
    }
260
261
    /**
262
     * {@inheritDoc}
263
     * @return bool|PDOStatement
264
     */
265 25
    public function query($sql)
266
    {
267
        try {
268 25
            $start = microtime(true);
269
270 25
            $result = $this->prepare($sql);
271 25
            $this->execute($result);
272
273 25
            if ($this->saveAffectedRows($result) === 0 && $this->isResult($result) && ! $this->isSelectQuery($sql)) {
274 25
                $result = true;
275
            }
276 25
            $this->getConnect()->logQuery($sql, [], $this->getElapsedTime($start));
277 3
        } catch (\Exception $exception) {
278 3
            $this->lastError = $this->isResult($result) ? $result->errorInfo() : [];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
279 2
            $code = $this->isResult($result) ? $result->errorCode() : '';
280 2
            $this->lastErrorNo = $this->isResult($result) ? (empty($code) ? $exception->getCode() : $code) : '';
281 2
            throw (new Exceptions\QueryException($exception->getMessage(), $exception->getCode()))
282 2
                ->setQuery($sql);
283
        }
284
285 25
        return $result;
286
    }
287
288
    /**
289
     * @param PDOStatement $result
290
     * {@inheritDoc}
291
     */
292 13
    public function getRecordCount($result)
293
    {
294 13
        return $this->isResult($result) ? $result->rowCount() : 0;
295
    }
296
297
    /**
298
     * @param PDOStatement $result
299
     * {@inheritDoc}
300
     */
301 11
    public function getRow($result, $mode = 'assoc')
302
    {
303
        switch ($mode) {
304 11
            case 'assoc':
305 6
                $out = $result->fetch(\PDO::FETCH_ASSOC);
306 6
                break;
307 7
            case 'num':
308 4
                $out = $result->fetch(\PDO::FETCH_NUM);
309 4
                break;
310 3
            case 'object':
311 1
                $out = $result->fetchObject();
312 1
                break;
313 2
            case 'both':
314 1
                $out = $result->fetch(\PDO::FETCH_BOTH);
315 1
                break;
316
            default:
317 1
                throw new Exceptions\UnknownFetchTypeException(
318 1
                    "Unknown get type ($mode) specified for fetchRow - must be empty, 'assoc', 'num', 'object' or 'both'."
319
                );
320
        }
321
322 11
        return $out;
323
    }
324
325
    /**
326
     * {@inheritDoc}
327
     */
328 4
    public function getVersion()
329
    {
330 4
        return $this->getConnect()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
331
    }
332
333
    /**
334
     * {@inheritDoc}
335
     */
336 4
    public function getInsertId()
337
    {
338 4
        return $this->getConnect()->getPdo()->lastInsertId();
339
    }
340
341
    /**
342
     * @return int
343
     */
344 25
    public function getAffectedRows()
345
    {
346 25
        return $this->affectedRows;
347
    }
348
349
    /**
350
     * @param PDOStatement|bool $result
351
     * @return int
352
     */
353 25
    protected function saveAffectedRows($result)
354
    {
355 25
        $this->affectedRows = \is_bool($result) ? 0 : $result->rowCount();
356 25
        return $this->getAffectedRows();
357
    }
358
359
    /**
360
     * @param string $sql
361
     * @return PDOStatement|bool
362
     * @throws Exceptions\ConnectException
363
     */
364 25
    public function prepare($sql)
365
    {
366 25
        $pdo = $this->getConnect()->getPdo();
367 25
        $result = $pdo->prepare(
368 25
            $sql,
369
            [
370 25
                \PDO::ATTR_CURSOR => \PDO::CURSOR_SCROLL,
371
372
            ]
373
        );
374
375 25
        if ($this->isResult($result)) {
376 25
            $result->setFetchMode(\PDO::FETCH_ASSOC);
377
        }
378
379 25
        return $result;
380
    }
381
382
    /**
383
     * @param PDOStatement|bool $result
384
     * @return bool
385
     */
386 25
    public function execute($result)
387
    {
388 25
        return $this->isResult($result) ? $result->execute() : (bool)$result;
389
    }
390
391
    /**
392
     * @param DispatcherContract|null $dispatcher
393
     * @return bool
394
     */
395 28
    public function useEloquent(DispatcherContract $dispatcher = null)
396
    {
397 28
        $out = false;
398 28
        if ($dispatcher === null) {
399 28
            $dispatcher = $this->getCapsule()->getEventDispatcher();
400
        }
401
402 28
        if ($dispatcher === null && class_exists(Dispatcher::class)) {
403 4
            $dispatcher = new Dispatcher(new Container);
404
        }
405
406 28
        if ($dispatcher !== null) {
407 28
            $this->getCapsule()->setEventDispatcher($dispatcher);
408
409 28
            $out = true;
410
        }
411
412 28
        $this->getCapsule()->bootEloquent();
413
414 28
        return $out;
415
    }
416
417
    /**
418
     * @return Capsule
419
     */
420 29
    public function getCapsule()
421
    {
422 29
        return $this->capsule;
423
    }
424
425
    /**
426
     * @param $name
427
     * @return bool
428
     */
429 29
    public function hasConnectionName($name)
430
    {
431 29
        $connections = $this->getCapsule()->getDatabaseManager()->getConnections();
432 29
        return isset($connections[$name]);
433
    }
434
435
    /**
436
     * @param string $query
437
     * @return bool
438
     */
439 25
    protected function isSelectQuery($query)
440
    {
441 25
        return 0 === mb_stripos(trim($query), 'SELECT');
442
    }
443
}
444