Failed Conditions
Push — master ( 656579...2742cd )
by Marco
11:55
created

Connection   F

Complexity

Total Complexity 180

Size/Duplication

Total Lines 1636
Duplicated Lines 6.05 %

Test Coverage

Coverage 87.94%

Importance

Changes 0
Metric Value
wmc 180
dl 99
loc 1636
ccs 423
cts 481
cp 0.8794
rs 0.5217
c 0
b 0
f 0

68 Methods

Rating   Name   Duplication   Size   Complexity  
A getDriver() 0 3 1
A getEventManager() 0 3 1
A getDatabase() 0 3 1
A getConfiguration() 0 3 1
A getDatabasePlatform() 0 7 2
A getExpressionBuilder() 0 3 1
A getParams() 0 3 1
B __construct() 0 36 6
A isTransactionActive() 0 3 1
C commit() 17 35 11
C _bindTypedValues() 13 27 7
A setNestTransactionsWithSavepoints() 0 11 3
A _getNestedTransactionSavePointName() 0 3 1
A setTransactionIsolation() 0 5 1
B executeUpdate() 14 33 6
A getServerVersion() 0 11 3
A rollbackSavepoint() 0 7 2
A getHost() 0 3 1
A isConnected() 0 3 1
C beginTransaction() 15 23 7
A releaseSavepoint() 0 8 3
A getNestTransactionsWithSavepoints() 0 3 1
C resolveParams() 14 34 7
A insert() 0 21 4
B update() 0 24 3
A getUsername() 0 3 1
D rollBack() 0 36 9
A fetchColumn() 0 3 1
C getDatabasePlatformVersion() 0 50 7
A getBindingInfo() 6 13 3
A getSchemaManager() 0 7 2
A errorInfo() 0 5 1
A transactional() 0 13 3
C executeCacheQuery() 0 26 7
A getPort() 0 3 1
A setRollbackOnly() 0 6 2
A fetchAll() 0 3 1
A quoteIdentifier() 0 3 1
A isAutoCommit() 0 3 1
A lastInsertId() 0 5 1
A getPassword() 0 3 1
A errorCode() 0 5 1
A createSavepoint() 0 7 2
A commitAll() 0 12 4
A fetchArray() 0 3 1
A isRollbackOnly() 0 7 2
A convertToPHPValue() 0 3 1
A exec() 0 20 4
A ping() 0 14 3
A getWrappedConnection() 0 5 1
A getTransactionIsolation() 0 7 2
A createQueryBuilder() 0 3 1
B query() 0 24 4
A setAutoCommit() 0 14 4
A convertToDatabaseValue() 0 3 1
A fetchAssoc() 0 3 1
C executeQuery() 13 38 7
A delete() 0 12 3
A project() 0 12 2
A prepare() 0 11 2
A close() 0 5 1
A gatherConditions() 0 18 3
A quote() 0 7 1
A setFetchMode() 0 3 1
A extractTypeValues() 0 11 3
A detectDatabasePlatform() 0 11 2
A getTransactionNestingLevel() 0 3 1
B connect() 4 23 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL;
21
22
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
23
use Doctrine\DBAL\Exception\InvalidArgumentException;
24
use PDO;
25
use Closure;
26
use Exception;
27
use Doctrine\DBAL\Types\Type;
28
use Doctrine\DBAL\Driver\Connection as DriverConnection;
29
use Doctrine\Common\EventManager;
30
use Doctrine\DBAL\Cache\ResultCacheStatement;
31
use Doctrine\DBAL\Cache\QueryCacheProfile;
32
use Doctrine\DBAL\Cache\ArrayStatement;
33
use Doctrine\DBAL\Cache\CacheException;
34
use Doctrine\DBAL\Driver\PingableConnection;
35
use Throwable;
36
37
/**
38
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
39
 * events, transaction isolation levels, configuration, emulated transaction nesting,
40
 * lazy connecting and more.
41
 *
42
 * @link   www.doctrine-project.org
43
 * @since  2.0
44
 * @author Guilherme Blanco <[email protected]>
45
 * @author Jonathan Wage <[email protected]>
46
 * @author Roman Borschel <[email protected]>
47
 * @author Konsta Vesterinen <[email protected]>
48
 * @author Lukas Smith <[email protected]> (MDB2 library)
49
 * @author Benjamin Eberlei <[email protected]>
50
 */
