Failed Conditions
Pull Request — master (#2936)
by Sergei
10:58
created

Connection   F

Complexity

Total Complexity 188

Size/Duplication

Total Lines 1642
Duplicated Lines 6.03 %

Test Coverage

Coverage 87.84%

Importance

Changes 0
Metric Value
wmc 188
dl 99
loc 1642
ccs 426
cts 485
cp 0.8784
rs 0.5217
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
C commit() 17 35 11
A getDatabase() 0 3 1
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 2
C __construct() 0 40 7
A isConnected() 0 3 1
A getConfiguration() 0 3 1
C beginTransaction() 15 23 7
A releaseSavepoint() 0 8 3
A getNestTransactionsWithSavepoints() 0 3 1
A getDatabasePlatform() 0 7 2
C resolveParams() 14 34 7
A insert() 0 21 4
B update() 0 24 3
A getUsername() 0 3 2
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 2
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 2
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 getExpressionBuilder() 0 3 1
A setFetchMode() 0 3 1
A extractTypeValues() 0 11 3
A detectDatabasePlatform() 0 11 2
A getTransactionNestingLevel() 0 3 1
A getParams() 0 3 1
C connect() 4 25 7

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 (!$config) {
229
            echo 'Hmm... there\'s still no config';
230
        }
231
232 131
        if ( ! $eventManager) {
233 32
            $eventManager = new EventManager();
234
        }
235
236 131
        $this->_config = $config;
237 131
        $this->_eventManager = $eventManager;
238
239 131
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
240
241 131
        $this->autoCommit = $config->getAutoCommit();
242 131
    }
243
244
    /**
245
     * Gets the parameters used during instantiation.
246
     *
247
     * @return array
248
     */
249 97
    public function getParams()
250
    {
251 97
        return $this->_params;
252
    }
253
254
    /**
255
     * Gets the name of the database this Connection is connected to.
256
     *
257
     * @return string
258
     */
259 48
    public function getDatabase()
260
    {
261 48
        return $this->_driver->getDatabase($this);
262
    }
263
264
    /**
265
     * Gets the hostname of the currently connected database.
266
     *
267
     * @return string|null
268
     */
269 1
    public function getHost()
270
    {
271 1
        return isset($this->_params['host']) ? $this->_params['host'] : null;
272
    }
273
274
    /**
275
     * Gets the port of the currently connected database.
276
     *
277
     * @return mixed
278
     */
279 1
    public function getPort()
280
    {
281 1
        return isset($this->_params['port']) ? $this->_params['port'] : null;
282
    }
283
284
    /**
285
     * Gets the username used by this connection.
286
     *
287
     * @return string|null
288
     */
289 1
    public function getUsername()
290
    {
291 1
        return isset($this->_params['user']) ? $this->_params['user'] : null;
292
    }
293
294
    /**
295
     * Gets the password used by this connection.
296
     *
297
     * @return string|null
298
     */
299 1
    public function getPassword()
300
    {
301 1
        return isset($this->_params['password']) ? $this->_params['password'] : null;
302
    }
303
304
    /**
305
     * Gets the DBAL driver instance.
306
     *
307
     * @return \Doctrine\DBAL\Driver
308
     */
309 52
    public function getDriver()
310
    {
311 52
        return $this->_driver;
312
    }
313
314
    /**
315
     * Gets the Configuration used by the Connection.
316
     *
317
     * @return \Doctrine\DBAL\Configuration
318
     */
319 262
    public function getConfiguration()
320
    {
321 262
        return $this->_config;
322
    }
323
324
    /**
325
     * Gets the EventManager used by the Connection.
326
     *
327
     * @return \Doctrine\Common\EventManager
328
     */
329 10
    public function getEventManager()
330
    {
331 10
        return $this->_eventManager;
332
    }
333
334
    /**
335
     * Gets the DatabasePlatform for the connection.
336
     *
337
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
338
     *
339
     * @throws \Doctrine\DBAL\DBALException
340
     */
341 207
    public function getDatabasePlatform()
342
    {
343 207
        if (null === $this->platform) {
344 27
            $this->detectDatabasePlatform();
345
        }
346
347 206
        return $this->platform;
348
    }
