Passed
Push — phpstan ( d53556...752f9f )
by Michael
24:26
created

Connection   F

Complexity

Total Complexity 179

Size/Duplication

Total Lines 1644
Duplicated Lines 0 %

Test Coverage

Coverage 89.79%

Importance

Changes 0
Metric Value
wmc 179
dl 0
loc 1644
ccs 431
cts 480
cp 0.8979
rs 0.8
c 0
b 0
f 0

68 Methods

Rating   Name   Duplication   Size   Complexity  
A getDriver() 0 3 1
A isTransactionActive() 0 3 1
A getEventManager() 0 3 1
B commit() 0 35 11
A getDatabase() 0 3 1
B _bindTypedValues() 0 27 7
A _getNestedTransactionSavePointName() 0 3 1
A setTransactionIsolation() 0 5 1
B executeUpdate() 0 33 6
A getServerVersion() 0 11 3
A getHost() 0 3 1
B __construct() 0 36 6
A isConnected() 0 3 1
A getConfiguration() 0 3 1
B beginTransaction() 0 23 7
A getNestTransactionsWithSavepoints() 0 3 1
A getDatabasePlatform() 0 7 2
B resolveParams() 0 34 7
A insert() 0 21 4
A update() 0 24 3
A getUsername() 0 3 1
B rollBack() 0 36 9
A fetchColumn() 0 3 1
B getDatabasePlatformVersion() 0 50 7
A getBindingInfo() 0 13 3
A getSchemaManager() 0 7 2
A errorInfo() 0 5 1
A transactional() 0 13 3
B 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 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
A query() 0 24 4
A setAutoCommit() 0 14 4
A convertToDatabaseValue() 0 3 1
A fetchAssoc() 0 3 1
B executeQuery() 0 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 getExpressionBuilder() 0 3 1
A setFetchMode() 0 3 1
A extractTypeValues() 0 9 2
A detectDatabasePlatform() 0 13 2
A getTransactionNestingLevel() 0 3 1
A getParams() 0 3 1
A connect() 0 23 4
A setNestTransactionsWithSavepoints() 0 11 3
A rollbackSavepoint() 0 7 2
A releaseSavepoint() 0 8 3
A createSavepoint() 0 7 2

How to fix   Complexity   

Complex Class

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 Closure;
25
use Exception;
26
use Doctrine\DBAL\Types\Type;
27
use Doctrine\DBAL\Driver\Connection as DriverConnection;
28
use Doctrine\DBAL\Driver\Statement as DriverStatement;
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
use function array_key_exists;
37
use function array_merge;
38
use function func_get_args;
39
use function implode;
40
use function is_int;
41
use function is_string;
42
use function key;
43
44
/**
45
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
46
 * events, transaction isolation levels, configuration, emulated transaction nesting,
47
 * lazy connecting and more.
48
 *
49
 * @link   www.doctrine-project.org
50
 * @since  2.0
51
 * @author Guilherme Blanco <[email protected]>
52
 * @author Jonathan Wage <[email protected]>
53
 * @author Roman Borschel <[email protected]>
54
 * @author Konsta Vesterinen <[email protected]>
55
 * @author Lukas Smith <[email protected]> (MDB2 library)
56
 * @author Benjamin Eberlei <[email protected]>
57
 */
58
class Connection implements DriverConnection
59
{
60
    /**
61
     * Constant for transaction isolation level READ UNCOMMITTED.
62
     *
63
     * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
64
     */
65
    public const TRANSACTION_READ_UNCOMMITTED = TransactionIsolationLevel::READ_UNCOMMITTED;
66
67
    /**
68
     * Constant for transaction isolation level READ COMMITTED.
69
     *
70
     * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
71
     */
72
    public const TRANSACTION_READ_COMMITTED = TransactionIsolationLevel::READ_COMMITTED;
73
74
    /**
75
     * Constant for transaction isolation level REPEATABLE READ.
76
     *
77
     * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
78
     */
79
    public const TRANSACTION_REPEATABLE_READ = TransactionIsolationLevel::REPEATABLE_READ;
80
81
    /**
82
     * Constant for transaction isolation level SERIALIZABLE.
83
     *
84
     * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
85
     */
86
    public const TRANSACTION_SERIALIZABLE = TransactionIsolationLevel::SERIALIZABLE;
87
88
    /**
89
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
90
     *
91
     * @var int
92
     */
93
    public const PARAM_INT_ARRAY = ParameterType::INTEGER + self::ARRAY_PARAM_OFFSET;
94
95
    /**
96
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
97
     *
98
     * @var int
99
     */
100
    public const PARAM_STR_ARRAY = ParameterType::STRING + self::ARRAY_PARAM_OFFSET;
101
102
    /**
103
     * Offset by which PARAM_* constants are detected as arrays of the param type.
104
     *
105
     * @var int
106
     */
107
    const ARRAY_PARAM_OFFSET = 100;
108
109
    /**
110
     * The wrapped driver connection.
111
     *
112
     * @var \Doctrine\DBAL\Driver\Connection|null
113
     */
114
    protected $_conn;
115
116
    /**
117
     * @var \Doctrine\DBAL\Configuration
118
     */
119
    protected $_config;
120
121
    /**
122
     * @var \Doctrine\Common\EventManager
123
     */
124
    protected $_eventManager;
125
126
    /**
127
     * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
128
     */
129
    protected $_expr;
130
131
    /**
132
     * Whether or not a connection has been established.
133
     *
134
     * @var bool
135
     */
136
    private $_isConnected = false;
137
138
    /**
139
     * The current auto-commit mode of this connection.
140
     *
141
     * @var bool
142
     */
143
    private $autoCommit = true;
144
145
    /**
146
     * The transaction nesting level.
147
     *
148
     * @var int
149
     */
150
    private $_transactionNestingLevel = 0;
151
152
    /**
153
     * The currently active transaction isolation level.
154
     *
155
     * @var int
156
     */
157
    private $_transactionIsolationLevel;
158
159
    /**
160
     * If nested transactions should use savepoints.
161
     *
162
     * @var bool
163
     */
164
    private $_nestTransactionsWithSavepoints = false;
165
166
    /**
167
     * The parameters used during creation of the Connection instance.
168
     *
169
     * @var array
170
     */
171
    private $_params = [];
172
173
    /**
174
     * The DatabasePlatform object that provides information about the
175
     * database platform used by the connection.
176
     *
177
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
178
     */
179
    private $platform;
180
181
    /**
182
     * The schema manager.
183
     *
184
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
185
     */
186
    protected $_schemaManager;
187
188
    /**
189
     * The used DBAL driver.
190
     *
191
     * @var \Doctrine\DBAL\Driver
192
     */
193
    protected $_driver;
194
195
    /**
196
     * Flag that indicates whether the current transaction is marked for rollback only.
197
     *
198
     * @var bool
199
     */
200
    private $_isRollbackOnly = false;
201
202
    /**
203
     * @var int
204
     */
205
    protected $defaultFetchMode = FetchMode::ASSOCIATIVE;
206
207
    /**
208
     * Initializes a new instance of the Connection class.
209
     *
210
     * @param array                              $params       The connection parameters.
211
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
212
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
213
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
214
     *
215
     * @throws \Doctrine\DBAL\DBALException
216
     */
217 2419
    public function __construct(array $params, Driver $driver, Configuration $config = null,
218
            EventManager $eventManager = null)
219
    {
220 2419
        $this->_driver = $driver;
221 2419
        $this->_params = $params;
222
223 2419
        if (isset($params['pdo'])) {
224 216
            $this->_conn = $params['pdo'];
225 216
            $this->_isConnected = true;
226 216
            unset($this->_params['pdo']);
227
        }
228
229 2419
        if (isset($params["platform"])) {
230 504
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
231 18
                throw DBALException::invalidPlatformType($params['platform']);
232
            }
233
234 486
            $this->platform = $params["platform"];
235 486
            unset($this->_params["platform"]);
236
        }
237
238
        // Create default config and event manager if none given
239 2419
        if ( ! $config) {
240 558
            $config = new Configuration();
241
        }
242
243 2419
        if ( ! $eventManager) {
244 558
            $eventManager = new EventManager();
245
        }
246
247 2419
        $this->_config = $config;
248 2419
        $this->_eventManager = $eventManager;
249
250 2419
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
251
252 2419
        $this->autoCommit = $config->getAutoCommit();
253 2419
    }