51
class Connection implements DriverConnection
52
{
53
    /**
54
     * Constant for transaction isolation level READ UNCOMMITTED.
55
     */
56
    const TRANSACTION_READ_UNCOMMITTED = 1;
57
58
    /**
59
     * Constant for transaction isolation level READ COMMITTED.
60
     */
61
    const TRANSACTION_READ_COMMITTED = 2;
62
63
    /**
64
     * Constant for transaction isolation level REPEATABLE READ.
65
     */
66
    const TRANSACTION_REPEATABLE_READ = 3;
67
68
    /**
69
     * Constant for transaction isolation level SERIALIZABLE.
70
     */
71
    const TRANSACTION_SERIALIZABLE = 4;
72
73
    /**
74
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
75
     *
76
     * @var integer
77
     */
78
    const PARAM_INT_ARRAY = 101;
79
80
    /**
81
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
82
     *
83
     * @var integer
84
     */
85
    const PARAM_STR_ARRAY = 102;
86
87
    /**
88
     * Offset by which PARAM_* constants are detected as arrays of the param type.
89
     *
90
     * @var integer
91
     */
92
    const ARRAY_PARAM_OFFSET = 100;
93
94
    /**
95
     * The wrapped driver connection.
96
     *
97
     * @var \Doctrine\DBAL\Driver\Connection
98
     */
99
    protected $_conn;
100
101
    /**
102
     * @var \Doctrine\DBAL\Configuration
103
     */
104
    protected $_config;
105
106
    /**
107
     * @var \Doctrine\Common\EventManager
108
     */
109
    protected $_eventManager;
110
111
    /**
112
     * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
113
     */
114
    protected $_expr;
115
116
    /**
117
     * Whether or not a connection has been established.
118
     *
119
     * @var boolean
120
     */
121
    private $_isConnected = false;
122
123
    /**
124
     * The current auto-commit mode of this connection.
125
     *
126
     * @var boolean
127
     */
128
    private $autoCommit = true;
129
130
    /**
131
     * The transaction nesting level.
132
     *
133
     * @var integer
134
     */
135
    private $_transactionNestingLevel = 0;
136
137
    /**
138
     * The currently active transaction isolation level.
139
     *
140
     * @var integer
141
     */
142
    private $_transactionIsolationLevel;
143
144
    /**
145
     * If nested transactions should use savepoints.
146
     *
147
     * @var boolean
148
     */
149
    private $_nestTransactionsWithSavepoints = false;
150
151
    /**
152
     * The parameters used during creation of the Connection instance.
153
     *
154
     * @var array
155
     */
156
    private $_params = [];
157
158
    /**
159
     * The DatabasePlatform object that provides information about the
160
     * database platform used by the connection.
161
     *
162
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
163
     */
164
    private $platform;
165
166
    /**
167
     * The schema manager.
168
     *
169
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
170
     */
171
    protected $_schemaManager;
172
173
    /**
174
     * The used DBAL driver.
175
     *
176
     * @var \Doctrine\DBAL\Driver
177
     */
178
    protected $_driver;
179
180
    /**
181
     * Flag that indicates whether the current transaction is marked for rollback only.
182
     *
183
     * @var boolean
184
     */
185
    private $_isRollbackOnly = false;
186
187
    /**
188
     * @var integer
189
     */
190
    protected $defaultFetchMode = PDO::FETCH_ASSOC;
191
192
    /**
193
     * Initializes a new instance of the Connection class.
194
     *
195
     * @param array                              $params       The connection parameters.
196
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
197
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
198
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
199
     *
200
     * @throws \Doctrine\DBAL\DBALException
201
     */
202 131
    public function __construct(array $params, Driver $driver, Configuration $config = null,
203
            EventManager $eventManager = null)
204
    {
205 131
        $this->_driver = $driver;
206 131
        $this->_params = $params;
207
208 131
        if (isset($params['pdo'])) {
209 12
            $this->_conn = $params['pdo'];
210 12
            $this->_isConnected = true;
211 12
            unset($this->_params['pdo']);
212
        }
213
214 131
        if (isset($params["platform"])) {
215 28
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
216 1
                throw DBALException::invalidPlatformType($params['platform']);
217
            }
218
219 27
            $this->platform = $params["platform"];
220 27
            unset($this->_params["platform"]);
221
        }
222
223
        // Create default config and event manager if none given
224 131
        if ( ! $config) {
225 32
            $config = new Configuration();
226
        }
227
228 131
        if ( ! $eventManager) {
229 32
            $eventManager = new EventManager();
230
        }
231
232 131
        $this->_config = $config;
233 131
        $this->_eventManager = $eventManager;
234
235 131
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
236
237 131
        $this->autoCommit = $config->getAutoCommit();
238 131
    }
239
240
    /**
241
     * Gets the parameters used during instantiation.
242
     *
243
     * @return array
244
     */
245 97
    public function getParams()
246
    {
247 97
        return $this->_params;
248
    }
249
250
    /**
251
     * Gets the name of the database this Connection is connected to.
252
     *
253
     * @return string
254
     */
255 48
    public function getDatabase()
256
    {
257 48
        return $this->_driver->getDatabase($this);
258
    }
259
260
    /**
261
     * Gets the hostname of the currently connected database.
262
     *
263
     * @return string|null
264
     */
265 1
    public function getHost()
266
    {
267 1
        return $this->_params['host'] ?? null;
268
    }
269
270
    /**
271
     * Gets the port of the currently connected database.
272
     *
273
     * @return mixed
274
     */
275 1
    public function getPort()
276
    {
277 1
        return $this->_params['port'] ?? null;
278
    }
279
280
    /**
281
     * Gets the username used by this connection.
282
     *
283
     * @return string|null
284
     */
285 1
    public function getUsername()
286
    {
287 1
        return $this->_params['user'] ?? null;
288
    }
289
290
    /**
291
     * Gets the password used by this connection.
292
     *
293
     * @return string|null
294
     */
295 1
    public function getPassword()
296
    {
297 1
        return $this->_params['password'] ?? null;
298
    }
299
300
    /**
301
     * Gets the DBAL driver instance.
302
     *
303
     * @return \Doctrine\DBAL\Driver
304
     */
305 52
    public function getDriver()
306
    {
307 52
        return $this->_driver;
308
    }
309
310
    /**
311
     * Gets the Configuration used by the Connection.
312
     *
313
     * @return \Doctrine\DBAL\Configuration
314
     */
315 262
    public function getConfiguration()
316
    {
317 262
        return $this->_config;
318
    }
319
320
    /**
321
     * Gets the EventManager used by the Connection.
322
     *
323
     * @return \Doctrine\Common\EventManager
324
     */
325 10
    public function getEventManager()
326
    {
327 10
        return $this->_eventManager;
328
    }
329
330
    /**
331
     * Gets the DatabasePlatform for the connection.
332
     *
333
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
334
     *
335
     * @throws \Doctrine\DBAL\DBALException
336
     */
337 207
    public function getDatabasePlatform()
338
    {
339 207
        if (null === $this->platform) {
340 27
            $this->detectDatabasePlatform();
341
        }
342
343 206
        return $this->platform;
344
    }
345
346
    /**
347
     * Gets the ExpressionBuilder for the connection.
348
     *
349
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
350
     */
351
    public function getExpressionBuilder()
352
    {
353
        return $this->_expr;
354
    }
355
356
    /**
357
     * Establishes the connection with the database.
358
     *
359
     * @return boolean TRUE if the connection was successfully established, FALSE if
360
     *                 the connection is already open.
361
     */
362 268
    public function connect()
363
    {
364 268
        if ($this->_isConnected) {
365 251
            return false;
366
        }
367
368 46
        $driverOptions = $this->_params['driverOptions'] ?? [];
369 46
        $user = $this->_params['user'] ?? null;
370 46
        $password = $this->_params['password'] ?? null;
371
372 46
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
373 44
        $this->_isConnected = true;
374
375 44
        if (false === $this->autoCommit) {
376 3
            $this->beginTransaction();
377
        }
378
379 44 View Code Duplication
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380 1
            $eventArgs = new Event\ConnectionEventArgs($this);
381 1
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
382
        }
383
384 44
        return true;
385
    }
386
387
    /**
388
     * Detects and sets the database platform.
389
     *
390
     * Evaluates custom platform class and version in order to set the correct platform.
391
     *
392
     * @throws DBALException if an invalid platform was specified for this connection.
393
     */
394 27
    private function detectDatabasePlatform()
395
    {
396 27
        $version = $this->getDatabasePlatformVersion();
397
398 26
        if (null !== $version) {
399 1
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
400
        } else {
401 25
            $this->platform = $this->_driver->getDatabasePlatform();
402
        }
403
404 26
        $this->platform->setEventManager($this->_eventManager);
405 26
    }
