Completed
Pull Request — master (#3588)
by Andrej
14:44
created

Connection   F

Complexity

Total Complexity 178

Size/Duplication

Total Lines 1642
Duplicated Lines 0 %

Test Coverage

Coverage 89.56%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 178
eloc 442
c 1
b 0
f 0
dl 0
loc 1642
rs 2
ccs 429
cts 479
cp 0.8956

68 Methods

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

How to fix   Complexity   

Complex Class

Complex classes like Connection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Connection, and based on these observations, apply Extract Interface, too.

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

899
                /** @scrutinizer ignore-call */ 
900
                $stmt = $connection->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...
900
            }
901 8717
        } catch (Throwable $ex) {
902 8717
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
903
        }
904
905 7466
        $stmt->setFetchMode($this->defaultFetchMode);
906
907 7466
        if ($logger) {
908 7425
            $logger->stopQuery();
909
        }
910
911 7466
        return $stmt;
912
    }
913
914
    /**
915
     * Executes a caching query.
916
     *
917
     * @param string            $query  The SQL query to execute.
918
     * @param mixed[]           $params The parameters to bind to the query, if any.
919
     * @param int[]|string[]    $types  The types the previous parameters are in.
920
     * @param QueryCacheProfile $qcp    The query cache profile.
921
     *
922
     * @return ResultStatement
923
     *
924
     * @throws CacheException
925
     */
926 8029
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
927
    {
928 8029
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
929
930 8029
        if ($resultCache === null) {
931
            throw CacheException::noResultDriverConfigured();
932
        }
933
934 8029
        $connectionParams = $this->getParams();
935 8029
        unset($connectionParams['platform']);
936
937 8029
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $connectionParams);
938
939
        // fetch the row pointers entry
940 8029
        $data = $resultCache->fetch($cacheKey);
941
942 8029
        if ($data !== false) {
943
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
944 8021
            if (isset($data[$realKey])) {
945 8021
                $stmt = new ArrayStatement($data[$realKey]);
946
            } elseif (array_key_exists($realKey, $data)) {
947
                $stmt = new ArrayStatement([]);
948
            }
949
        }
950
951 8029
        if (! isset($stmt)) {
952 4134
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
953
        }
954
955 8029
        $stmt->setFetchMode($this->defaultFetchMode);
956
957 8029
        return $stmt;
958
    }
959
960
    /**
961
     * Executes an, optionally parametrized, SQL query and returns the result,
962
     * applying a given projection/transformation function on each row of the result.
963
     *
964
     * @param string  $query    The SQL query to execute.
965
     * @param mixed[] $params   The parameters, if any.
966
     * @param Closure $function The transformation function that is applied on each row.
967
     *                           The function receives a single parameter, an array, that
968
     *                           represents a row of the result set.
969
     *
970
     * @return mixed[] The projected result of the query.
971
     */
972
    public function project($query, array $params, Closure $function)
973
    {
974
        $result = [];
975
        $stmt   = $this->executeQuery($query, $params);
976
977
        while ($row = $stmt->fetch()) {
978
            $result[] = $function($row);
979
        }
980
981
        $stmt->closeCursor();
982
983
        return $result;
984
    }
985
986
    /**
987
     * Executes an SQL statement, returning a result set as a Statement object.
988
     *
989
     * @return \Doctrine\DBAL\Driver\Statement
990
     *
991
     * @throws DBALException
992
     */
993 8760
    public function query()
994
    {
995 8760
        $connection = $this->getWrappedConnection();
996
997 8760
        $args = func_get_args();
998
999 8760
        $logger = $this->_config->getSQLLogger();
1000 8760
        if ($logger) {
1001 7328
            $logger->startQuery($args[0]);
1002
        }
1003
1004
        try {
1005 8760
            $statement = $connection->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

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