254
255
    /**
256
     * Gets the parameters used during instantiation.
257
     *
258
     * @return array
259
     */
260 2158
    public function getParams()
261
    {
262 2158
        return $this->_params;
263
    }
264
265
    /**
266
     * Gets the name of the database this Connection is connected to.
267
     *
268
     * @return string
269
     */
270 1112
    public function getDatabase()
271
    {
272 1112
        return $this->_driver->getDatabase($this);
273
    }
274
275
    /**
276
     * Gets the hostname of the currently connected database.
277
     *
278
     * @return string|null
279
     */
280 18
    public function getHost()
281
    {
282 18
        return $this->_params['host'] ?? null;
283
    }
284
285
    /**
286
     * Gets the port of the currently connected database.
287
     *
288
     * @return mixed
289
     */
290 18
    public function getPort()
291
    {
292 18
        return $this->_params['port'] ?? null;
293
    }
294
295
    /**
296
     * Gets the username used by this connection.
297
     *
298
     * @return string|null
299
     */
300 71
    public function getUsername()
301
    {
302 71
        return $this->_params['user'] ?? null;
303
    }
304
305
    /**
306
     * Gets the password used by this connection.
307
     *
308
     * @return string|null
309
     */
310 18
    public function getPassword()
311
    {
312 18
        return $this->_params['password'] ?? null;
313
    }
314
315
    /**
316
     * Gets the DBAL driver instance.
317
     *
318
     * @return \Doctrine\DBAL\Driver
319
     */
320 969
    public function getDriver()
321
    {
322 969
        return $this->_driver;
323
    }
324
325
    /**
326
     * Gets the Configuration used by the Connection.
327
     *
328
     * @return \Doctrine\DBAL\Configuration
329
     */
330 5261
    public function getConfiguration()
331
    {
332 5261
        return $this->_config;
333
    }
334
335
    /**
336
     * Gets the EventManager used by the Connection.
337
     *
338
     * @return \Doctrine\Common\EventManager
339
     */
340 247
    public function getEventManager()
341
    {
342 247
        return $this->_eventManager;
343
    }
344
345
    /**
346
     * Gets the DatabasePlatform for the connection.
347
     *
348
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
349
     *
350
     * @throws \Doctrine\DBAL\DBALException
351
     */
352 4412
    public function getDatabasePlatform()
353
    {
354 4412
        if (null === $this->platform) {
355 666
            $this->detectDatabasePlatform();
356
        }
357
358 4394
        return $this->platform;
359
    }
360
361
    /**
362
     * Gets the ExpressionBuilder for the connection.
363
     *
364
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
365
     */
366
    public function getExpressionBuilder()
367
    {
368
        return $this->_expr;
369
    }
370
371
    /**
372
     * Establishes the connection with the database.
373
     *
374
     * @return bool TRUE if the connection was successfully established, FALSE if
375
     *              the connection is already open.
376
     */
377 5368
    public function connect()
378
    {
379 5368
        if ($this->_isConnected) {
380 5015
            return false;
381
        }
382
383 837
        $driverOptions = $this->_params['driverOptions'] ?? [];
384 837
        $user = $this->_params['user'] ?? null;
385 837
        $password = $this->_params['password'] ?? null;
386
387 837
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
388 782
        $this->_isConnected = true;
389
390 782
        if (false === $this->autoCommit) {
391 54
            $this->beginTransaction();
392
        }
393
394 782
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
395 18
            $eventArgs = new Event\ConnectionEventArgs($this);
396 18
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
397
        }
398
399 782
        return true;
400
    }
401
402
    /**
403
     * Detects and sets the database platform.
404
     *
405
     * Evaluates custom platform class and version in order to set the correct platform.
406
     *
407
     * @throws DBALException if an invalid platform was specified for this connection.
408
     */
409 666
    private function detectDatabasePlatform()
410
    {
411 666
        $version = $this->getDatabasePlatformVersion();
412
413 612
        if ($version !== null) {
414 446
            assert($this->_driver instanceof VersionAwarePlatformDriver);
415
416 446
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
417
        } else {
418 166
            $this->platform = $this->_driver->getDatabasePlatform();
419
        }
420
421 612
        $this->platform->setEventManager($this->_eventManager);
422 612
    }