406
407
    /**
408
     * Returns the version of the related platform if applicable.
409
     *
410
     * Returns null if either the driver is not capable to create version
411
     * specific platform instances, no explicit server version was specified
412
     * or the underlying driver connection cannot determine the platform
413
     * version without having to query it (performance reasons).
414
     *
415
     * @return string|null
416
     *
417
     * @throws Exception
418
     */
419 27
    private function getDatabasePlatformVersion()
420
    {
421
        // Driver does not support version specific platforms.
422 27
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
423 24
            return null;
424
        }
425
426
        // Explicit platform version requested (supersedes auto-detection).
427 3
        if (isset($this->_params['serverVersion'])) {
428
            return $this->_params['serverVersion'];
429
        }
430
431
        // If not connected, we need to connect now to determine the platform version.
432 3
        if (null === $this->_conn) {
433
            try {
434 3
                $this->connect();
435 1
            } catch (\Exception $originalException) {
436 1
                if (empty($this->_params['dbname'])) {
437
                    throw $originalException;
438
                }
439
440
                // The database to connect to might not yet exist.
441
                // Retry detection without database name connection parameter.
442 1
                $databaseName = $this->_params['dbname'];
443 1
                $this->_params['dbname'] = null;
444
445
                try {
446 1
                    $this->connect();
447 1
                } catch (\Exception $fallbackException) {
448
                    // Either the platform does not support database-less connections
449
                    // or something else went wrong.
450
                    // Reset connection parameters and rethrow the original exception.
451 1
                    $this->_params['dbname'] = $databaseName;
452
453 1
                    throw $originalException;
454
                }
455
456
                // Reset connection parameters.
457
                $this->_params['dbname'] = $databaseName;
458
                $serverVersion = $this->getServerVersion();
459
460
                // Close "temporary" connection to allow connecting to the real database again.
461
                $this->close();
462
463
                return $serverVersion;
464
            }
465
466
        }
467
468 2
        return $this->getServerVersion();
469
    }
470
471
    /**
472
     * Returns the database server version if the underlying driver supports it.
473
     *
474
     * @return string|null
475
     */
476 2
    private function getServerVersion()
477
    {
478
        // Automatic platform version detection.
479 2
        if ($this->_conn instanceof ServerInfoAwareConnection &&
480 2
            ! $this->_conn->requiresQueryForServerVersion()
481
        ) {
482 1
            return $this->_conn->getServerVersion();
483
        }
484
485
        // Unable to detect platform version.
486 1
        return null;
487
    }
488
489
    /**
490
     * Returns the current auto-commit mode for this connection.
491
     *
492
     * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise.
493
     *
494
     * @see    setAutoCommit
495
     */
496 2
    public function isAutoCommit()
497
    {
498 2
        return true === $this->autoCommit;
499
    }
500
501
    /**
502
     * Sets auto-commit mode for this connection.
503
     *
504
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
505
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
506
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
507
     *
508
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
509
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
510
     *
511
     * @param boolean $autoCommit True to enable auto-commit mode; false to disable it.
512
     *
513
     * @see   isAutoCommit
514
     */
515 5
    public function setAutoCommit($autoCommit)
516
    {
517 5
        $autoCommit = (boolean) $autoCommit;
518
519
        // Mode not changed, no-op.
520 5
        if ($autoCommit === $this->autoCommit) {
521 1
            return;
522
        }
523
524 5
        $this->autoCommit = $autoCommit;
525
526
        // Commit all currently active transactions if any when switching auto-commit mode.
527 5
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
528 1
            $this->commitAll();
529
        }
530 5
    }
531
532
    /**
533
     * Sets the fetch mode.
534
     *
535
     * @param integer $fetchMode
536
     *
537
     * @return void
538
     */
539 1
    public function setFetchMode($fetchMode)
540
    {
541 1
        $this->defaultFetchMode = $fetchMode;
542 1
    }
543
544
    /**
545
     * Prepares and executes an SQL query and returns the first row of the result
546
     * as an associative array.
547
     *
548
     * @param string $statement The SQL query.
549
     * @param array  $params    The query parameters.
550
     * @param array  $types     The query parameter types.
551
     *
552
     * @return array|bool False is returned if no rows are found.
553
     *
554
     * @throws \Doctrine\DBAL\DBALException
555
     */
556 42
    public function fetchAssoc($statement, array $params = [], array $types = [])
557
    {
558 42
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC);
559
    }
560
561
    /**
562
     * Prepares and executes an SQL query and returns the first row of the result
563
     * as a numerically indexed array.
564
     *
565
     * @param string $statement The SQL query to be executed.
566
     * @param array  $params    The prepared statement params.
567
     * @param array  $types     The query parameter types.
568
     *
569
     * @return array|bool False is returned if no rows are found.
570
     */
571 4
    public function fetchArray($statement, array $params = [], array $types = [])
572
    {
573 4
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM);
574
    }
575
576
    /**
577
     * Prepares and executes an SQL query and returns the value of a single column
578
     * of the first row of the result.
579
     *
580
     * @param string  $statement The SQL query to be executed.
581
     * @param array   $params    The prepared statement params.
582
     * @param integer $column    The 0-indexed column number to retrieve.
583
     * @param array  $types      The query parameter types.
584
     *
585
     * @return mixed|bool False is returned if no rows are found.
586
     *
587
     * @throws \Doctrine\DBAL\DBALException
588
     */
589 28
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
590
    {
591 28
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
592
    }
593
594
    /**
595
     * Whether an actual connection to the database is established.
596
     *
597
     * @return boolean
598
     */
599 4
    public function isConnected()
600
    {
601 4
        return $this->_isConnected;
602
    }
603
604
    /**
605
     * Checks whether a transaction is currently active.
606
     *
607
     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
608
     */
609 261
    public function isTransactionActive()
610
    {
611 261
        return $this->_transactionNestingLevel > 0;
612
    }
613
614
    /**
615
     * Gathers conditions for an update or delete call.
616
     *
617
     * @param array $identifiers Input array of columns to values
618
     *
619
     * @return string[][] a triplet with:
620
     *                    - the first key being the columns
621
     *                    - the second key being the values
622
     *                    - the third key being the conditions
623
     */
624 13
    private function gatherConditions(array $identifiers)
