Failed Conditions
Push — master ( edfbda...298c91 )
by Luís
16s
created

lib/Doctrine/DBAL/Connection.php (18 issues)

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

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

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