423
424
    /**
425
     * Returns the version of the related platform if applicable.
426
     *
427
     * Returns null if either the driver is not capable to create version
428
     * specific platform instances, no explicit server version was specified
429
     * or the underlying driver connection cannot determine the platform
430
     * version without having to query it (performance reasons).
431
     *
432
     * @return string|null
433
     *
434
     * @throws Exception
435
     */
436 666
    private function getDatabasePlatformVersion()
437
    {
438
        // Driver does not support version specific platforms.
439 666
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
440 148
            return null;
441
        }
442
443
        // Explicit platform version requested (supersedes auto-detection).
444 518
        if (isset($this->_params['serverVersion'])) {
445
            return $this->_params['serverVersion'];
446
        }
447
448
        // If not connected, we need to connect now to determine the platform version.
449 518
        if (null === $this->_conn) {
450
            try {
451 486
                $this->connect();
452 70
            } catch (\Exception $originalException) {
453 70
                if (empty($this->_params['dbname'])) {
454
                    throw $originalException;
455
                }
456
457
                // The database to connect to might not yet exist.
458
                // Retry detection without database name connection parameter.
459 70
                $databaseName = $this->_params['dbname'];
460 70
                $this->_params['dbname'] = null;
461
462
                try {
463 70
                    $this->connect();
464 54
                } catch (\Exception $fallbackException) {
465
                    // Either the platform does not support database-less connections
466
                    // or something else went wrong.
467
                    // Reset connection parameters and rethrow the original exception.
468 54
                    $this->_params['dbname'] = $databaseName;
469
470 54
                    throw $originalException;
471
                }
472
473
                // Reset connection parameters.
474 16
                $this->_params['dbname'] = $databaseName;
475 16
                $serverVersion = $this->getServerVersion();
476
477
                // Close "temporary" connection to allow connecting to the real database again.
478 16
                $this->close();
479
480 16
                return $serverVersion;
481
            }
482
483
        }
484
485 464
        return $this->getServerVersion();
486
    }
487
488
    /**
489
     * Returns the database server version if the underlying driver supports it.
490
     *
491
     * @return string|null
492
     */
493 464
    private function getServerVersion()
494
    {
495
        // Automatic platform version detection.
496 464
        if ($this->_conn instanceof ServerInfoAwareConnection &&
497 464
            ! $this->_conn->requiresQueryForServerVersion()
498
        ) {
499 446
            return $this->_conn->getServerVersion();
500
        }
501
502
        // Unable to detect platform version.
503 18
        return null;
504
    }
505
506
    /**
507
     * Returns the current auto-commit mode for this connection.
508
     *
509
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
510
     *
511
     * @see    setAutoCommit
512
     */
513 36
    public function isAutoCommit()
514
    {
515 36
        return true === $this->autoCommit;
516
    }
517
518
    /**
519
     * Sets auto-commit mode for this connection.
520
     *
521
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
522
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
523
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
524
     *
525
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
526
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
527
     *
528
     * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
529
     *
530
     * @see   isAutoCommit
531
     */
532 90
    public function setAutoCommit($autoCommit)
533
    {
534 90
        $autoCommit = (boolean) $autoCommit;
535
536
        // Mode not changed, no-op.
537 90
        if ($autoCommit === $this->autoCommit) {
538 18
            return;
539
        }
540
541 90
        $this->autoCommit = $autoCommit;
542
543
        // Commit all currently active transactions if any when switching auto-commit mode.
544 90
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
545 18
            $this->commitAll();
546
        }
547 90
    }
548
549
    /**
550
     * Sets the fetch mode.
551
     *
552
     * @param int $fetchMode
553
     *
554
     * @return void
555
     */
556 18
    public function setFetchMode($fetchMode)
557
    {
558 18
        $this->defaultFetchMode = $fetchMode;
559 18
    }
560
561
    /**
562
     * Prepares and executes an SQL query and returns the first row of the result
563
     * as an associative array.
564
     *
565
     * @param string $statement The SQL query.
566
     * @param array  $params    The query parameters.
567
     * @param array  $types     The query parameter types.
568
     *
569
     * @return array|bool False is returned if no rows are found.
570
     *
571
     * @throws \Doctrine\DBAL\DBALException
572
     */
573 844
    public function fetchAssoc($statement, array $params = [], array $types = [])
574
    {
575 844
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::ASSOCIATIVE);
576
    }
577
578
    /**
579
     * Prepares and executes an SQL query and returns the first row of the result
580
     * as a numerically indexed array.
581
     *
582
     * @param string $statement The SQL query to be executed.
583
     * @param array  $params    The prepared statement params.
584
     * @param array  $types     The query parameter types.
585
     *
586
     * @return array|bool False is returned if no rows are found.
587
     */
588 68
    public function fetchArray($statement, array $params = [], array $types = [])
589
    {
590 68
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::NUMERIC);
591
    }
592
593
    /**
594
     * Prepares and executes an SQL query and returns the value of a single column
595
     * of the first row of the result.
596
     *
597
     * @param string $statement The SQL query to be executed.
598
     * @param array  $params    The prepared statement params.
599
     * @param int    $column    The 0-indexed column number to retrieve.
600
     * @param array  $types     The query parameter types.
601
     *
602
     * @return mixed|bool False is returned if no rows are found.
603
     *
604
     * @throws \Doctrine\DBAL\DBALException
605
     */
606 628
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
607
    {
608 628
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
609
    }
610
611
    /**
612
     * Whether an actual connection to the database is established.
613
     *
614
     * @return bool
615
     */
616 71
    public function isConnected()
617
    {
618 71
        return $this->_isConnected;
619
    }
620
621
    /**
622
     * Checks whether a transaction is currently active.
623
     *
624
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
625
     */
626 5243
    public function isTransactionActive()
627
    {
628 5243
        return $this->_transactionNestingLevel > 0;
629
    }
630
631
    /**
632
     * Gathers conditions for an update or delete call.
633
     *
634
     * @param array $identifiers Input array of columns to values
635
     *
636
     * @return string[][] a triplet with:
637
     *                    - the first key being the columns
638
     *                    - the second key being the values
639
     *                    - the third key being the conditions
640
     */
641 261
    private function gatherConditions(array $identifiers)
642
    {
643 261
        $columns = [];
644 261
        $values = [];
645 261
        $conditions = [];
646
647 261
        foreach ($identifiers as $columnName => $value) {
648 261
            if (null === $value) {
649 72
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
650 72
                continue;
651
            }
652
653 225
            $columns[] = $columnName;
654 225
            $values[] = $value;
655 225
            $conditions[] = $columnName . ' = ?';
656
        }
657
658 261
        return [$columns, $values, $conditions];
659
    }