625
    {
626 13
        $columns = [];
627 13
        $values = [];
628 13
        $conditions = [];
629
630 13
        foreach ($identifiers as $columnName => $value) {
631 13
            if (null === $value) {
632 4
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
633 4
                continue;
634
            }
635
636 11
            $columns[] = $columnName;
637 11
            $values[] = $value;
638 11
            $conditions[] = $columnName . ' = ?';
639
        }
640
641 13
        return [$columns, $values, $conditions];
642
    }
643
644
    /**
645
     * Executes an SQL DELETE statement on a table.
646
     *
647
     * Table expression and columns are not escaped and are not safe for user-input.
648
     *
649
     * @param string $tableExpression  The expression of the table on which to delete.
650
     * @param array  $identifier The deletion criteria. An associative array containing column-value pairs.
651
     * @param array  $types      The types of identifiers.
652
     *
653
     * @return integer The number of affected rows.
654
     *
655
     * @throws \Doctrine\DBAL\DBALException
656
     * @throws InvalidArgumentException
657
     */
658 6
    public function delete($tableExpression, array $identifier, array $types = [])
659
    {
660 6
        if (empty($identifier)) {
661 1
            throw InvalidArgumentException::fromEmptyCriteria();
662
        }
663
664 5
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
665
666 5
        return $this->executeUpdate(
667 5
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
668 5
            $values,
669 5
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
670
        );
671
    }
672
673
    /**
674
     * Closes the connection.
675
     *
676
     * @return void
677
     */
678 28
    public function close()
679
    {
680 28
        $this->_conn = null;
681
682 28
        $this->_isConnected = false;
683 28
    }
684
685
    /**
686
     * Sets the transaction isolation level.
687
     *
688
     * @param integer $level The level to set.
689
     *
690
     * @return integer
691
     */
692
    public function setTransactionIsolation($level)
693
    {
694
        $this->_transactionIsolationLevel = $level;
695
696
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
697
    }
698
699
    /**
700
     * Gets the currently active transaction isolation level.
701
     *
702
     * @return integer The current transaction isolation level.
703
     */
704
    public function getTransactionIsolation()
705
    {
706
        if (null === $this->_transactionIsolationLevel) {
707
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
708
        }
709
710
        return $this->_transactionIsolationLevel;
711
    }
712
713
    /**
714
     * Executes an SQL UPDATE statement on a table.
715
     *
716
     * Table expression and columns are not escaped and are not safe for user-input.
717
     *
718
     * @param string $tableExpression  The expression of the table to update quoted or unquoted.
719
     * @param array  $data       An associative array containing column-value pairs.
720
     * @param array  $identifier The update criteria. An associative array containing column-value pairs.
721
     * @param array  $types      Types of the merged $data and $identifier arrays in that order.
722
     *
723
     * @return integer The number of affected rows.
724
     *
725
     * @throws \Doctrine\DBAL\DBALException
726
     */
727 8
    public function update($tableExpression, array $data, array $identifier, array $types = [])
728
    {
729 8
        $setColumns = [];
730 8
        $setValues = [];
731 8
        $set = [];
732
733 8
        foreach ($data as $columnName => $value) {
734 8
            $setColumns[] = $columnName;
735 8
            $setValues[] = $value;
736 8
            $set[] = $columnName . ' = ?';
737
        }
738
739 8
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
740 8
        $columns = array_merge($setColumns, $conditionColumns);
741 8
        $values = array_merge($setValues, $conditionValues);
742
743 8
        if (is_string(key($types))) {
744 5
            $types = $this->extractTypeValues($columns, $types);
745
        }
746
747 8
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
748 8
                . ' WHERE ' . implode(' AND ', $conditions);
749
750 8
        return $this->executeUpdate($sql, $values, $types);
751
    }
752
753
    /**
754
     * Inserts a table row with specified data.
755
     *
756
     * Table expression and columns are not escaped and are not safe for user-input.
757
     *
758
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
759
     * @param array  $data      An associative array containing column-value pairs.
760
     * @param array  $types     Types of the inserted data.
761
     *
762
     * @return integer The number of affected rows.
763
     *
764
     * @throws \Doctrine\DBAL\DBALException
765
     */
766 78
    public function insert($tableExpression, array $data, array $types = [])
767
    {
768 78
        if (empty($data)) {
769 1
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
770
        }
771
772 77
        $columns = [];
773 77
        $values = [];
774 77
        $set = [];
775
776 77
        foreach ($data as $columnName => $value) {
777 77
            $columns[] = $columnName;
778 77
            $values[] = $value;
779 77
            $set[] = '?';
780
        }
781
782 77
        return $this->executeUpdate(
783 77
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
784 77
            ' VALUES (' . implode(', ', $set) . ')',
785 77
            $values,
786 77
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
787
        );
788
    }
789
790
    /**
791
     * Extract ordered type list from an ordered column list and type map.
792
     *
793
     * @param array $columnList
794
     * @param array $types
795
     *
796
     * @return array
797
     */
798 9
    private function extractTypeValues(array $columnList, array $types)
799
    {
800 9
        $typeValues = [];
801
802 9
        foreach ($columnList as $columnIndex => $columnName) {
803 9
            $typeValues[] = isset($types[$columnName])
804 9
                ? $types[$columnName]
805 9
                : \PDO::PARAM_STR;
806
        }
807
808 9
        return $typeValues;
809
    }
810
811
    /**
812
     * Quotes a string so it can be safely used as a table or column name, even if
813
     * it is a reserved name.
814
     *
815
     * Delimiting style depends on the underlying database platform that is being used.
816
     *
817
     * NOTE: Just because you CAN use quoted identifiers does not mean
818
     * you SHOULD use them. In general, they end up causing way more
819
     * problems than they solve.
820
     *
821
     * @param string $str The name to be quoted.
822
     *
823
     * @return string The quoted name.
824
     */
825 1
    public function quoteIdentifier($str)
826
    {
827 1
        return $this->getDatabasePlatform()->quoteIdentifier($str);
828
    }
829
830
    /**
831
     * Quotes a given input parameter.
832
     *
833
     * @param mixed       $input The parameter to be quoted.
834
     * @param int|null $type  The type of the parameter.
835
     *
836
     * @return string The quoted parameter.
837
     */
838 4
    public function quote($input, $type = null)
839
    {
840 4
        $this->connect();
841
842 4
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
843
844 4
        return $this->_conn->quote($value, $bindingType);
845
    }
846
847
    /**
848
     * Prepares and executes an SQL query and returns the result as an associative array.
849
     *
850
     * @param string $sql    The SQL query.
851
     * @param array  $params The query parameters.
852
     * @param array  $types  The query parameter types.
853
     *
854
     * @return array
855
     */