349
350
    /**
351
     * Gets the ExpressionBuilder for the connection.
352
     *
353
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
354
     */
355
    public function getExpressionBuilder()
356
    {
357
        return $this->_expr;
358
    }
359
360
    /**
361
     * Establishes the connection with the database.
362
     *
363
     * @return boolean TRUE if the connection was successfully established, FALSE if
364
     *                 the connection is already open.
365
     */
366 268
    public function connect()
367
    {
368 268
        if ($this->_isConnected) {
369 251
            return false;
370
        }
371
372 46
        $driverOptions = isset($this->_params['driverOptions']) ?
373 46
            $this->_params['driverOptions'] : [];
374 46
        $user = isset($this->_params['user']) ? $this->_params['user'] : null;
375 46
        $password = isset($this->_params['password']) ?
376 46
            $this->_params['password'] : null;
377
378 46
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
379 44
        $this->_isConnected = true;
380
381 44
        if (false === $this->autoCommit) {
382 3
            $this->beginTransaction();
383
        }
384
385 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...
386 1
            $eventArgs = new Event\ConnectionEventArgs($this);
387 1
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
388
        }
389
390 44
        return true;
391
    }
392
393
    /**
394
     * Detects and sets the database platform.
395
     *
396
     * Evaluates custom platform class and version in order to set the correct platform.
397
     *
398
     * @throws DBALException if an invalid platform was specified for this connection.
399
     */
400 27
    private function detectDatabasePlatform()
401
    {
402 27
        $version = $this->getDatabasePlatformVersion();
403
404 26
        if (null !== $version) {
405 1
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
406
        } else {
407 25
            $this->platform = $this->_driver->getDatabasePlatform();
408
        }
409
410 26
        $this->platform->setEventManager($this->_eventManager);
411 26
    }
412
413
    /**
414
     * Returns the version of the related platform if applicable.
415
     *
416
     * Returns null if either the driver is not capable to create version
417
     * specific platform instances, no explicit server version was specified
418
     * or the underlying driver connection cannot determine the platform
419
     * version without having to query it (performance reasons).
420
     *
421
     * @return string|null
422
     *
423
     * @throws Exception
424
     */
425 27
    private function getDatabasePlatformVersion()
426
    {
427
        // Driver does not support version specific platforms.
428 27
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
429 24
            return null;
430
        }
431
432
        // Explicit platform version requested (supersedes auto-detection).
433 3
        if (isset($this->_params['serverVersion'])) {
434
            return $this->_params['serverVersion'];
435
        }
436
437
        // If not connected, we need to connect now to determine the platform version.
438 3
        if (null === $this->_conn) {
439
            try {
440 3
                $this->connect();
441 1
            } catch (\Exception $originalException) {
442 1
                if (empty($this->_params['dbname'])) {
443
                    throw $originalException;
444
                }
445
446
                // The database to connect to might not yet exist.
447
                // Retry detection without database name connection parameter.
448 1
                $databaseName = $this->_params['dbname'];
449 1
                $this->_params['dbname'] = null;
450
451
                try {
452 1
                    $this->connect();
453 1
                } catch (\Exception $fallbackException) {
454
                    // Either the platform does not support database-less connections
455
                    // or something else went wrong.
456
                    // Reset connection parameters and rethrow the original exception.
457 1
                    $this->_params['dbname'] = $databaseName;
458
459 1
                    throw $originalException;
460
                }
461
462
                // Reset connection parameters.
463
                $this->_params['dbname'] = $databaseName;
464
                $serverVersion = $this->getServerVersion();
465
466
                // Close "temporary" connection to allow connecting to the real database again.
467
                $this->close();
468
469
                return $serverVersion;
470
            }
471
472
        }
473
474 2
        return $this->getServerVersion();
475
    }
476
477
    /**
478
     * Returns the database server version if the underlying driver supports it.
479
     *
480
     * @return string|null
481
     */
482 2
    private function getServerVersion()
483
    {
484
        // Automatic platform version detection.
485 2
        if ($this->_conn instanceof ServerInfoAwareConnection &&
486 2
            ! $this->_conn->requiresQueryForServerVersion()
487
        ) {
488 1
            return $this->_conn->getServerVersion();
489
        }
490
491
        // Unable to detect platform version.
492 1
        return null;
493
    }