660
661
    /**
662
     * Executes an SQL DELETE statement on a table.
663
     *
664
     * Table expression and columns are not escaped and are not safe for user-input.
665
     *
666
     * @param string $tableExpression The expression of the table on which to delete.
667
     * @param array  $identifier      The deletion criteria. An associative array containing column-value pairs.
668
     * @param array  $types           The types of identifiers.
669
     *
670
     * @return int The number of affected rows.
671
     *
672
     * @throws \Doctrine\DBAL\DBALException
673
     * @throws InvalidArgumentException
674
     */
675 122
    public function delete($tableExpression, array $identifier, array $types = [])
676
    {
677 122
        if (empty($identifier)) {
678 18
            throw InvalidArgumentException::fromEmptyCriteria();
679
        }
680
681 104
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
682
683 104
        return $this->executeUpdate(
684 104
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
685 104
            $values,
686 104
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
687
        );
688
    }
689
690
    /**
691
     * Closes the connection.
692
     *
693
     * @return void
694
     */
695 446
    public function close()
696
    {
697 446
        $this->_conn = null;
698
699 446
        $this->_isConnected = false;
700 446
    }
701
702
    /**
703
     * Sets the transaction isolation level.
704
     *
705
     * @param int $level The level to set.
706
     *
707
     * @return int
708
     */
709
    public function setTransactionIsolation($level)
710
    {
711
        $this->_transactionIsolationLevel = $level;
712
713
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
714
    }
715
716
    /**
717
     * Gets the currently active transaction isolation level.
718
     *
719
     * @return int The current transaction isolation level.
720
     */
721
    public function getTransactionIsolation()
722
    {
723
        if (null === $this->_transactionIsolationLevel) {
724
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
725
        }
726
727
        return $this->_transactionIsolationLevel;
728
    }
729
730
    /**
731
     * Executes an SQL UPDATE statement on a table.
732
     *
733
     * Table expression and columns are not escaped and are not safe for user-input.
734
     *
735
     * @param string $tableExpression The expression of the table to update quoted or unquoted.
736
     * @param array  $data            An associative array containing column-value pairs.
737
     * @param array  $identifier      The update criteria. An associative array containing column-value pairs.
738
     * @param array  $types           Types of the merged $data and $identifier arrays in that order.
739
     *
740
     * @return int The number of affected rows.
741
     *
742
     * @throws \Doctrine\DBAL\DBALException
743
     */
744 157
    public function update($tableExpression, array $data, array $identifier, array $types = [])
745
    {
746 157
        $setColumns = [];
747 157
        $setValues = [];
748 157
        $set = [];
749
750 157
        foreach ($data as $columnName => $value) {
751 157
            $setColumns[] = $columnName;
752 157
            $setValues[] = $value;
753 157
            $set[] = $columnName . ' = ?';
754
        }
755
756 157
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
757 157
        $columns = array_merge($setColumns, $conditionColumns);
758 157
        $values = array_merge($setValues, $conditionValues);
759
760 157
        if (is_string(key($types))) {
761 90
            $types = $this->extractTypeValues($columns, $types);
762
        }
763
764 157
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
765 157
                . ' WHERE ' . implode(' AND ', $conditions);
766
767 157
        return $this->executeUpdate($sql, $values, $types);
768
    }
769
770
    /**
771
     * Inserts a table row with specified data.
772
     *
773
     * Table expression and columns are not escaped and are not safe for user-input.
774
     *
775
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
776
     * @param array  $data            An associative array containing column-value pairs.
777
     * @param array  $types           Types of the inserted data.
778
     *
779
     * @return int The number of affected rows.
780
     *
781
     * @throws \Doctrine\DBAL\DBALException
782
     */
783 1540
    public function insert($tableExpression, array $data, array $types = [])
784
    {
785 1540
        if (empty($data)) {
786 18
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
787
        }
788
789 1522
        $columns = [];
790 1522
        $values = [];
791 1522
        $set = [];
792
793 1522
        foreach ($data as $columnName => $value) {
794 1522
            $columns[] = $columnName;
795 1522
            $values[] = $value;
796 1522
            $set[] = '?';
797
        }
798
799 1522
        return $this->executeUpdate(
800 1522
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
801 1522
            ' VALUES (' . implode(', ', $set) . ')',
802 1522
            $values,
803 1522
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
804
        );
805
    }
806
807
    /**
808
     * Extract ordered type list from an ordered column list and type map.
809
     *
810
     * @param array $columnList
811
     * @param array $types
812
     *
813
     * @return array
814
     */
815 162
    private function extractTypeValues(array $columnList, array $types)
816
    {
817 162
        $typeValues = [];
818
819 162
        foreach ($columnList as $columnIndex => $columnName) {
820 162
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
821
        }
822
823 162
        return $typeValues;
824
    }
825
826
    /**
827
     * Quotes a string so it can be safely used as a table or column name, even if
828
     * it is a reserved name.
829
     *
830
     * Delimiting style depends on the underlying database platform that is being used.
831
     *
832
     * NOTE: Just because you CAN use quoted identifiers does not mean
833
     * you SHOULD use them. In general, they end up causing way more
834
     * problems than they solve.
835
     *
836
     * @param string $str The name to be quoted.
837
     *
838
     * @return string The quoted name.
839
     */
840 18
    public function quoteIdentifier($str)
841
    {
842 18
        return $this->getDatabasePlatform()->quoteIdentifier($str);
843
    }
844
845
    /**
846
     * Quotes a given input parameter.
847
     *
848
     * @param mixed    $input The parameter to be quoted.
849
     * @param int|null $type  The type of the parameter.
850
     *
851
     * @return string The quoted parameter.
852
     */
853 80
    public function quote($input, $type = null)
854
    {
855 80
        $this->connect();
856
857 80
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
858
859 80
        return $this->_conn->quote($value, $bindingType);
0 ignored issues
show
Bug introduced by
The method quote() 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

859
        return $this->_conn->/** @scrutinizer ignore-call */ quote($value, $bindingType);

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...
860
    }