856 87
    public function fetchAll($sql, array $params = [], $types = [])
857
    {
858 87
        return $this->executeQuery($sql, $params, $types)->fetchAll();
859
    }
860
861
    /**
862
     * Prepares an SQL statement.
863
     *
864
     * @param string $statement The SQL statement to prepare.
865
     *
866
     * @return \Doctrine\DBAL\Statement The prepared statement.
867
     *
868
     * @throws \Doctrine\DBAL\DBALException
869
     */
870 40
    public function prepare($statement)
871
    {
872
        try {
873 40
            $stmt = new Statement($statement, $this);
874 1
        } catch (Exception $ex) {
875 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
876
        }
877
878 39
        $stmt->setFetchMode($this->defaultFetchMode);
879
880 39
        return $stmt;
881
    }
882
883
    /**
884
     * Executes an, optionally parametrized, SQL query.
885
     *
886
     * If the query is parametrized, a prepared statement is used.
887
     * If an SQLLogger is configured, the execution is logged.
888
     *
889
     * @param string                                      $query  The SQL query to execute.
890
     * @param array                                       $params The parameters to bind to the query, if any.
891
     * @param array                                       $types  The types the previous parameters are in.
892
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
893
     *
894
     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
895
     *
896
     * @throws \Doctrine\DBAL\DBALException
897
     */
898 183
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
899
    {
900 183
        if ($qcp !== null) {
901 10
            return $this->executeCacheQuery($query, $params, $types, $qcp);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->executeCac... $params, $types, $qcp) returns the type Doctrine\DBAL\Cache\Resu...AL\Cache\ArrayStatement which is incompatible with the documented return type Doctrine\DBAL\Driver\Statement.
Loading history...
902
        }
903
904 183
        $this->connect();
905
906 183
        $logger = $this->_config->getSQLLogger();
907 183
        if ($logger) {
908 178
            $logger->startQuery($query, $params, $types);
909
        }
910
911
        try {
912 183 View Code Duplication
            if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
913 31
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
914
915 31
                $stmt = $this->_conn->prepare($query);
916 31
                if ($types) {
917 15
                    $this->_bindTypedValues($stmt, $params, $types);
918 15
                    $stmt->execute();
919
                } else {
920 31
                    $stmt->execute($params);
921
                }
922
            } else {
923 179
                $stmt = $this->_conn->query($query);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Driver\Connection::query() has too many arguments starting with $query. ( Ignorable by Annotation )

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

923
                /** @scrutinizer ignore-call */ 
924
                $stmt = $this->_conn->query($query);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
924
            }
925 8
        } catch (Exception $ex) {
926 8
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
927
        }
928
929 175
        $stmt->setFetchMode($this->defaultFetchMode);
930
931 175
        if ($logger) {
932 171
            $logger->stopQuery();
933
        }
934
935 175
        return $stmt;
936
    }
937
938
    /**
939
     * Executes a caching query.
940
     *
941
     * @param string                                 $query  The SQL query to execute.
942
     * @param array                                  $params The parameters to bind to the query, if any.
943
     * @param array                                  $types  The types the previous parameters are in.
944
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
945
     *
946
     * @return \Doctrine\DBAL\Driver\ResultStatement
947
     *
948
     * @throws \Doctrine\DBAL\Cache\CacheException
949
     */
950 12
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
951
    {
952 12
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
953 12
        if ( ! $resultCache) {
954
            throw CacheException::noResultDriverConfigured();
955
        }
956
957 12
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
958
959
        // fetch the row pointers entry
960 12
        if ($data = $resultCache->fetch($cacheKey)) {
961
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
962 9
            if (isset($data[$realKey])) {
963 9
                $stmt = new ArrayStatement($data[$realKey]);
964
            } elseif (array_key_exists($realKey, $data)) {
965
                $stmt = new ArrayStatement([]);
966
            }
967
        }
968
969 12
        if (!isset($stmt)) {
970 10
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
971
        }
972
973 12
        $stmt->setFetchMode($this->defaultFetchMode);
974
975 12
        return $stmt;
976
    }
977
978
    /**
979
     * Executes an, optionally parametrized, SQL query and returns the result,
980
     * applying a given projection/transformation function on each row of the result.
981
     *
982
     * @param string   $query    The SQL query to execute.
983
     * @param array    $params   The parameters, if any.
984
     * @param \Closure $function The transformation function that is applied on each row.
985
     *                           The function receives a single parameter, an array, that
986
     *                           represents a row of the result set.
987
     *
988
     * @return array The projected result of the query.
989
     */
990
    public function project($query, array $params, Closure $function)
991
    {
992
        $result = [];
993
        $stmt = $this->executeQuery($query, $params);
994
995
        while ($row = $stmt->fetch()) {
996
            $result[] = $function($row);
997
        }
998
999
        $stmt->closeCursor();
1000
1001
        return $result;
1002
    }
1003
1004
    /**
1005
     * Executes an SQL statement, returning a result set as a Statement object.
1006
     *
1007
     * @return \Doctrine\DBAL\Driver\Statement
1008
     *
1009
     * @throws \Doctrine\DBAL\DBALException
1010
     */
1011 8
    public function query()
1012
    {
1013 8
        $this->connect();
1014
1015 8
        $args = func_get_args();
1016
1017 8
        $logger = $this->_config->getSQLLogger();
1018 8
        if ($logger) {
1019 7
            $logger->startQuery($args[0]);
1020
        }
1021
1022
        try {
1023 8
            $statement = $this->_conn->query(...$args);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\DBAL\Driver\Connection::query() has too many arguments starting with $args. ( Ignorable by Annotation )

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

1023
            /** @scrutinizer ignore-call */ 
1024
            $statement = $this->_conn->query(...$args);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1024 1
        } catch (Exception $ex) {
1025 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1026
        }
1027
1028 7
        $statement->setFetchMode($this->defaultFetchMode);
1029
1030 7
        if ($logger) {
1031 7
            $logger->stopQuery();
1032
        }
1033
1034 7
        return $statement;
1035
    }
1036
1037
    /**
1038
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1039
     * and returns the number of affected rows.
1040
     *
1041
     * This method supports PDO binding types as well as DBAL mapping types.
1042
     *
1043
     * @param string $query  The SQL query.
1044
     * @param array  $params The query parameters.
1045
     * @param array  $types  The parameter types.
1046
     *
1047
     * @return integer The number of affected rows.
1048
     *
1049
     * @throws \Doctrine\DBAL\DBALException
1050
     */