494
495
    /**
496
     * Returns the current auto-commit mode for this connection.
497
     *
498
     * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise.
499
     *
500
     * @see    setAutoCommit
501
     */
502 2
    public function isAutoCommit()
503
    {
504 2
        return true === $this->autoCommit;
505
    }
506
507
    /**
508
     * Sets auto-commit mode for this connection.
509
     *
510
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
511
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
512
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
513
     *
514
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
515
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
516
     *
517
     * @param boolean $autoCommit True to enable auto-commit mode; false to disable it.
518
     *
519
     * @see   isAutoCommit
520
     */
521 5
    public function setAutoCommit($autoCommit)
522
    {
523 5
        $autoCommit = (boolean) $autoCommit;
524
525
        // Mode not changed, no-op.
526 5
        if ($autoCommit === $this->autoCommit) {
527 1
            return;
528
        }
529
530 5
        $this->autoCommit = $autoCommit;
531
532
        // Commit all currently active transactions if any when switching auto-commit mode.
533 5
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
534 1
            $this->commitAll();
535
        }
536 5
    }
537
538
    /**
539
     * Sets the fetch mode.
540
     *
541
     * @param integer $fetchMode
542
     *
543
     * @return void
544
     */
545 1
    public function setFetchMode($fetchMode)
546
    {
547 1
        $this->defaultFetchMode = $fetchMode;
548 1
    }
549
550
    /**
551
     * Prepares and executes an SQL query and returns the first row of the result
552
     * as an associative array.
553
     *
554
     * @param string $statement The SQL query.
555
     * @param array  $params    The query parameters.
556
     * @param array  $types     The query parameter types.
557
     *
558
     * @return array|bool False is returned if no rows are found.
559
     *
560
     * @throws \Doctrine\DBAL\DBALException
561
     */
562 42
    public function fetchAssoc($statement, array $params = [], array $types = [])
563
    {
564 42
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC);
565
    }
566
567
    /**
568
     * Prepares and executes an SQL query and returns the first row of the result
569
     * as a numerically indexed array.
570
     *
571
     * @param string $statement The SQL query to be executed.
572
     * @param array  $params    The prepared statement params.
573
     * @param array  $types     The query parameter types.
574
     *
575
     * @return array|bool False is returned if no rows are found.
576
     */
577 4
    public function fetchArray($statement, array $params = [], array $types = [])
578
    {
579 4
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM);
580
    }
581
582
    /**
583
     * Prepares and executes an SQL query and returns the value of a single column
584
     * of the first row of the result.
585
     *
586
     * @param string  $statement The SQL query to be executed.
587
     * @param array   $params    The prepared statement params.
588
     * @param integer $column    The 0-indexed column number to retrieve.
589
     * @param array  $types      The query parameter types.
590
     *
591
     * @return mixed|bool False is returned if no rows are found.
592
     *
593
     * @throws \Doctrine\DBAL\DBALException
594
     */
595 28
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
596
    {
597 28
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
598
    }
599
600
    /**
601
     * Whether an actual connection to the database is established.
602
     *
603
     * @return boolean
604
     */
605 4
    public function isConnected()
606
    {
607 4
        return $this->_isConnected;
608
    }
609
610
    /**
611
     * Checks whether a transaction is currently active.
612
     *
613
     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
614
     */
615 261
    public function isTransactionActive()
616
    {
617 261
        return $this->_transactionNestingLevel > 0;
618
    }
619
620
    /**
621
     * Gathers conditions for an update or delete call.
622
     *
623
     * @param array $identifiers Input array of columns to values
624
     *
625
     * @return string[][] a triplet with:
626
     *                    - the first key being the columns
627
     *                    - the second key being the values
628
     *                    - the third key being the conditions
629
     */
630 13
    private function gatherConditions(array $identifiers)
631
    {
632 13
        $columns = [];
633 13
        $values = [];
634 13
        $conditions = [];
635
636 13
        foreach ($identifiers as $columnName => $value) {
637 13
            if (null === $value) {
638 4
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
639 4
                continue;
640
            }
641
642 11
            $columns[] = $columnName;
643 11
            $values[] = $value;
644 11
            $conditions[] = $columnName . ' = ?';
645
        }
646
647 13
        return [$columns, $values, $conditions];
648
    }