861
862
    /**
863
     * Prepares and executes an SQL query and returns the result as an associative array.
864
     *
865
     * @param string $sql    The SQL query.
866
     * @param array  $params The query parameters.
867
     * @param array  $types  The query parameter types.
868
     *
869
     * @return array
870
     */
871 1837
    public function fetchAll($sql, array $params = [], $types = [])
872
    {
873 1837
        return $this->executeQuery($sql, $params, $types)->fetchAll();
874
    }
875
876
    /**
877
     * Prepares an SQL statement.
878
     *
879
     * @param string $statement The SQL statement to prepare.
880
     *
881
     * @return DriverStatement The prepared statement.
882
     *
883
     * @throws \Doctrine\DBAL\DBALException
884
     */
885 769
    public function prepare($statement)
886
    {
887
        try {
888 769
            $stmt = new Statement($statement, $this);
889 18
        } catch (Exception $ex) {
890 18
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
891
        }
892
893 751
        $stmt->setFetchMode($this->defaultFetchMode);
894
895 751
        return $stmt;
896
    }
897
898
    /**
899
     * Executes an, optionally parametrized, SQL query.
900
     *
901
     * If the query is parametrized, a prepared statement is used.
902
     * If an SQLLogger is configured, the execution is logged.
903
     *
904
     * @param string                                      $query  The SQL query to execute.
905
     * @param array                                       $params The parameters to bind to the query, if any.
906
     * @param array                                       $types  The types the previous parameters are in.
907
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
908
     *
909
     * @return \Doctrine\DBAL\Driver\ResultStatement The executed statement.
910
     *
911
     * @throws \Doctrine\DBAL\DBALException
912
     */
913 3676
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
914
    {
915 3676
        if ($qcp !== null) {
916 180
            return $this->executeCacheQuery($query, $params, $types, $qcp);
917
        }
918
919 3676
        $this->connect();
920
921 3676
        $logger = $this->_config->getSQLLogger();
922 3676
        if ($logger) {
923 3562
            $logger->startQuery($query, $params, $types);
924
        }
925
926
        try {
927 3676
            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...
928 647
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
929
930 647
                $stmt = $this->_conn->prepare($query);
931 647
                if ($types) {
932 288
                    $this->_bindTypedValues($stmt, $params, $types);
933 288
                    $stmt->execute();
934
                } else {
935 647
                    $stmt->execute($params);
936
                }
937
            } else {
938 3620
                $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

938
                /** @scrutinizer ignore-call */ 
939
                $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...
939
            }
940 119
        } catch (Exception $ex) {
941 119
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
942
        }
943
944 3557
        $stmt->setFetchMode($this->defaultFetchMode);
945
946 3557
        if ($logger) {
947 3461
            $logger->stopQuery();
948
        }
949
950 3557
        return $stmt;
951
    }
952
953
    /**
954
     * Executes a caching query.
955
     *
956
     * @param string                                 $query  The SQL query to execute.
957
     * @param array                                  $params The parameters to bind to the query, if any.
958
     * @param array                                  $types  The types the previous parameters are in.
959
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
960
     *
961
     * @return \Doctrine\DBAL\Driver\ResultStatement
962
     *
963
     * @throws \Doctrine\DBAL\Cache\CacheException
964
     */
965 216
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
966
    {
967 216
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
968 216
        if ( ! $resultCache) {
969
            throw CacheException::noResultDriverConfigured();
970
        }
971
972 216
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
973
974
        // fetch the row pointers entry
975 216
        if ($data = $resultCache->fetch($cacheKey)) {
976
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
977 162
            if (isset($data[$realKey])) {
978 162
                $stmt = new ArrayStatement($data[$realKey]);
979
            } elseif (array_key_exists($realKey, $data)) {
980
                $stmt = new ArrayStatement([]);
981
            }
982
        }
983
984 216
        if (!isset($stmt)) {
985 180
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
986
        }
987
988 216
        $stmt->setFetchMode($this->defaultFetchMode);
989
990 216
        return $stmt;
991
    }
992
993
    /**
994
     * Executes an, optionally parametrized, SQL query and returns the result,
995
     * applying a given projection/transformation function on each row of the result.
996
     *
997
     * @param string   $query    The SQL query to execute.
998
     * @param array    $params   The parameters, if any.
999
     * @param \Closure $function The transformation function that is applied on each row.
1000
     *                           The function receives a single parameter, an array, that
1001
     *                           represents a row of the result set.
1002
     *
1003
     * @return array The projected result of the query.
1004
     */
1005
    public function project($query, array $params, Closure $function)
1006
    {
1007
        $result = [];
1008
        $stmt = $this->executeQuery($query, $params);
1009
1010
        while ($row = $stmt->fetch()) {
1011
            $result[] = $function($row);
1012
        }
1013
1014
        $stmt->closeCursor();
1015
1016
        return $result;
1017
    }
1018
1019
    /**
1020
     * Executes an SQL statement, returning a result set as a Statement object.
1021
     *
1022
     * @return \Doctrine\DBAL\Driver\Statement
1023
     *
1024
     * @throws \Doctrine\DBAL\DBALException
1025
     */
1026 268
    public function query()
1027
    {
1028 268
        $this->connect();
1029
1030 268
        $args = func_get_args();
1031
1032 268
        $logger = $this->_config->getSQLLogger();
1033 268
        if ($logger) {
1034 250
            $logger->startQuery($args[0]);
1035
        }
1036
1037
        try {
1038 268
            $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

1038
            /** @scrutinizer ignore-call */ 
1039
            $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...
1039 18
        } catch (Exception $ex) {
1040 18
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1041
        }
1042
1043 250
        $statement->setFetchMode($this->defaultFetchMode);
1044
1045 250
        if ($logger) {
1046 250
            $logger->stopQuery();
1047
        }
1048
1049 250
        return $statement;
1050
    }
1051
1052
    /**
1053
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1054
     * and returns the number of affected rows.
1055
     *
1056
     * This method supports PDO binding types as well as DBAL mapping types.
1057
     *
1058
     * @param string $query  The SQL query.
1059
     * @param array  $params The query parameters.
1060
     * @param array  $types  The parameter types.
1061
     *
1062
     * @return int The number of affected rows.
1063
     *
1064
     * @throws \Doctrine\DBAL\DBALException
1065
     */
1066 3276
    public function executeUpdate($query, array $params = [], array $types = [])