1051 141
    public function executeUpdate($query, array $params = [], array $types = [])
1052
    {
1053 141
        $this->connect();
1054
1055 141
        $logger = $this->_config->getSQLLogger();
1056 141
        if ($logger) {
1057 136
            $logger->startQuery($query, $params, $types);
1058
        }
1059
1060
        try {
1061 141 View Code Duplication
            if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1062 83
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1063
1064 83
                $stmt = $this->_conn->prepare($query);
1065 82
                if ($types) {
1066 12
                    $this->_bindTypedValues($stmt, $params, $types);
1067 12
                    $stmt->execute();
1068
                } else {
1069 70
                    $stmt->execute($params);
1070
                }
1071 81
                $result = $stmt->rowCount();
1072
            } else {
1073 139
                $result = $this->_conn->exec($query);
1074
            }
1075 52
        } catch (Exception $ex) {
1076 52
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1077
        }
1078
1079 138
        if ($logger) {
1080 134
            $logger->stopQuery();
1081
        }
1082
1083 138
        return $result;
1084
    }
1085
1086
    /**
1087
     * Executes an SQL statement and return the number of affected rows.
1088
     *
1089
     * @param string $statement
1090
     *
1091
     * @return integer The number of affected rows.
1092
     *
1093
     * @throws \Doctrine\DBAL\DBALException
1094
     */
1095 33
    public function exec($statement)
1096
    {
1097 33
        $this->connect();
1098
1099 32
        $logger = $this->_config->getSQLLogger();
1100 32
        if ($logger) {
1101 28
            $logger->startQuery($statement);
1102
        }
1103
1104
        try {
1105 32
            $result = $this->_conn->exec($statement);
1106 4
        } catch (Exception $ex) {
1107 4
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1108
        }
1109
1110 30
        if ($logger) {
1111 28
            $logger->stopQuery();
1112
        }
1113
1114 30
        return $result;
1115
    }
1116
1117
    /**
1118
     * Returns the current transaction nesting level.
1119
     *
1120
     * @return integer The nesting level. A value of 0 means there's no active transaction.
1121
     */
1122 15
    public function getTransactionNestingLevel()
1123
    {
1124 15
        return $this->_transactionNestingLevel;
1125
    }
1126
1127
    /**
1128
     * Fetches the SQLSTATE associated with the last database operation.
1129
     *
1130
     * @return integer The last error code.
1131
     */
1132
    public function errorCode()
1133
    {
1134
        $this->connect();
1135
1136
        return $this->_conn->errorCode();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_conn->errorCode() also could return the type string which is incompatible with the documented return type integer.
Loading history...
1137
    }
1138
1139
    /**
1140
     * Fetches extended error information associated with the last database operation.
1141
     *
1142
     * @return array The last error information.
1143
     */
1144
    public function errorInfo()
1145
    {
1146
        $this->connect();
1147
1148
        return $this->_conn->errorInfo();
1149
    }
1150
1151
    /**
1152
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1153
     * depending on the underlying driver.
1154
     *
1155
     * Note: This method may not return a meaningful or consistent result across different drivers,
1156
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1157
     * columns or sequences.
1158
     *
1159
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
1160
     *
1161
     * @return string A string representation of the last inserted ID.
1162
     */
1163 2
    public function lastInsertId($seqName = null)
1164
    {
1165 2
        $this->connect();
1166
1167 2
        return $this->_conn->lastInsertId($seqName);
1168
    }
1169
1170
    /**
1171
     * Executes a function in a transaction.
1172
     *
1173
     * The function gets passed this Connection instance as an (optional) parameter.
1174
     *
1175
     * If an exception occurs during execution of the function or transaction commit,
1176
     * the transaction is rolled back and the exception re-thrown.
1177
     *
1178
     * @param \Closure $func The function to execute transactionally.
1179
     *
1180
     * @return mixed The value returned by $func
1181
     *
1182
     * @throws Exception
1183
     * @throws Throwable
1184
     */
1185 4
    public function transactional(Closure $func)
1186
    {
1187 4
        $this->beginTransaction();
1188
        try {
1189 4
            $res = $func($this);
1190 2
            $this->commit();
1191 2
            return $res;
1192 2
        } catch (Exception $e) {
1193 1
            $this->rollBack();
1194 1
            throw $e;
1195 1
        } catch (Throwable $e) {
1196 1
            $this->rollBack();
1197 1
            throw $e;
1198
        }
1199
    }
1200
1201
    /**
1202
     * Sets if nested transactions should use savepoints.
1203
     *
1204
     * @param boolean $nestTransactionsWithSavepoints
1205
     *
1206
     * @return void
1207
     *
1208
     * @throws \Doctrine\DBAL\ConnectionException
1209
     */
1210 2
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1211
    {
1212 2
        if ($this->_transactionNestingLevel > 0) {
1213 2
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1214
        }
1215
1216 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1217
            throw ConnectionException::savepointsNotSupported();
1218
        }
1219
1220 1
        $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1221 1
    }
1222
1223
    /**
1224
     * Gets if nested transactions should use savepoints.
1225
     *
1226
     * @return boolean
1227
     */
1228 1
    public function getNestTransactionsWithSavepoints()
1229
    {
1230 1
        return $this->_nestTransactionsWithSavepoints;
1231
    }
1232
1233
    /**
1234
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1235
     * "savepointFormat" parameter is not set
1236
     *
1237
     * @return mixed A string with the savepoint name or false.
1238
     */
1239 1
    protected function _getNestedTransactionSavePointName()
1240
    {
1241 1
        return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
1242
    }
1243
1244
    /**
1245
     * Starts a transaction by suspending auto-commit mode.
1246
     *
1247
     * @return void
1248
     */
1249 18
    public function beginTransaction()
1250
    {
1251 18
        $this->connect();
1252
1253 18
        ++$this->_transactionNestingLevel;
1254
1255 18
        $logger = $this->_config->getSQLLogger();
1256
1257 18 View Code Duplication
        if ($this->_transactionNestingLevel == 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1258 18
            if ($logger) {
1259 13
                $logger->startQuery('"START TRANSACTION"');
1260
            }
1261 18
            $this->_conn->beginTransaction();
1262 18
            if ($logger) {
1263 18
                $logger->stopQuery();
1264
            }
1265 3
        } elseif ($this->_nestTransactionsWithSavepoints) {
1266 1
            if ($logger) {
1267 1
                $logger->startQuery('"SAVEPOINT"');
1268
            }
1269 1
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1270 1
            if ($logger) {
1271 1
                $logger->stopQuery();
1272
            }
1273
        }
1274 18
    }