649
650
    /**
651
     * Executes an SQL DELETE statement on a table.
652
     *
653
     * Table expression and columns are not escaped and are not safe for user-input.
654
     *
655
     * @param string $tableExpression  The expression of the table on which to delete.
656
     * @param array  $identifier The deletion criteria. An associative array containing column-value pairs.
657
     * @param array  $types      The types of identifiers.
658
     *
659
     * @return integer The number of affected rows.
660
     *
661
     * @throws \Doctrine\DBAL\DBALException
662
     * @throws InvalidArgumentException
663
     */
664 6
    public function delete($tableExpression, array $identifier, array $types = [])
665
    {
666 6
        if (empty($identifier)) {
667 1
            throw InvalidArgumentException::fromEmptyCriteria();
668
        }
669
670 5
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
671
672 5
        return $this->executeUpdate(
673 5
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
674 5
            $values,
675 5
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
676
        );
677
    }
678
679
    /**
680
     * Closes the connection.
681
     *
682
     * @return void
683
     */
684 28
    public function close()
685
    {
686 28
        $this->_conn = null;
687
688 28
        $this->_isConnected = false;
689 28
    }
690
691
    /**
692
     * Sets the transaction isolation level.
693
     *
694
     * @param integer $level The level to set.
695
     *
696
     * @return integer
697
     */
698
    public function setTransactionIsolation($level)
699
    {
700
        $this->_transactionIsolationLevel = $level;
701
702
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
703
    }
704
705
    /**
706
     * Gets the currently active transaction isolation level.
707
     *
708
     * @return integer The current transaction isolation level.
709
     */
710
    public function getTransactionIsolation()
711
    {
712
        if (null === $this->_transactionIsolationLevel) {
713
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
714
        }
715
716
        return $this->_transactionIsolationLevel;
717
    }
718
719
    /**
720
     * Executes an SQL UPDATE statement on a table.
721
     *
722
     * Table expression and columns are not escaped and are not safe for user-input.
723
     *
724
     * @param string $tableExpression  The expression of the table to update quoted or unquoted.
725
     * @param array  $data       An associative array containing column-value pairs.
726
     * @param array  $identifier The update criteria. An associative array containing column-value pairs.
727
     * @param array  $types      Types of the merged $data and $identifier arrays in that order.
728
     *
729
     * @return integer The number of affected rows.
730
     *
731
     * @throws \Doctrine\DBAL\DBALException
732
     */
733 8
    public function update($tableExpression, array $data, array $identifier, array $types = [])
734
    {
735 8
        $setColumns = [];
736 8
        $setValues = [];
737 8
        $set = [];
738
739 8
        foreach ($data as $columnName => $value) {
740 8
            $setColumns[] = $columnName;
741 8
            $setValues[] = $value;
742 8
            $set[] = $columnName . ' = ?';
743
        }
744
745 8
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
746 8
        $columns = array_merge($setColumns, $conditionColumns);
747 8
        $values = array_merge($setValues, $conditionValues);
748
749 8
        if (is_string(key($types))) {
750 5
            $types = $this->extractTypeValues($columns, $types);
751
        }
752
753 8
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
754 8
                . ' WHERE ' . implode(' AND ', $conditions);
755
756 8
        return $this->executeUpdate($sql, $values, $types);
757
    }
758
759
    /**
760
     * Inserts a table row with specified data.
761
     *
762
     * Table expression and columns are not escaped and are not safe for user-input.
763
     *
764
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
765
     * @param array  $data      An associative array containing column-value pairs.
766
     * @param array  $types     Types of the inserted data.
767
     *
768
     * @return integer The number of affected rows.
769
     *
770
     * @throws \Doctrine\DBAL\DBALException
771
     */
772 78
    public function insert($tableExpression, array $data, array $types = [])
773
    {
774 78
        if (empty($data)) {
775 1
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
776
        }
777
778 77
        $columns = [];
779 77
        $values = [];
780 77
        $set = [];
781
782 77
        foreach ($data as $columnName => $value) {
783 77
            $columns[] = $columnName;
784 77
            $values[] = $value;
785 77
            $set[] = '?';
786
        }
787
788 77
        return $this->executeUpdate(
789 77
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
790 77
            ' VALUES (' . implode(', ', $set) . ')',
791 77
            $values,
792 77
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
793
        );
794
    }