1067
    {
1068 3276
        $this->connect();
1069
1070 3276
        $logger = $this->_config->getSQLLogger();
1071 3276
        if ($logger) {
1072 3186
            $logger->startQuery($query, $params, $types);
1073
        }
1074
1075
        try {
1076 3276
            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...
1077 1653
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1078
1079 1653
                $stmt = $this->_conn->prepare($query);
1080 1649
                if ($types) {
1081 237
                    $this->_bindTypedValues($stmt, $params, $types);
1082 237
                    $stmt->execute();
1083
                } else {
1084 1412
                    $stmt->execute($params);
1085
                }
1086 1623
                $result = $stmt->rowCount();
1087
            } else {
1088 3246
                $result = $this->_conn->exec($query);
1089
            }
1090 2090
        } catch (Exception $ex) {
1091 2090
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1092
        }
1093
1094 2997
        if ($logger) {
1095 2925
            $logger->stopQuery();
1096
        }
1097
1098 2997
        return $result;
1099
    }
1100
1101
    /**
1102
     * Executes an SQL statement and return the number of affected rows.
1103
     *
1104
     * @param string $statement
1105
     *
1106
     * @return int The number of affected rows.
1107
     *
1108
     * @throws \Doctrine\DBAL\DBALException
1109
     */
1110 1714
    public function exec($statement)
1111
    {
1112 1714
        $this->connect();
1113
1114 1713
        $logger = $this->_config->getSQLLogger();
1115 1713
        if ($logger) {
1116 1658
            $logger->startQuery($statement);
1117
        }
1118
1119
        try {
1120 1713
            $result = $this->_conn->exec($statement);
1121 1324
        } catch (Exception $ex) {
1122 1324
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1123
        }
1124
1125 450
        if ($logger) {
1126 414
            $logger->stopQuery();
1127
        }
1128
1129 450
        return $result;
1130
    }
1131
1132
    /**
1133
     * Returns the current transaction nesting level.
1134
     *
1135
     * @return int The nesting level. A value of 0 means there's no active transaction.
1136
     */
1137 325
    public function getTransactionNestingLevel()
1138
    {
1139 325
        return $this->_transactionNestingLevel;
1140
    }
1141
1142
    /**
1143
     * Fetches the SQLSTATE associated with the last database operation.
1144
     *
1145
     * @return string|bool|null The last error code.
1146
     */
1147
    public function errorCode()
1148
    {
1149
        $this->connect();
1150
1151
        return $this->_conn->errorCode();
1152
    }
1153
1154
    /**
1155
     * Fetches extended error information associated with the last database operation.
1156
     *
1157
     * @return array The last error information.
1158
     */
1159
    public function errorInfo()
1160
    {
1161
        $this->connect();
1162
1163
        return $this->_conn->errorInfo();
1164
    }
1165
1166
    /**
1167
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1168
     * depending on the underlying driver.
1169
     *
1170
     * Note: This method may not return a meaningful or consistent result across different drivers,
1171
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1172
     * columns or sequences.
1173
     *
1174
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
1175
     *
1176
     * @return string|int|bool A string representation of the last inserted ID.
1177
     */
1178 92
    public function lastInsertId($seqName = null)
1179
    {
1180 92
        $this->connect();
1181
1182 92
        return $this->_conn->lastInsertId($seqName);
1183
    }
1184
1185
    /**
1186
     * Executes a function in a transaction.
1187
     *
1188
     * The function gets passed this Connection instance as an (optional) parameter.
1189
     *
1190
     * If an exception occurs during execution of the function or transaction commit,
1191
     * the transaction is rolled back and the exception re-thrown.
1192
     *
1193
     * @param \Closure $func The function to execute transactionally.
1194
     *
1195
     * @return mixed The value returned by $func
1196
     *
1197
     * @throws Exception
1198
     * @throws Throwable
1199
     */
1200 72
    public function transactional(Closure $func)
1201
    {
1202 72
        $this->beginTransaction();
1203
        try {
1204 72
            $res = $func($this);
1205 36
            $this->commit();
1206 36
            return $res;
1207 36
        } catch (Exception $e) {
1208 18
            $this->rollBack();
1209 18
            throw $e;
1210 18
        } catch (Throwable $e) {
1211 18
            $this->rollBack();
1212 18
            throw $e;
1213
        }
1214
    }
1215
1216
    /**
1217
     * Sets if nested transactions should use savepoints.
1218
     *
1219
     * @param bool $nestTransactionsWithSavepoints
1220
     *
1221
     * @return void
1222
     *
1223
     * @throws \Doctrine\DBAL\ConnectionException
1224
     */
1225 35
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1226
    {
1227 35
        if ($this->_transactionNestingLevel > 0) {
1228 34
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1229
        }
1230
1231 18
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1232 1
            throw ConnectionException::savepointsNotSupported();
1233
        }
1234
1235 17
        $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1236 17
    }
1237
1238
    /**
1239
     * Gets if nested transactions should use savepoints.
1240
     *
1241
     * @return bool
1242
     */
1243 17
    public function getNestTransactionsWithSavepoints()
1244
    {
1245 17
        return $this->_nestTransactionsWithSavepoints;
1246
    }
1247
1248
    /**
1249
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1250
     * "savepointFormat" parameter is not set
1251
     *
1252
     * @return mixed A string with the savepoint name or false.
1253
     */
1254 17
    protected function _getNestedTransactionSavePointName()
1255
    {
1256 17
        return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
1257
    }
1258
1259
    /**
1260
     * Starts a transaction by suspending auto-commit mode.
1261
     *
1262
     * @return void
1263
     */
1264 364
    public function beginTransaction()
1265
    {
1266 364
        $this->connect();
1267
1268 364
        ++$this->_transactionNestingLevel;
1269
1270 364
        $logger = $this->_config->getSQLLogger();
1271
1272 364
        if ($this->_transactionNestingLevel == 1) {
1273 364
            if ($logger) {
1274 266
                $logger->startQuery('"START TRANSACTION"');
1275
            }
1276 364
            $this->_conn->beginTransaction();
1277 364
            if ($logger) {
1278 364
                $logger->stopQuery();
1279
            }
1280 53
        } elseif ($this->_nestTransactionsWithSavepoints) {
1281 17
            if ($logger) {
1282 17
                $logger->startQuery('"SAVEPOINT"');
1283
            }
1284 17
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1285 17
            if ($logger) {
1286 17
                $logger->stopQuery();
1287
            }
1288
        }
1289 364
    }