1275
1276
    /**
1277
     * Commits the current transaction.
1278
     *
1279
     * @return void
1280
     *
1281
     * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
1282
     *                                            because the transaction was marked for rollback only.
1283
     */
1284 9
    public function commit()
1285
    {
1286 9
        if ($this->_transactionNestingLevel == 0) {
1287 1
            throw ConnectionException::noActiveTransaction();
1288
        }
1289 8
        if ($this->_isRollbackOnly) {
1290 2
            throw ConnectionException::commitFailedRollbackOnly();
1291
        }
1292
1293 6
        $this->connect();
1294
1295 6
        $logger = $this->_config->getSQLLogger();
1296
1297 6 View Code Duplication
        if ($this->_transactionNestingLevel == 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1298 6
            if ($logger) {
1299 4
                $logger->startQuery('"COMMIT"');
1300
            }
1301 6
            $this->_conn->commit();
1302 6
            if ($logger) {
1303 6
                $logger->stopQuery();
1304
            }
1305 2
        } elseif ($this->_nestTransactionsWithSavepoints) {
1306 1
            if ($logger) {
1307 1
                $logger->startQuery('"RELEASE SAVEPOINT"');
1308
            }
1309 1
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1310 1
            if ($logger) {
1311 1
                $logger->stopQuery();
1312
            }
1313
        }
1314
1315 6
        --$this->_transactionNestingLevel;
1316
1317 6
        if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) {
1318 2
            $this->beginTransaction();
1319
        }
1320 6
    }
1321
1322
    /**
1323
     * Commits all current nesting transactions.
1324
     */
1325 1
    private function commitAll()
1326
    {
1327 1
        while (0 !== $this->_transactionNestingLevel) {
1328 1
            if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) {
1329
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1330
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1331 1
                $this->commit();
1332
1333 1
                return;
1334
            }
1335
1336 1
            $this->commit();
1337
        }
1338 1
    }
1339
1340
    /**
1341
     * Cancels any database changes done during the current transaction.
1342
     *
1343
     * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
1344
     */
1345 12
    public function rollBack()
1346
    {
1347 12
        if ($this->_transactionNestingLevel == 0) {
1348 1
            throw ConnectionException::noActiveTransaction();
1349
        }
1350
1351 11
        $this->connect();
1352
1353 11
        $logger = $this->_config->getSQLLogger();
1354
1355 11
        if ($this->_transactionNestingLevel == 1) {
1356 10
            if ($logger) {
1357 9
                $logger->startQuery('"ROLLBACK"');
1358
            }
1359 10
            $this->_transactionNestingLevel = 0;
1360 10
            $this->_conn->rollBack();
1361 10
            $this->_isRollbackOnly = false;
1362 10
            if ($logger) {
1363 9
                $logger->stopQuery();
1364
            }
1365
1366 10
            if (false === $this->autoCommit) {
1367 10
                $this->beginTransaction();
1368
            }
1369 2
        } elseif ($this->_nestTransactionsWithSavepoints) {
1370 1
            if ($logger) {
1371 1
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1372
            }
1373 1
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1374 1
            --$this->_transactionNestingLevel;
1375 1
            if ($logger) {
1376 1
                $logger->stopQuery();
1377
            }
1378
        } else {
1379 1
            $this->_isRollbackOnly = true;
1380 1
            --$this->_transactionNestingLevel;
1381
        }
1382 11
    }
1383
1384
    /**
1385
     * Creates a new savepoint.
1386
     *
1387
     * @param string $savepoint The name of the savepoint to create.
1388
     *
1389
     * @return void
1390
     *
1391
     * @throws \Doctrine\DBAL\ConnectionException
1392
     */
1393 1
    public function createSavepoint($savepoint)
1394
    {
1395 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1396
            throw ConnectionException::savepointsNotSupported();
1397
        }
1398
1399 1
        $this->_conn->exec($this->platform->createSavePoint($savepoint));
1400 1
    }
1401
1402
    /**
1403
     * Releases the given savepoint.
1404
     *
1405
     * @param string $savepoint The name of the savepoint to release.
1406
     *
1407
     * @return void
1408
     *
1409
     * @throws \Doctrine\DBAL\ConnectionException
1410
     */
1411 1
    public function releaseSavepoint($savepoint)
1412
    {
1413 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1414
            throw ConnectionException::savepointsNotSupported();
1415
        }
1416
1417 1
        if ($this->platform->supportsReleaseSavepoints()) {
1418 1
            $this->_conn->exec($this->platform->releaseSavePoint($savepoint));
1419
        }
1420 1
    }
1421
1422
    /**
1423
     * Rolls back to the given savepoint.
1424
     *
1425
     * @param string $savepoint The name of the savepoint to rollback to.
1426
     *
1427
     * @return void
1428
     *
1429
     * @throws \Doctrine\DBAL\ConnectionException
1430
     */
1431 1
    public function rollbackSavepoint($savepoint)
1432
    {
1433 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1434
            throw ConnectionException::savepointsNotSupported();
1435
        }
1436
1437 1
        $this->_conn->exec($this->platform->rollbackSavePoint($savepoint));
1438 1
    }
1439
1440
    /**
1441
     * Gets the wrapped driver connection.
1442
     *
1443
     * @return \Doctrine\DBAL\Driver\Connection
1444
     */
1445 47
    public function getWrappedConnection()
1446
    {
1447 47
        $this->connect();
1448
1449 47
        return $this->_conn;
1450
    }
1451
1452
    /**
1453
     * Gets the SchemaManager that can be used to inspect or change the
1454
     * database schema through the connection.
1455
     *
1456
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
1457
     */
1458 141
    public function getSchemaManager()
1459
    {
1460 141
        if ( ! $this->_schemaManager) {
1461 11
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1462
        }
1463
1464 141
        return $this->_schemaManager;
1465
    }
1466
1467
    /**
1468
     * Marks the current transaction so that the only possible
1469
     * outcome for the transaction to be rolled back.
1470
     *
1471
     * @return void
1472
     *
1473
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1474
     */
1475 2
    public function setRollbackOnly()
1476
    {
1477 2
        if ($this->_transactionNestingLevel == 0) {
1478 1
            throw ConnectionException::noActiveTransaction();
1479
        }
1480 1
        $this->_isRollbackOnly = true;
1481 1
    }
