Passed
Pull Request — 2.1 (#69)
by Vincent
12:22 queued 06:14
created

SimpleConnection   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 423
Duplicated Lines 0 %

Test Coverage

Coverage 90.21%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 55
eloc 118
c 1
b 0
f 0
dl 0
loc 423
ccs 129
cts 143
cp 0.9021
rs 6

28 Methods

Rating   Name   Duplication   Size   Complexity  
A beginTransaction() 0 5 1
A make() 0 3 1
B runOrReconnect() 0 30 7
B execute() 0 26 7
A executeStatement() 0 7 2
A getName() 0 3 1
A commit() 0 5 1
A prepare() 0 3 1
A prepareLogger() 0 7 3
A from() 0 3 1
A executeQuery() 0 7 2
A __construct() 0 16 1
A schema() 0 7 2
A setName() 0 5 1
A factory() 0 3 1
A close() 0 5 1
A isConnected() 0 3 1
B executePrepared() 0 36 7
A toDatabase() 0 3 1
A select() 0 3 1
A rollBack() 0 5 1
A delete() 0 3 1
A insert() 0 3 1
A platform() 0 18 5
A fromDatabase() 0 3 1
A builder() 0 3 1
A update() 0 3 1
A getDatabase() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SimpleConnection 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 SimpleConnection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bdf\Prime\Connection;
4
5
use Bdf\Prime\Connection\Event\ConnectionClosedListenerInterface;
6
use Bdf\Prime\Connection\Extensions\LostConnection;
7
use Bdf\Prime\Connection\Extensions\SchemaChanged;
8
use Bdf\Prime\Connection\Result\DoctrineResultSet;
9
use Bdf\Prime\Connection\Result\ResultSetInterface;
10
use Bdf\Prime\Connection\Result\UpdateResultSet;
11
use Bdf\Prime\Exception\DBALException;
12
use Bdf\Prime\Exception\PrimeException;
13
use Bdf\Prime\Exception\QueryExecutionException;
14
use Bdf\Prime\Platform\PlatformInterface;
15
use Bdf\Prime\Platform\Sql\SqlPlatform;
16
use Bdf\Prime\Query\CommandInterface;
17
use Bdf\Prime\Query\Compiler\Preprocessor\PreprocessorInterface;
18
use Bdf\Prime\Query\Compiler\SqlCompiler;
19
use Bdf\Prime\Query\Contract\Compilable;
20
use Bdf\Prime\Query\Contract\Query\InsertQueryInterface;
21
use Bdf\Prime\Query\Contract\Query\KeyValueQueryInterface;
22
use Bdf\Prime\Query\Custom\BulkInsert\BulkInsertQuery;
23
use Bdf\Prime\Query\Custom\BulkInsert\BulkInsertSqlCompiler;
24
use Bdf\Prime\Query\Custom\KeyValue\KeyValueQuery;
25
use Bdf\Prime\Query\Custom\KeyValue\KeyValueSqlCompiler;
26
use Bdf\Prime\Query\Factory\DefaultQueryFactory;
27
use Bdf\Prime\Query\Factory\QueryFactoryInterface;
28
use Bdf\Prime\Query\Query;
29
use Bdf\Prime\Schema\SchemaManager;
30
use Closure;
31
use Doctrine\Common\EventManager;
32
use Doctrine\DBAL\Cache\QueryCacheProfile;
33
use Doctrine\DBAL\Configuration;
34
use Doctrine\DBAL\Connection as BaseConnection;
35
use Doctrine\DBAL\Driver;
36
use Doctrine\DBAL\Exception as DoctrineDBALException;
37
use Doctrine\DBAL\Exception\DriverException;
38
use Doctrine\DBAL\Result;
39
use Doctrine\DBAL\Statement;
40
41
/**
42
 * Connection
43
 *
44
 * @method \Bdf\Prime\Configuration getConfiguration()
45
 */
46
class SimpleConnection extends BaseConnection implements ConnectionInterface, TransactionManagerInterface
47
{
48
    use LostConnection;
49
    use SchemaChanged;
50
51
    /**
52
     * The connection name.
53
     *
54
     * @var string
55
     */
56
    protected $name;
57
58
    /**
59
     * The schema manager.
60
     *
61
     * @var SchemaManager
62
     */
63
    private $schema;
64
65
    /**
66
     * @var SqlPlatform
67
     */
68
    private $platform;
69
70
    /**
71
     * @var QueryFactoryInterface
72
     */
73
    private $factory;
74
75
    /**
76
     * SimpleConnection constructor.
77
     *
78
     * @param array $params
79
     * @param Driver $driver
80
     * @param Configuration|null $config
81
     * @param EventManager|null $eventManager
82
     * @throws DoctrineDBALException
83
     */
84 404
    public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null)
85
    {
86
        /** @psalm-suppress InternalMethod */
87 404
        parent::__construct($params, $driver, $config, $eventManager);
88
89
        /** @psalm-suppress InvalidArgument */
90 404
        $this->factory = new DefaultQueryFactory(
91 404
            $this,
92 404
            new SqlCompiler($this),
93 404
            [
94 404
                KeyValueQuery::class   => KeyValueSqlCompiler::class,
95 404
                BulkInsertQuery::class => BulkInsertSqlCompiler::class,
96 404
            ],
97 404
            [
98 404
                KeyValueQueryInterface::class => KeyValueQuery::class,
99 404
                InsertQueryInterface::class   => BulkInsertQuery::class,
100 404
            ]
101 404
        );
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107 405
    public function setName(string $name)
108
    {
109 405
        $this->name = $name;
110
111 405
        return $this;
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 408
    public function getName(): string
118
    {
119 408
        return $this->name;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125 1016
    public function getDatabase(): ?string
126
    {
127 1016
        return parent::getDatabase();
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 1
    public function isConnected()
134
    {
135 1
        return $this->_conn !== null;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141 1088
    public function schema(): SchemaManager
142
    {
143 1088
        if ($this->schema === null) {
144 370
            $this->schema = new SchemaManager($this);
145
        }
146
147 1088
        return $this->schema;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 1330
    public function platform(): PlatformInterface
154
    {
155 1330
        if ($this->platform === null) {
156
            try {
157 387
                $config = $this->getConfiguration();
158 387
                $this->platform = new SqlPlatform($this->getDatabasePlatform(), $config->getTypes());
159 387
                $types = $this->platform->types();
160
161 387
                foreach ($config->getPlatformTypes() as $alias => $type) {
162 1
                    $types->register($type, is_string($alias) ? $alias : null);
163
                }
164
            } catch (DoctrineDBALException $e) {
165
                /** @psalm-suppress InvalidScalarArgument */
166
                throw new DBALException($e->getMessage(), $e->getCode(), $e);
167
            }
168
        }
169
170 1330
        return $this->platform;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 111
    public function fromDatabase($value, $type, array $fieldOptions = [])
177
    {
178 111
        return $this->platform()->types()->fromDatabase($value, $type, $fieldOptions);
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 5
    public function toDatabase($value, $type = null)
185
    {
186 5
        return $this->platform()->types()->toDatabase($value, $type);
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 914
    public function builder(PreprocessorInterface $preprocessor = null): Query
193
    {
194 914
        return $this->factory->make(Query::class, $preprocessor);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->factory->m...::class, $preprocessor) returns the type Bdf\Prime\Query\CommandInterface which includes types incompatible with the type-hinted return Bdf\Prime\Query\Query.
Loading history...
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 441
    public function make(string $query, PreprocessorInterface $preprocessor = null): CommandInterface
201
    {
202 441
        return $this->factory->make($query, $preprocessor);
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208 1084
    public function factory(): QueryFactoryInterface
209
    {
210 1084
        return $this->factory;
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216 730
    public function from($table, ?string $alias = null): Query
217
    {
218 730
        return $this->builder()->from($table, $alias);
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224 13
    public function delete($table, array $criteria, array $types = [])
225
    {
226 13
        return $this->from($table)->where($criteria)->delete();
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232 1
    public function update($table, array $data, array $criteria, array $types = [])
233
    {
234 1
        return $this->from($table)->where($criteria)->update($data, $types);
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240 619
    public function insert($table, array $data, array $types = [])
241
    {
242 619
        return $this->from($table)->insert($data);
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248 2
    public function select($query, array $bindings = []): ResultSetInterface
249
    {
250 2
        return (new DoctrineResultSet($this->executeQuery($query, $bindings)))->asObject();
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256 1115
    public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result
257
    {
258 1115
        $this->prepareLogger();
259
260 1115
        $types = $types ?: Binder::types($params);
261
262 1115
        return $this->runOrReconnect(fn () => parent::executeQuery($sql, $params, $types, $qcp));
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268 1097
    public function executeStatement($sql, array $params = [], array $types = [])
269
    {
270 1097
        $this->prepareLogger();
271
272 1097
        $types = $types ?: Binder::types($params);
273
274 1097
        return $this->runOrReconnect(fn () => parent::executeStatement($sql, $params, $types));
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     *
280
     * @throws PrimeException
281
     */
282 521
    public function prepare(string $sql): Statement
283
    {
284 521
        return $this->runOrReconnect(fn () => parent::prepare($sql));
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290 864
    public function execute(Compilable $query): ResultSetInterface
291
    {
292
        try {
293 864
            $statement = $query->compile();
294
295 861
            if ($statement instanceof Statement) {
296 716
                return $this->executePrepared($statement, $query);
297
            }
298
299
            // $statement is a SQL query
300 799
            if ($query->type() === Compilable::TYPE_SELECT) {
301 742
                return new DoctrineResultSet($this->executeQuery($statement, $query->getBindings()));
302
            }
303
304 674
            return new UpdateResultSet((int) $this->executeStatement($statement, $query->getBindings()));
305 13
        } catch (DriverException $e) {
306 8
            throw new QueryExecutionException(
307 8
                'Error on execute : ' . $e->getMessage(),
308 8
                $e->getCode(),
309 8
                $e,
310 8
                $e->getQuery() ? $e->getQuery()->getSQL() : null,
311 8
                $e->getQuery() ? $e->getQuery()->getParams() : null
312 8
            );
313 5
        } catch (DoctrineDBALException $e) {
314
            /** @psalm-suppress InvalidScalarArgument */
315
            throw new QueryExecutionException('Error on execute : '.$e->getMessage(), $e->getCode(), $e);
316
        }
317
    }
318
319
    /**
320
     * Execute a prepared statement
321
     *
322
     * @param Statement $statement
323
     * @param Compilable $query
324
     *
325
     * @return ResultSetInterface The query result
326
     *
327
     * @throws DoctrineDBALException
328
     * @throws PrimeException
329
     *
330
     * @psalm-suppress InternalMethod
331
     */
332 716
    protected function executePrepared(Statement $statement, Compilable $query)
333
    {
334 716
        $statement = Binder::bindValues($statement, $query);
335 716
        $isRead = $query->type() === Compilable::TYPE_SELECT;
336
337 716
        $this->prepareLogger();
338
339
        try {
340 716
            $result = $isRead
341 310
                ? new DoctrineResultSet($statement->executeQuery())
342 716
                : new UpdateResultSet($statement->executeStatement())
343 716
            ;
344 9
        } catch (DoctrineDBALException $exception) {
345
            // Prepared query on SQLite for PHP < 7.2 invalidates the query when schema change
346
            // This process may be removed on PHP 7.2
347 9
            if ($this->causedBySchemaChange($exception)) {
348 2
                $statement = Binder::bindValues($query->compile(true), $query);
349 2
                $result = $isRead
350
                    ? new DoctrineResultSet($statement->executeQuery())
351 2
                    : new UpdateResultSet($statement->executeStatement())
352 2
                ;
353 7
            } elseif ($this->causedByLostConnection($exception->getPrevious())) { // If the connection is lost, the query must be recompiled
354
                $this->close();
355
                $this->connect();
356
357
                $statement = Binder::bindValues($query->compile(true), $query);
358
                $result = $isRead
359
                    ? new DoctrineResultSet($statement->executeQuery())
360
                    : new UpdateResultSet($statement->executeStatement())
361
                ;
362
            } else {
363 7
                throw $exception;
364
            }
365
        }
366
367 714
        return $result;
368
    }
369
370
    /**
371
     * {@inheritdoc}
372
     */
373 1095
    public function beginTransaction(): bool
374
    {
375 1095
        $this->prepareLogger();
376
377 1095
        return parent::beginTransaction() ?? true;
378
    }
379
380
    /**
381
     * {@inheritdoc}
382
     */
383 57
    public function commit(): bool
384
    {
385 57
        $this->prepareLogger();
386
387 57
        return parent::commit() ?? true;
388
    }
389
390
    /**
391
     * {@inheritdoc}
392
     */
393 1067
    public function rollBack(): bool
394
    {
395 1067
        $this->prepareLogger();
396
397 1067
        return parent::rollBack() ?? true;
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403 16
    public function close(): void
404
    {
405 16
        parent::close();
406
407 16
        $this->_eventManager->dispatchEvent(ConnectionClosedListenerInterface::EVENT_NAME);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\DBAL\Connection::$_eventManager has been deprecated. ( Ignorable by Annotation )

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

407
        /** @scrutinizer ignore-deprecated */ $this->_eventManager->dispatchEvent(ConnectionClosedListenerInterface::EVENT_NAME);
Loading history...
408
    }
409
410
    /**
411
     * Setup the logger by setting the connection
412
     *
413
     * @return void
414
     */
415 1254
    protected function prepareLogger(): void
416
    {
417
        /** @psalm-suppress InternalMethod */
418 1254
        $logger = $this->getConfiguration()->getSQLLogger();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Configuration::getSQLLogger() has been deprecated. ( Ignorable by Annotation )

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

418
        $logger = /** @scrutinizer ignore-deprecated */ $this->getConfiguration()->getSQLLogger();
Loading history...
419
420 1254
        if ($logger && $logger instanceof ConnectionAwareInterface) {
421
            $logger->setConnection($this);
422
        }
423
    }
424
425
    /**
426
     * Execute a query. Try to reconnect if needed
427
     *
428
     * @param Closure():T $callback
429
     *
430
     * @return T The query result
431
     *
432
     * @throws QueryExecutionException When an error occurs during query execution
433
     * @throws DBALException When any other error occurs
434
     *
435
     * @template T
436
     *
437
     * @psalm-suppress InternalMethod
438
     */
439 1118
    protected function runOrReconnect(Closure $callback)
440
    {
441
        try {
442
            try {
443 1118
                return $callback();
444 70
            } catch (DoctrineDBALException $exception) {
445 70
                if ($this->causedByLostConnection($exception->getPrevious())) {
446
                    // Should check for active transaction.
447
                    // Only reconnect the start transaction.
448
                    // Should raise exception during transaction.
449 2
                    $this->close();
450
                    /** @psalm-suppress InternalMethod */
451 2
                    $this->connect();
452
453 2
                    return $callback();
454
                }
455
456 68
                throw $exception;
457
            }
458 68
        } catch (DriverException $e) {
459 68
            throw new QueryExecutionException(
460 68
                'Error on execute : ' . $e->getMessage(),
461 68
                $e->getCode(),
462 68
                $e,
463 68
                $e->getQuery() ? $e->getQuery()->getSQL() : null,
464 68
                $e->getQuery() ? $e->getQuery()->getParams() : null
465 68
            );
466
        } catch (DoctrineDBALException $e) {
467
            /** @psalm-suppress InvalidScalarArgument */
468
            throw new DBALException('Error on execute : '.$e->getMessage(), $e->getCode(), $e);
469
        }
470
    }
471
}
472