1290
1291
    /**
1292
     * Commits the current transaction.
1293
     *
1294
     * @return void
1295
     *
1296
     * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
1297
     *                                            because the transaction was marked for rollback only.
1298
     */
1299 203
    public function commit()
1300
    {
1301 203
        if ($this->_transactionNestingLevel == 0) {
1302 18
            throw ConnectionException::noActiveTransaction();
1303
        }
1304 185
        if ($this->_isRollbackOnly) {
1305 36
            throw ConnectionException::commitFailedRollbackOnly();
1306
        }
1307
1308 149
        $this->connect();
1309
1310 149
        $logger = $this->_config->getSQLLogger();
1311
1312 149
        if ($this->_transactionNestingLevel == 1) {
1313 149
            if ($logger) {
1314 105
                $logger->startQuery('"COMMIT"');
1315
            }
1316 149
            $this->_conn->commit();
1317 149
            if ($logger) {
1318 149
                $logger->stopQuery();
1319
            }
1320 35
        } elseif ($this->_nestTransactionsWithSavepoints) {
1321 17
            if ($logger) {
1322 17
                $logger->startQuery('"RELEASE SAVEPOINT"');
1323
            }
1324 17
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1325 17
            if ($logger) {
1326 17
                $logger->stopQuery();
1327
            }
1328
        }
1329
1330 149
        --$this->_transactionNestingLevel;
1331
1332 149
        if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) {
1333 36
            $this->beginTransaction();
1334
        }
1335 149
    }
1336
1337
    /**
1338
     * Commits all current nesting transactions.
1339
     */
1340 18
    private function commitAll()
1341
    {
1342 18
        while (0 !== $this->_transactionNestingLevel) {
1343 18
            if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) {
1344
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1345
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1346 18
                $this->commit();
1347
1348 18
                return;
1349
            }
1350
1351 18
            $this->commit();
1352
        }
1353 18
    }
1354
1355
    /**
1356
     * Cancels any database changes done during the current transaction.
1357
     *
1358
     * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
1359
     */
1360 231
    public function rollBack()
1361
    {
1362 231
        if ($this->_transactionNestingLevel == 0) {
1363 18
            throw ConnectionException::noActiveTransaction();
1364
        }
1365
1366 213
        $this->connect();
1367
1368 213
        $logger = $this->_config->getSQLLogger();
1369
1370 213
        if ($this->_transactionNestingLevel == 1) {
1371 196
            if ($logger) {
1372 178
                $logger->startQuery('"ROLLBACK"');
1373
            }
1374 196
            $this->_transactionNestingLevel = 0;
1375 196
            $this->_conn->rollBack();
1376 196
            $this->_isRollbackOnly = false;
1377 196
            if ($logger) {
1378 178
                $logger->stopQuery();
1379
            }
1380
1381 196
            if (false === $this->autoCommit) {
1382 196
                $this->beginTransaction();
1383
            }
1384 35
        } elseif ($this->_nestTransactionsWithSavepoints) {
1385 17
            if ($logger) {
1386 17
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1387
            }
1388 17
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1389 17
            --$this->_transactionNestingLevel;
1390 17
            if ($logger) {
1391 17
                $logger->stopQuery();
1392
            }
1393
        } else {
1394 18
            $this->_isRollbackOnly = true;
1395 18
            --$this->_transactionNestingLevel;
1396
        }
1397 213
    }
1398
1399
    /**
1400
     * Creates a new savepoint.
1401
     *
1402
     * @param string $savepoint The name of the savepoint to create.
1403
     *
1404
     * @return void
1405
     *
1406
     * @throws \Doctrine\DBAL\ConnectionException
1407
     */
1408 18
    public function createSavepoint($savepoint)
1409
    {
1410 18
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1411 1
            throw ConnectionException::savepointsNotSupported();
1412
        }
1413
1414 17
        $this->_conn->exec($this->platform->createSavePoint($savepoint));
1415 17
    }
1416
1417
    /**
1418
     * Releases the given savepoint.
1419
     *
1420
     * @param string $savepoint The name of the savepoint to release.
1421
     *
1422
     * @return void
1423
     *
1424
     * @throws \Doctrine\DBAL\ConnectionException
1425
     */
1426 18
    public function releaseSavepoint($savepoint)
1427
    {
1428 18
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1429 1
            throw ConnectionException::savepointsNotSupported();
1430
        }
1431
1432 17
        if ($this->platform->supportsReleaseSavepoints()) {
1433 15
            $this->_conn->exec($this->platform->releaseSavePoint($savepoint));
1434
        }
1435 17
    }
1436
1437
    /**
1438
     * Rolls back to the given savepoint.
1439
     *
1440
     * @param string $savepoint The name of the savepoint to rollback to.
1441
     *
1442
     * @return void
1443
     *
1444
     * @throws \Doctrine\DBAL\ConnectionException
1445
     */
1446 18
    public function rollbackSavepoint($savepoint)
1447
    {
1448 18
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1449 1
            throw ConnectionException::savepointsNotSupported();
1450
        }
1451
1452 17
        $this->_conn->exec($this->platform->rollbackSavePoint($savepoint));
1453 17
    }
1454
1455
    /**
1456
     * Gets the wrapped driver connection.
1457
     *
1458
     * @return \Doctrine\DBAL\Driver\Connection
1459
     */
1460 889
    public function getWrappedConnection()
1461
    {
1462 889
        $this->connect();
1463
1464 889
        return $this->_conn;
1465
    }
1466
1467
    /**
1468
     * Gets the SchemaManager that can be used to inspect or change the
1469
     * database schema through the connection.
1470
     *
1471
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
1472
     */
1473 2997
    public function getSchemaManager()
1474
    {
1475 2997
        if ( ! $this->_schemaManager) {
1476 197
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1477
        }
1478
1479 2997
        return $this->_schemaManager;
1480
    }
1481
1482
    /**
1483
     * Marks the current transaction so that the only possible
1484
     * outcome for the transaction to be rolled back.
1485
     *
1486
     * @return void
1487
     *
1488
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1489
     */
1490 36
    public function setRollbackOnly()
1491
    {
1492 36
        if ($this->_transactionNestingLevel == 0) {
1493 18
            throw ConnectionException::noActiveTransaction();
1494
        }
1495 18
        $this->_isRollbackOnly = true;
1496 18
    }