795
796
    /**
797
     * Extract ordered type list from an ordered column list and type map.
798
     *
799
     * @param array $columnList
800
     * @param array $types
801
     *
802
     * @return array
803
     */
804 9
    private function extractTypeValues(array $columnList, array $types)
805
    {
806 9
        $typeValues = [];
807
808 9
        foreach ($columnList as $columnIndex => $columnName) {
809 9
            $typeValues[] = isset($types[$columnName])
810 9
                ? $types[$columnName]
811 9
                : \PDO::PARAM_STR;
812
        }
813
814 9
        return $typeValues;
815
    }
816
817
    /**
818
     * Quotes a string so it can be safely used as a table or column name, even if
819
     * it is a reserved name.
820
     *
821
     * Delimiting style depends on the underlying database platform that is being used.
822
     *
823
     * NOTE: Just because you CAN use quoted identifiers does not mean
824
     * you SHOULD use them. In general, they end up causing way more
825
     * problems than they solve.
826
     *
827
     * @param string $str The name to be quoted.
828
     *
829
     * @return string The quoted name.
830
     */
831 1
    public function quoteIdentifier($str)
832
    {
833 1
        return $this->getDatabasePlatform()->quoteIdentifier($str);
834
    }
835
836
    /**
837
     * Quotes a given input parameter.
838
     *
839
     * @param mixed       $input The parameter to be quoted.
840
     * @param int|null $type  The type of the parameter.
841
     *
842
     * @return string The quoted parameter.
843
     */
844 4
    public function quote($input, $type = null)
845
    {
846 4
        $this->connect();
847
848 4
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
849
850 4
        return $this->_conn->quote($value, $bindingType);
851
    }
852
853
    /**
854
     * Prepares and executes an SQL query and returns the result as an associative array.
855
     *
856
     * @param string $sql    The SQL query.
857
     * @param array  $params The query parameters.
858
     * @param array  $types  The query parameter types.
859
     *
860
     * @return array
861
     */
862 87
    public function fetchAll($sql, array $params = [], $types = [])
863
    {
864 87
        return $this->executeQuery($sql, $params, $types)->fetchAll();
865
    }
866
867
    /**
868
     * Prepares an SQL statement.
869
     *
870
     * @param string $statement The SQL statement to prepare.
871
     *
872
     * @return \Doctrine\DBAL\Statement The prepared statement.
873
     *
874
     * @throws \Doctrine\DBAL\DBALException
875
     */
876 40
    public function prepare($statement)
877
    {
878
        try {
879 40
            $stmt = new Statement($statement, $this);
880 1
        } catch (Exception $ex) {
881 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
882
        }
883
884 39
        $stmt->setFetchMode($this->defaultFetchMode);
885
886 39
        return $stmt;
887
    }
888
889
    /**
890
     * Executes an, optionally parametrized, SQL query.
891
     *
892
     * If the query is parametrized, a prepared statement is used.
893
     * If an SQLLogger is configured, the execution is logged.
894
     *
895
     * @param string                                      $query  The SQL query to execute.
896
     * @param array                                       $params The parameters to bind to the query, if any.
897
     * @param array                                       $types  The types the previous parameters are in.
898
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
899
     *
900
     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
901
     *
902
     * @throws \Doctrine\DBAL\DBALException
903
     */
904 183
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
905
    {
906 183
        if ($qcp !== null) {
907 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...
908
        }
909
910 183
        $this->connect();
911
912 183
        $logger = $this->_config->getSQLLogger();
913 183
        if ($logger) {
914 178
            $logger->startQuery($query, $params, $types);
915
        }
916
917
        try {
918 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...
919 31
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
920
921 31
                $stmt = $this->_conn->prepare($query);
922 31
                if ($types) {
923 15
                    $this->_bindTypedValues($stmt, $params, $types);
924 15
                    $stmt->execute();
925
                } else {
926 31
                    $stmt->execute($params);
927
                }
928
            } else {
929 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

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

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