Failed Conditions
Push — phpstan ( 752f9f...1acc4a )
by Michael
21:57
created

Connection::getServerVersion()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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

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

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