1497
1498
    /**
1499
     * Checks whether the current transaction is marked for rollback only.
1500
     *
1501
     * @return bool
1502
     *
1503
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1504
     */
1505 53
    public function isRollbackOnly()
1506
    {
1507 53
        if ($this->_transactionNestingLevel == 0) {
1508 18
            throw ConnectionException::noActiveTransaction();
1509
        }
1510
1511 35
        return $this->_isRollbackOnly;
1512
    }
1513
1514
    /**
1515
     * Converts a given value to its database representation according to the conversion
1516
     * rules of a specific DBAL mapping type.
1517
     *
1518
     * @param mixed  $value The value to convert.
1519
     * @param string $type  The name of the DBAL mapping type.
1520
     *
1521
     * @return mixed The converted value.
1522
     */
1523
    public function convertToDatabaseValue($value, $type)
1524
    {
1525
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1526
    }
1527
1528
    /**
1529
     * Converts a given value to its PHP representation according to the conversion
1530
     * rules of a specific DBAL mapping type.
1531
     *
1532
     * @param mixed  $value The value to convert.
1533
     * @param string $type  The name of the DBAL mapping type.
1534
     *
1535
     * @return mixed The converted type.
1536
     */
1537
    public function convertToPHPValue($value, $type)
1538
    {
1539
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1540
    }
1541
1542
    /**
1543
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1544
     * or DBAL mapping type, to a given statement.
1545
     *
1546
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1547
     * @param array                           $params The map/list of named/positional parameters.
1548
     * @param array                           $types  The parameter types (PDO binding types or DBAL mapping types).
1549
     *
1550
     * @return void
1551
     *
1552
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1553
     *           raw PDOStatement instances.
1554
     */
1555 489
    private function _bindTypedValues($stmt, array $params, array $types)
1556
    {
1557
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1558 489
        if (is_int(key($params))) {
1559
            // Positional parameters
1560 489
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1561 489
            $bindIndex = 1;
1562 489
            foreach ($params as $value) {
1563 489
                $typeIndex = $bindIndex + $typeOffset;
1564 489
                if (isset($types[$typeIndex])) {
1565 489
                    $type = $types[$typeIndex];
1566 489
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1567 489
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1568
                } else {
1569 18
                    $stmt->bindValue($bindIndex, $value);
1570
                }
1571 489
                ++$bindIndex;
1572
            }
1573
        } else {
1574
            // Named parameters
1575
            foreach ($params as $name => $value) {
1576
                if (isset($types[$name])) {
1577
                    $type = $types[$name];
1578
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1579
                    $stmt->bindValue($name, $value, $bindingType);
1580
                } else {
1581
                    $stmt->bindValue($name, $value);
1582
                }
1583
            }
1584
        }
1585 489
    }
1586
1587
    /**
1588
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1589
     *
1590
     * @param mixed $value The value to bind.
1591
     * @param mixed $type  The type to bind (PDO or DBAL).
1592
     *
1593
     * @return array [0] => the (escaped) value, [1] => the binding type.
1594
     */
1595 569
    private function getBindingInfo($value, $type)
1596
    {
1597 569
        if (is_string($type)) {
1598 216
            $type = Type::getType($type);
1599
        }
1600 569
        if ($type instanceof Type) {
1601 216
            $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1602 216
            $bindingType = $type->getBindingType();
1603
        } else {
1604 461
            $bindingType = $type;
1605
        }
1606
1607 569
        return [$value, $bindingType];
1608
    }
1609
1610
    /**
1611
     * Resolves the parameters to a format which can be displayed.
1612
     *
1613
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1614
     *           copy/paste the code as this method may change, or be removed without prior notice.
1615
     *
1616
     * @param array $params
1617
     * @param array $types
1618
     *
1619
     * @return array
1620
     */
1621 2209
    public function resolveParams(array $params, array $types)
1622
    {
1623 2209
        $resolvedParams = [];
1624
1625
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1626 2209
        if (is_int(key($params))) {
1627
            // Positional parameters
1628 158
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1629 158
            $bindIndex = 1;
1630 158
            foreach ($params as $value) {
1631 158
                $typeIndex = $bindIndex + $typeOffset;
1632 158
                if (isset($types[$typeIndex])) {
1633
                    $type = $types[$typeIndex];
1634
                    list($value,) = $this->getBindingInfo($value, $type);
1635
                    $resolvedParams[$bindIndex] = $value;
1636
                } else {
1637 158
                    $resolvedParams[$bindIndex] = $value;
1638
                }
1639 158
                ++$bindIndex;
1640
            }
1641
        } else {
1642
            // Named parameters
1643 2051
            foreach ($params as $name => $value) {
1644
                if (isset($types[$name])) {
1645
                    $type = $types[$name];
1646
                    list($value,) = $this->getBindingInfo($value, $type);
1647
                    $resolvedParams[$name] = $value;
1648
                } else {
1649
                    $resolvedParams[$name] = $value;
1650
                }
1651
            }
1652
        }
1653
1654 2209
        return $resolvedParams;
1655
    }
1656
1657
    /**
1658
     * Creates a new instance of a SQL query builder.
1659
     *
1660
     * @return \Doctrine\DBAL\Query\QueryBuilder
1661
     */
1662
    public function createQueryBuilder()
1663
    {
1664
        return new Query\QueryBuilder($this);
1665
    }
1666
1667
    /**
1668
     * Ping the server
1669
     *
1670
     * When the server is not available the method returns FALSE.
1671
     * It is responsibility of the developer to handle this case
1672
     * and abort the request or reconnect manually:
1673
     *
1674
     * @example
1675
     *
1676
     *   if ($conn->ping() === false) {
1677
     *      $conn->close();
1678
     *      $conn->connect();
1679
     *   }
1680
     *
1681
     * It is undefined if the underlying driver attempts to reconnect
1682
     * or disconnect when the connection is not available anymore
1683
     * as long it returns TRUE when a reconnect succeeded and
1684
     * FALSE when the connection was dropped.
1685
     *
1686
     * @return bool
1687
     */
1688 18
    public function ping()
1689
    {
1690 18
        $this->connect();
1691
1692 18
        if ($this->_conn instanceof PingableConnection) {
1693 3
            return $this->_conn->ping();
1694
        }
1695
1696
        try {
1697 15
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1698
1699 15
            return true;
1700
        } catch (DBALException $e) {
1701
            return false;
1702
        }
1703
    }
1704
}
1705