1482
1483
    /**
1484
     * Checks whether the current transaction is marked for rollback only.
1485
     *
1486
     * @return boolean
1487
     *
1488
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1489
     */
1490 3
    public function isRollbackOnly()
1491
    {
1492 3
        if ($this->_transactionNestingLevel == 0) {
1493 1
            throw ConnectionException::noActiveTransaction();
1494
        }
1495
1496 2
        return $this->_isRollbackOnly;
1497
    }
1498
1499
    /**
1500
     * Converts a given value to its database representation according to the conversion
1501
     * rules of a specific DBAL mapping type.
1502
     *
1503
     * @param mixed  $value The value to convert.
1504
     * @param string $type  The name of the DBAL mapping type.
1505
     *
1506
     * @return mixed The converted value.
1507
     */
1508
    public function convertToDatabaseValue($value, $type)
1509
    {
1510
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1511
    }
1512
1513
    /**
1514
     * Converts a given value to its PHP representation according to the conversion
1515
     * rules of a specific DBAL mapping type.
1516
     *
1517
     * @param mixed  $value The value to convert.
1518
     * @param string $type  The name of the DBAL mapping type.
1519
     *
1520
     * @return mixed The converted type.
1521
     */
1522
    public function convertToPHPValue($value, $type)
1523
    {
1524
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1525
    }
1526
1527
    /**
1528
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1529
     * or DBAL mapping type, to a given statement.
1530
     *
1531
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1532
     * @param array                           $params The map/list of named/positional parameters.
1533
     * @param array                           $types  The parameter types (PDO binding types or DBAL mapping types).
1534
     *
1535
     * @return void
1536
     *
1537
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1538
     *           raw PDOStatement instances.
1539
     */
1540 26
    private function _bindTypedValues($stmt, array $params, array $types)
1541
    {
1542
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1543 26
        if (is_int(key($params))) {
1544
            // Positional parameters
1545 26
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1546 26
            $bindIndex = 1;
1547 26
            foreach ($params as $value) {
1548 26
                $typeIndex = $bindIndex + $typeOffset;
1549 26 View Code Duplication
                if (isset($types[$typeIndex])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1550 26
                    $type = $types[$typeIndex];
1551 26
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1552 26
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1553
                } else {
1554 1
                    $stmt->bindValue($bindIndex, $value);
1555
                }
1556 26
                ++$bindIndex;
1557
            }
1558
        } else {
1559
            // Named parameters
1560
            foreach ($params as $name => $value) {
1561 View Code Duplication
                if (isset($types[$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1562
                    $type = $types[$name];
1563
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1564
                    $stmt->bindValue($name, $value, $bindingType);
1565
                } else {
1566
                    $stmt->bindValue($name, $value);
1567
                }
1568
            }
1569
        }
1570 26
    }
1571
1572
    /**
1573
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1574
     *
1575
     * @param mixed $value The value to bind.
1576
     * @param mixed $type  The type to bind (PDO or DBAL).
1577
     *
1578
     * @return array [0] => the (escaped) value, [1] => the binding type.
1579
     */
1580 30
    private function getBindingInfo($value, $type)
1581
    {
1582 30
        if (is_string($type)) {
1583 12
            $type = Type::getType($type);
1584
        }
1585 30 View Code Duplication
        if ($type instanceof Type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1586 12
            $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1587 12
            $bindingType = $type->getBindingType();
1588
        } else {
1589 24
            $bindingType = $type; // PDO::PARAM_* constants
1590
        }
1591
1592 30
        return [$value, $bindingType];
1593
    }
1594
1595
    /**
1596
     * Resolves the parameters to a format which can be displayed.
1597
     *
1598
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1599
     *           copy/paste the code as this method may change, or be removed without prior notice.
1600
     *
1601
     * @param array $params
1602
     * @param array $types
1603
     *
1604
     * @return array
1605
     */
1606 60
    public function resolveParams(array $params, array $types)
1607
    {
1608 60
        $resolvedParams = [];
1609
1610
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1611 60
        if (is_int(key($params))) {
1612
            // Positional parameters
1613 8
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1614 8
            $bindIndex = 1;
1615 8
            foreach ($params as $value) {
1616 8
                $typeIndex = $bindIndex + $typeOffset;
1617 8 View Code Duplication
                if (isset($types[$typeIndex])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1618
                    $type = $types[$typeIndex];
1619
                    list($value,) = $this->getBindingInfo($value, $type);
1620
                    $resolvedParams[$bindIndex] = $value;
1621
                } else {
1622 8
                    $resolvedParams[$bindIndex] = $value;
1623
                }
1624 8
                ++$bindIndex;
1625
            }
1626
        } else {
1627
            // Named parameters
1628 52
            foreach ($params as $name => $value) {
1629 View Code Duplication
                if (isset($types[$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1630
                    $type = $types[$name];
1631
                    list($value,) = $this->getBindingInfo($value, $type);
1632
                    $resolvedParams[$name] = $value;
1633
                } else {
1634
                    $resolvedParams[$name] = $value;
1635
                }
1636
            }
1637
        }
1638
1639 60
        return $resolvedParams;
1640
    }
1641
1642
    /**
1643
     * Creates a new instance of a SQL query builder.
1644
     *
1645
     * @return \Doctrine\DBAL\Query\QueryBuilder
1646
     */
1647
    public function createQueryBuilder()
1648
    {
1649
        return new Query\QueryBuilder($this);
1650
    }
1651
1652
    /**
1653
     * Ping the server
1654
     *
1655
     * When the server is not available the method returns FALSE.
1656
     * It is responsibility of the developer to handle this case
1657
     * and abort the request or reconnect manually:
1658
     *
1659
     * @example
1660
     *
1661
     *   if ($conn->ping() === false) {
1662
     *      $conn->close();
1663
     *      $conn->connect();
1664
     *   }
1665
     *
1666
     * It is undefined if the underlying driver attempts to reconnect
1667
     * or disconnect when the connection is not available anymore
1668
     * as long it returns TRUE when a reconnect succeeded and
1669
     * FALSE when the connection was dropped.
1670
     *
1671
     * @return bool
1672
     */
1673 1
    public function ping()
1674
    {
1675 1
        $this->connect();
1676
1677 1
        if ($this->_conn instanceof PingableConnection) {
1678
            return $this->_conn->ping();
1679
        }
1680
1681
        try {
1682 1
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1683
1684 1
            return true;
1685
        } catch (DBALException $e) {
1686
            return false;
1687
        }
1688
    }
1689
}
1690