Completed
Pull Request — master (#3588)
by Andrej
19:46
created

Connection   F

Complexity

Total Complexity 178

Size/Duplication

Total Lines 1646
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 1646
ccs 429
cts 479
cp 0.8956
rs 2

68 Methods

Rating   Name   Duplication   Size   Complexity  
A isAutoCommit() 0 3 1
A getTransactionNestingLevel() 0 3 1
A setTransactionIsolation() 0 5 1
A getTransactionIsolation() 0 7 2
A close() 0 5 1
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
A getWrappedConnection() 0 5 1
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 setRollbackOnly() 0 6 2
A createSavepoint() 0 7 2
A commitAll() 0 12 4
A isRollbackOnly() 0 7 2
A convertToPHPValue() 0 3 1
A ping() 0 14 3
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 9254
    public function __construct(
183
        array $params,
184
        Driver $driver,
185
        ?Configuration $config = null,
186
        ?EventManager $eventManager = null
187
    ) {
188 9254
        $this->_driver = $driver;
189 9254
        $this->params  = $params;
190
191 9254
        if (isset($params['pdo'])) {
192 8517
            $this->_conn       = $params['pdo'];
193 8517
            $this->isConnected = true;
194 8517
            unset($this->params['pdo']);
195
        }
196
197 9254
        if (isset($params['platform'])) {
198 8415
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
199 7960
                throw DBALException::invalidPlatformType($params['platform']);
200
            }
201
202 8413
            $this->platform = $params['platform'];
203
        }
204
205
        // Create default config and event manager if none given
206 9254
        if (! $config) {
207 8853
            $config = new Configuration();
208
        }
209
210 9254
        if (! $eventManager) {
211 8601
            $eventManager = new EventManager();
212
        }
213
214 9254
        $this->_config       = $config;
215 9254
        $this->_eventManager = $eventManager;
216
217 9254
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
218
219 9254
        $this->autoCommit = $config->getAutoCommit();
220 9254
    }
221
222
    /**
223
     * Gets the parameters used during instantiation.
224
     *
225
     * @return mixed[]
226
     */
227 8448
    public function getParams()
228
    {
229 8448
        return $this->params;
230
    }
231
232
    /**
233
     * Gets the name of the database this Connection is connected to.
234
     *
235
     * @return string
236
     */
237 7868
    public function getDatabase()
238
    {
239 7868
        return $this->_driver->getDatabase($this);
240
    }
241
242
    /**
243
     * Gets the hostname of the currently connected database.
244
     *
245
     * @return string|null
246
     */
247 8860
    public function getHost()
248
    {
249 8860
        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 8948
    public function getDriver()
288
    {
289 8948
        return $this->_driver;
290
    }
291
292
    /**
293
     * Gets the Configuration used by the Connection.
294
     *
295
     * @return Configuration
296
     */
297 9492
    public function getConfiguration()
298
    {
299 9492
        return $this->_config;
300
    }
301
302
    /**
303
     * Gets the EventManager used by the Connection.
304
     *
305
     * @return EventManager
306
     */
307 6827
    public function getEventManager()
308
    {
309 6827
        return $this->_eventManager;
310
    }
311
312
    /**
313
     * Gets the DatabasePlatform for the connection.
314
     *
315
     * @return AbstractPlatform
316
     *
317
     * @throws DBALException
318
     */
319 9226
    public function getDatabasePlatform()
320
    {
321 9226
        if ($this->platform === null) {
322 8843
            $this->detectDatabasePlatform();
323
        }
324
325 9224
        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 9439
    public function connect()
345
    {
346 9439
        if ($this->isConnected) {
347 9142
            return false;
348
        }
349
350 8900
        $driverOptions = $this->params['driverOptions'] ?? [];
351 8900
        $user          = $this->params['user'] ?? null;
352 8900
        $password      = $this->params['password'] ?? null;
353
354 8900
        $this->_conn       = $this->_driver->connect($this->params, $user, $password, $driverOptions);
355 8892
        $this->isConnected = true;
356
357 8892
        if ($this->autoCommit === false) {
358 8539
            $this->beginTransaction();
359
        }
360
361 8892
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
362 8860
            $eventArgs = new Event\ConnectionEventArgs($this);
363 8860
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
364
        }
365
366 8892
        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 8843
    private function detectDatabasePlatform()
377
    {
378 8843
        $version = $this->getDatabasePlatformVersion();
379
380 8841
        if ($version !== null) {
381 8035
            assert($this->_driver instanceof VersionAwarePlatformDriver);
382
383 8035
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
384
        } else {
385 8839
            $this->platform = $this->_driver->getDatabasePlatform();
386
        }
387
388 8841
        $this->platform->setEventManager($this->_eventManager);
389 8841
    }
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 8843
    private function getDatabasePlatformVersion()
404
    {
405
        // Driver does not support version specific platforms.
406 8843
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
407 8839
            return null;
408
        }
409
410
        // Explicit platform version requested (supersedes auto-detection).
411 8037
        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 8037
        if ($this->_conn === null) {
417
            try {
418 8037
                $this->connect();
419 7935
            } catch (Throwable $originalException) {
420 7935
                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 7935
                $databaseName           = $this->params['dbname'];
427 7935
                $this->params['dbname'] = null;
428
429
                try {
430 7935
                    $this->connect();
431 7935
                } 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 7935
                    $this->params['dbname'] = $databaseName;
436
437 7935
                    throw $originalException;
438
                }
439
440
                // Reset connection parameters.
441 6357
                $this->params['dbname'] = $databaseName;
442 6357
                $serverVersion          = $this->getServerVersion();
443
444
                // Close "temporary" connection to allow connecting to the real database again.
445 6357
                $this->close();
446
447 6357
                return $serverVersion;
448
            }
449
        }
450
451 8035
        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 8035
    private function getServerVersion()
460
    {
461 8035
        $connection = $this->getWrappedConnection();
462
463
        // Automatic platform version detection.
464 8035
        if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
465 8035
            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 8587
    public function isAutoCommit()
480
    {
481 8587
        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 8568
    public function setAutoCommit($autoCommit)
499
    {
500 8568
        $autoCommit = (bool) $autoCommit;
501
502
        // Mode not changed, no-op.
503 8568
        if ($autoCommit === $this->autoCommit) {
504 8560
            return;
505
        }
506
507 8568
        $this->autoCommit = $autoCommit;
508
509
        // Commit all currently active transactions if any when switching auto-commit mode.
510 8568
        if ($this->isConnected !== true || $this->transactionNestingLevel === 0) {
511 8566
            return;
512
        }
513
514 8410
        $this->commitAll();
515 8410
    }
516
517
    /**
518
     * Sets the fetch mode.
519
     *
520
     * @param int $fetchMode
521
     *
522
     * @return void
523
     */
524 4210
    public function setFetchMode($fetchMode)
525
    {
526 4210
        $this->defaultFetchMode = $fetchMode;
527 4210
    }
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 8367
    public function fetchAssoc($statement, array $params = [], array $types = [])
542
    {
543 8367
        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 6426
    public function fetchArray($statement, array $params = [], array $types = [])
557
    {
558 6426
        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 8354
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
575
    {
576 8354
        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 8989
    public function isConnected()
585
    {
586 8989
        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 9565
    public function isTransactionActive()
595
    {
596 9565
        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 8387
    private function addIdentifierCondition(
610
        array $identifier,
611
        array &$columns,
612
        array &$values,
613
        array &$conditions
614
    ) : void {
615 8387
        $platform = $this->getDatabasePlatform();
616
617 8387
        foreach ($identifier as $columnName => $value) {
618 8387
            if ($value === null) {
619 8341
                $conditions[] = $platform->getIsNullExpression($columnName);
620 8341
                continue;
621
            }
622
623 8383
            $columns[]    = $columnName;
624 8383
            $values[]     = $value;
625 8383
            $conditions[] = $columnName . ' = ?';
626
        }
627 8387
    }
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 8322
    public function delete($tableExpression, array $identifier, array $types = [])
644
    {
645 8322
        if (empty($identifier)) {
646 8185
            throw InvalidArgumentException::fromEmptyCriteria();
647
        }
648
649 8320
        $columns = $values = $conditions = [];
650
651 8320
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
652
653 8320
        return $this->executeUpdate(
654 8320
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
655 12
            $values,
656 8320
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
657
        );
658
    }
659
660
    /**
661
     * Closes the connection.
662
     *
663
     * @return void
664
     */
665 7872
    public function close()
666
    {
667 7872
        $this->_conn = null;
668
669 7872
        $this->isConnected = false;
670 7872
    }
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 8375
    public function update($tableExpression, array $data, array $identifier, array $types = [])
715
    {
716 8375
        $columns = $values = $conditions = $set = [];
717
718 8375
        foreach ($data as $columnName => $value) {
719 8375
            $columns[] = $columnName;
720 8375
            $values[]  = $value;
721 8375
            $set[]     = $columnName . ' = ?';
722
        }
723
724 8375
        $this->addIdentifierCondition($identifier, $columns, $values, $conditions);
725
726 8375
        if (is_string(key($types))) {
727 8368
            $types = $this->extractTypeValues($columns, $types);
728
        }
729
730 8375
        $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
731 8375
                . ' WHERE ' . implode(' AND ', $conditions);
732
733 8375
        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 8538
    public function insert($tableExpression, array $data, array $types = [])
750
    {
751 8538
        if (empty($data)) {
752 8385
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' () VALUES ()');
753
        }
754
755 8286
        $columns = [];
756 8286
        $values  = [];
757 8286
        $set     = [];
758
759 8286
        foreach ($data as $columnName => $value) {
760 8286
            $columns[] = $columnName;
761 8286
            $values[]  = $value;
762 8286
            $set[]     = '?';
763
        }
764
765 8286
        return $this->executeUpdate(
766 8286
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
767 8286
            ' VALUES (' . implode(', ', $set) . ')',
768 153
            $values,
769 8286
            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 8376
    private function extractTypeValues(array $columnList, array $types)
782
    {
783 8376
        $typeValues = [];
784
785 8376
        foreach ($columnList as $columnIndex => $columnName) {
786 8376
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
787
        }
788
789 8376
        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 6654
    public function quoteIdentifier($str)
807
    {
808 6654
        return $this->getDatabasePlatform()->quoteIdentifier($str);
809
    }
810
811
    /**
812
     * {@inheritDoc}
813
     */
814 6956
    public function quote($input, $type = null)
815
    {
816 6956
        $connection = $this->getWrappedConnection();
817
818 6956
        [$value, $bindingType] = $this->getBindingInfo($input, $type);
819
820 6956
        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 8440
    public function fetchAll($sql, array $params = [], $types = [])
833
    {
834 8440
        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 8737
    public function prepare($statement)
847
    {
848
        try {
849 8737
            $stmt = new Statement($statement, $this);
850 8660
        } catch (Throwable $ex) {
851 8660
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
852
        }
853
854 8160
        $stmt->setFetchMode($this->defaultFetchMode);
855
856 8160
        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 9157
    public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
875
    {
876 9157
        if ($qcp !== null) {
877 4139
            return $this->executeCacheQuery($query, $params, $types, $qcp);
878
        }
879
880 9157
        $connection = $this->getWrappedConnection();
881
882 9157
        $logger = $this->_config->getSQLLogger();
883 9157
        if ($logger) {
884 7446
            $logger->startQuery($query, $params, $types);
885
        }
886
887
        try {
888 9157
            if ($params) {
889 6678
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
890
891 6678
                $stmt = $connection->prepare($query);
892 6678
                if ($types) {
893 6608
                    $this->_bindTypedValues($stmt, $params, $types);
894 6608
                    $stmt->execute();
895
                } else {
896 6678
                    $stmt->execute($params);
897
                }
898
            } else {
899 9149
                $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 8724
        } catch (Throwable $ex) {
902 8724
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
903
        }
904
905 7473
        $stmt->setFetchMode($this->defaultFetchMode);
906
907 7473
        if ($logger) {
908 7432
            $logger->stopQuery();
909
        }
910
911 7473
        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 8036
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
927
    {
928 8036
        $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
929
930 8036
        if ($resultCache === null) {
931
            throw CacheException::noResultDriverConfigured();
932
        }
933
934 8036
        $connectionParams = $this->getParams();
935 8036
        unset($connectionParams['platform']);
936
937 8036
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($query, $params, $types, $connectionParams);
938
939
        // fetch the row pointers entry
940 8036
        $data = $resultCache->fetch($cacheKey);
941
942 8036
        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 8028
            if (isset($data[$realKey])) {
945 8028
                $stmt = new ArrayStatement($data[$realKey]);
946
            } elseif (array_key_exists($realKey, $data)) {
947
                $stmt = new ArrayStatement([]);
948
            }
949
        }
950
951 8036
        if (! isset($stmt)) {
952 4139
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
953
        }
954
955 8036
        $stmt->setFetchMode($this->defaultFetchMode);
956
957 8036
        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 8767
    public function query()
994
    {
995 8767
        $connection = $this->getWrappedConnection();
996
997 8767
        $args = func_get_args();
998
999 8767
        $logger = $this->_config->getSQLLogger();
1000 8767
        if ($logger) {
1001 7335
            $logger->startQuery($args[0]);
1002
        }
1003
1004
        try {
1005 8767
            $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 8735
        } catch (Throwable $ex) {
1007 8735
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1008
        }
1009
1010 7337
        $statement->setFetchMode($this->defaultFetchMode);
1011
1012 7337
        if ($logger) {
1013 7335
            $logger->stopQuery();
1014
        }
1015
1016 7337
        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 9041
    public function executeUpdate($query, array $params = [], array $types = [])
1034
    {
1035 9041
        $connection = $this->getWrappedConnection();
1036
1037 9041
        $logger = $this->_config->getSQLLogger();
1038 9041
        if ($logger) {
1039 7678
            $logger->startQuery($query, $params, $types);
1040
        }
1041
1042
        try {
1043 9041
            if ($params) {
1044 8325
                [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
1045
1046 8325
                $stmt = $connection->prepare($query);
1047
1048 8325
                if ($types) {
1049 7351
                    $this->_bindTypedValues($stmt, $params, $types);
1050 7351
                    $stmt->execute();
1051
                } else {
1052 8304
                    $stmt->execute($params);
1053
                }
1054 8321
                $result = $stmt->rowCount();
1055
            } else {
1056 9037
                $result = $connection->exec($query);
1057
            }
1058 8957
        } catch (Throwable $ex) {
1059 8957
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1060
        }
1061
1062 8498
        if ($logger) {
1063 7660
            $logger->stopQuery();
1064
        }
1065
1066 8498
        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 8920
    public function exec($statement)
1079
    {
1080 8920
        $connection = $this->getWrappedConnection();
1081
1082 8914
        $logger = $this->_config->getSQLLogger();
1083 8914
        if ($logger) {
1084 5862
            $logger->startQuery($statement);
1085
        }
1086
1087
        try {
1088 8914
            $result = $connection->exec($statement);
1089 8868
        } catch (Throwable $ex) {
1090 8868
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1091
        }
1092
1093 5760
        if ($logger) {
1094 5754
            $logger->stopQuery();
1095
        }
1096
1097 5760
        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 8438
    public function getTransactionNestingLevel()
1106
    {
1107 8438
        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 810
    public function lastInsertId($seqName = null)
1141
    {
1142 810
        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 7015
    public function transactional(Closure $func)
1161
    {
1162 7015
        $this->beginTransaction();
1163
        try {
1164 7015
            $res = $func($this);
1165 6961
            $this->commit();
1166
1167 6961
            return $res;
1168 7011
        } catch (Exception $e) {
1169 7009
            $this->rollBack();
1170 7009
            throw $e;
1171 6984
        } catch (Throwable $e) {
1172 6984
            $this->rollBack();
1173 6984
            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 7110
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1187
    {
1188 7110
        if ($this->transactionNestingLevel > 0) {
1189 6877
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1190
        }
1191
1192 7108
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1193 233
            throw ConnectionException::savepointsNotSupported();
1194
        }
1195
1196 6875
        $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1197 6875
    }
1198
1199
    /**
1200
     * Gets if nested transactions should use savepoints.
1201
     *
1202
     * @return bool
1203
     */
1204 6875
    public function getNestTransactionsWithSavepoints()
1205
    {
1206 6875
        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 6875
    protected function _getNestedTransactionSavePointName()
1216
    {
1217 6875
        return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel;
1218
    }
1219
1220
    /**
1221
     * {@inheritDoc}
1222
     */
1223 8581
    public function beginTransaction()
1224
    {
1225 8581
        $connection = $this->getWrappedConnection();
1226
1227 8581
        ++$this->transactionNestingLevel;
1228
1229 8581
        $logger = $this->_config->getSQLLogger();
1230
1231 8581
        if ($this->transactionNestingLevel === 1) {
1232 8581
            if ($logger) {
1233 7186
                $logger->startQuery('"START TRANSACTION"');
1234
            }
1235
1236 8581
            $connection->beginTransaction();
1237
1238 8581
            if ($logger) {
1239 8581
                $logger->stopQuery();
1240
            }
1241 8414
        } elseif ($this->nestTransactionsWithSavepoints) {
1242 6875
            if ($logger) {
1243 6875
                $logger->startQuery('"SAVEPOINT"');
1244
            }
1245 6875
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1246 6875
            if ($logger) {
1247 6875
                $logger->stopQuery();
1248
            }
1249
        }
1250
1251 8581
        return true;
1252
    }
1253
1254
    /**
1255
     * {@inheritDoc}
1256
     *
1257
     * @return bool
1258
     *
1259
     * @throws ConnectionException If the commit failed due to no active transaction or
1260
     *                                            because the transaction was marked for rollback only.
1261
     */
1262 8961
    public function commit()
1263
    {
1264 8961
        if ($this->transactionNestingLevel === 0) {
1265 8935
            throw ConnectionException::noActiveTransaction();
1266
        }
1267 8534
        if ($this->isRollbackOnly) {
1268 7160
            throw ConnectionException::commitFailedRollbackOnly();
1269
        }
1270
1271 8530
        $result = true;
1272
1273 8530
        $connection = $this->getWrappedConnection();
1274
1275 8530
        $logger = $this->_config->getSQLLogger();
1276
1277 8530
        if ($this->transactionNestingLevel === 1) {
1278 8530
            if ($logger) {
1279 7117
                $logger->startQuery('"COMMIT"');
1280
            }
1281
1282 8530
            $result = $connection->commit();
1283
1284 8530
            if ($logger) {
1285 8530
                $logger->stopQuery();
1286
            }
1287 8412
        } elseif ($this->nestTransactionsWithSavepoints) {
1288 6875
            if ($logger) {
1289 6875
                $logger->startQuery('"RELEASE SAVEPOINT"');
1290
            }
1291 6875
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1292 6875
            if ($logger) {
1293 6875
                $logger->stopQuery();
1294
            }
1295
        }
1296
1297 8530
        --$this->transactionNestingLevel;
1298
1299 8530
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1300 8503
            return $result;
1301
        }
1302
1303 8512
        $this->beginTransaction();
1304
1305 8512
        return $result;
1306
    }
1307
1308
    /**
1309
     * Commits all current nesting transactions.
1310
     */
1311 8410
    private function commitAll()
1312
    {
1313 8410
        while ($this->transactionNestingLevel !== 0) {
1314 8410
            if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
1315
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1316
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1317 8410
                $this->commit();
1318
1319 8410
                return;
1320
            }
1321
1322 8410
            $this->commit();
1323
        }
1324 8410
    }
1325
1326
    /**
1327
     * Cancels any database changes done during the current transaction.
1328
     *
1329
     * @return bool
1330
     *
1331
     * @throws ConnectionException If the rollback operation failed.
1332
     */
1333 8936
    public function rollBack()
1334
    {
1335 8936
        if ($this->transactionNestingLevel === 0) {
1336 8910
            throw ConnectionException::noActiveTransaction();
1337
        }
1338
1339 8484
        $result = true;
1340
1341 8484
        $connection = $this->getWrappedConnection();
1342
1343 8484
        $logger = $this->_config->getSQLLogger();
1344
1345 8484
        if ($this->transactionNestingLevel === 1) {
1346 8482
            if ($logger) {
1347 7174
                $logger->startQuery('"ROLLBACK"');
1348
            }
1349 8482
            $this->transactionNestingLevel = 0;
1350
1351 8482
            $result = $connection->rollBack();
1352
1353 8482
            $this->isRollbackOnly = false;
1354 8482
            if ($logger) {
1355 7174
                $logger->stopQuery();
1356
            }
1357
1358 8482
            if ($this->autoCommit === false) {
1359 8482
                $this->beginTransaction();
1360
            }
1361 7135
        } elseif ($this->nestTransactionsWithSavepoints) {
1362 6875
            if ($logger) {
1363 6875
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1364
            }
1365 6875
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1366 6875
            --$this->transactionNestingLevel;
1367 6875
            if ($logger) {
1368 6875
                $logger->stopQuery();
1369
            }
1370
        } else {
1371 7133
            $this->isRollbackOnly = true;
1372 7133
            --$this->transactionNestingLevel;
1373
        }
1374
1375 8484
        return $result;
1376
    }
1377
1378
    /**
1379
     * Creates a new savepoint.
1380
     *
1381
     * @param string $savepoint The name of the savepoint to create.
1382
     *
1383
     * @return void
1384
     *
1385
     * @throws ConnectionException
1386
     */
1387 7107
    public function createSavepoint($savepoint)
1388
    {
1389 7107
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1390 232
            throw ConnectionException::savepointsNotSupported();
1391
        }
1392
1393 6875
        $this->getWrappedConnection()->exec($this->platform->createSavePoint($savepoint));
1394 6875
    }
1395
1396
    /**
1397
     * Releases the given savepoint.
1398
     *
1399
     * @param string $savepoint The name of the savepoint to release.
1400
     *
1401
     * @return void
1402
     *
1403
     * @throws ConnectionException
1404
     */
1405 6875
    public function releaseSavepoint($savepoint)
1406
    {
1407 6875
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1408
            throw ConnectionException::savepointsNotSupported();
1409
        }
1410
1411 6875
        if (! $this->platform->supportsReleaseSavepoints()) {
1412 502
            return;
1413
        }
1414
1415 6373
        $this->getWrappedConnection()->exec($this->platform->releaseSavePoint($savepoint));
1416 6373
    }
1417
1418
    /**
1419
     * Rolls back to the given savepoint.
1420
     *
1421
     * @param string $savepoint The name of the savepoint to rollback to.
1422
     *
1423
     * @return void
1424
     *
1425
     * @throws ConnectionException
1426
     */
1427 6875
    public function rollbackSavepoint($savepoint)
1428
    {
1429 6875
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1430
            throw ConnectionException::savepointsNotSupported();
1431
        }
1432
1433 6875
        $this->getWrappedConnection()->exec($this->platform->rollbackSavePoint($savepoint));
1434 6875
    }
1435
1436
    /**
1437
     * Gets the wrapped driver connection.
1438
     *
1439
     * @return DriverConnection
1440
     */
1441 9399
    public function getWrappedConnection()
1442
    {
1443 9399
        $this->connect();
1444
1445 9393
        return $this->_conn;
1446
    }
1447
1448
    /**
1449
     * Gets the SchemaManager that can be used to inspect or change the
1450
     * database schema through the connection.
1451
     *
1452
     * @return AbstractSchemaManager
1453
     */
1454 8196
    public function getSchemaManager()
1455
    {
1456 8196
        if ($this->_schemaManager === null) {
1457 7889
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1458
        }
1459
1460 8196
        return $this->_schemaManager;
1461
    }
1462
1463
    /**
1464
     * Marks the current transaction so that the only possible
1465
     * outcome for the transaction to be rolled back.
1466
     *
1467
     * @return void
1468
     *
1469
     * @throws ConnectionException If no transaction is active.
1470
     */
1471 7160
    public function setRollbackOnly()
1472
    {
1473 7160
        if ($this->transactionNestingLevel === 0) {
1474 2
            throw ConnectionException::noActiveTransaction();
1475
        }
1476 7158
        $this->isRollbackOnly = true;
1477 7158
    }
1478
1479
    /**
1480
     * Checks whether the current transaction is marked for rollback only.
1481
     *
1482
     * @return bool
1483
     *
1484
     * @throws ConnectionException If no transaction is active.
1485
     */
1486 7137
    public function isRollbackOnly()
1487
    {
1488 7137
        if ($this->transactionNestingLevel === 0) {
1489 2
            throw ConnectionException::noActiveTransaction();
1490
        }
1491
1492 7135
        return $this->isRollbackOnly;
1493
    }
1494
1495
    /**
1496
     * Converts a given value to its database representation according to the conversion
1497
     * rules of a specific DBAL mapping type.
1498
     *
1499
     * @param mixed  $value The value to convert.
1500
     * @param string $type  The name of the DBAL mapping type.
1501
     *
1502
     * @return mixed The converted value.
1503
     */
1504
    public function convertToDatabaseValue($value, $type)
1505
    {
1506
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1507
    }
1508
1509
    /**
1510
     * Converts a given value to its PHP representation according to the conversion
1511
     * rules of a specific DBAL mapping type.
1512
     *
1513
     * @param mixed  $value The value to convert.
1514
     * @param string $type  The name of the DBAL mapping type.
1515
     *
1516
     * @return mixed The converted type.
1517
     */
1518
    public function convertToPHPValue($value, $type)
1519
    {
1520
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1521
    }
1522
1523
    /**
1524
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1525
     * or DBAL mapping type, to a given statement.
1526
     *
1527
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1528
     *           raw PDOStatement instances.
1529
     *
1530
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1531
     * @param mixed[]                         $params The map/list of named/positional parameters.
1532
     * @param int[]|string[]                  $types  The parameter types (PDO binding types or DBAL mapping types).
1533
     *
1534
     * @return void
1535
     */
1536 7379
    private function _bindTypedValues($stmt, array $params, array $types)
1537
    {
1538
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1539 7379
        if (is_int(key($params))) {
1540
            // Positional parameters
1541 7379
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1542 7379
            $bindIndex  = 1;
1543 7379
            foreach ($params as $value) {
1544 7379
                $typeIndex = $bindIndex + $typeOffset;
1545 7379
                if (isset($types[$typeIndex])) {
1546 7379
                    $type                  = $types[$typeIndex];
1547 7379
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1548 7379
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1549
                } else {
1550 7233
                    $stmt->bindValue($bindIndex, $value);
1551
                }
1552 7379
                ++$bindIndex;
1553
            }
1554
        } else {
1555
            // Named parameters
1556
            foreach ($params as $name => $value) {
1557
                if (isset($types[$name])) {
1558
                    $type                  = $types[$name];
1559
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1560
                    $stmt->bindValue($name, $value, $bindingType);
1561
                } else {
1562
                    $stmt->bindValue($name, $value);
1563
                }
1564
            }
1565
        }
1566 7379
    }
1567
1568
    /**
1569
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1570
     *
1571
     * @param mixed           $value The value to bind.
1572
     * @param int|string|null $type  The type to bind (PDO or DBAL).
1573
     *
1574
     * @return mixed[] [0] => the (escaped) value, [1] => the binding type.
1575
     */
1576 7412
    private function getBindingInfo($value, $type)
1577
    {
1578 7412
        if (is_string($type)) {
1579 6931
            $type = Type::getType($type);
1580
        }
1581 7412
        if ($type instanceof Type) {
1582 6931
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1583 6931
            $bindingType = $type->getBindingType();
1584
        } else {
1585 7400
            $bindingType = $type;
1586
        }
1587
1588 7412
        return [$value, $bindingType];
1589
    }
1590
1591
    /**
1592
     * Resolves the parameters to a format which can be displayed.
1593
     *
1594
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1595
     *           copy/paste the code as this method may change, or be removed without prior notice.
1596
     *
1597
     * @param mixed[]        $params
1598
     * @param int[]|string[] $types
1599
     *
1600
     * @return mixed[]
1601
     */
1602 8998
    public function resolveParams(array $params, array $types)
1603
    {
1604 8998
        $resolvedParams = [];
1605
1606
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1607 8998
        if (is_int(key($params))) {
1608
            // Positional parameters
1609 6033
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1610 6033
            $bindIndex  = 1;
1611 6033
            foreach ($params as $value) {
1612 6033
                $typeIndex = $bindIndex + $typeOffset;
1613 6033
                if (isset($types[$typeIndex])) {
1614
                    $type                       = $types[$typeIndex];
1615
                    [$value]                    = $this->getBindingInfo($value, $type);
1616
                    $resolvedParams[$bindIndex] = $value;
1617
                } else {
1618 6033
                    $resolvedParams[$bindIndex] = $value;
1619
                }
1620 6033
                ++$bindIndex;
1621
            }
1622
        } else {
1623
            // Named parameters
1624 8982
            foreach ($params as $name => $value) {
1625
                if (isset($types[$name])) {
1626
                    $type                  = $types[$name];
1627
                    [$value]               = $this->getBindingInfo($value, $type);
1628
                    $resolvedParams[$name] = $value;
1629
                } else {
1630
                    $resolvedParams[$name] = $value;
1631
                }
1632
            }
1633
        }
1634
1635 8998
        return $resolvedParams;
1636
    }
1637
1638
    /**
1639
     * Creates a new instance of a SQL query builder.
1640
     *
1641
     * @return QueryBuilder
1642
     */
1643
    public function createQueryBuilder()
1644
    {
1645
        return new Query\QueryBuilder($this);
1646
    }
1647
1648
    /**
1649
     * Ping the server
1650
     *
1651
     * When the server is not available the method returns FALSE.
1652
     * It is responsibility of the developer to handle this case
1653
     * and abort the request or reconnect manually:
1654
     *
1655
     * @return bool
1656
     *
1657
     * @example
1658
     *
1659
     *   if ($conn->ping() === false) {
1660
     *      $conn->close();
1661
     *      $conn->connect();
1662
     *   }
1663
     *
1664
     * It is undefined if the underlying driver attempts to reconnect
1665
     * or disconnect when the connection is not available anymore
1666
     * as long it returns TRUE when a reconnect succeeded and
1667
     * FALSE when the connection was dropped.
1668
     */
1669 6886
    public function ping()
1670
    {
1671 6886
        $connection = $this->getWrappedConnection();
1672
1673 6886
        if ($connection instanceof PingableConnection) {
1674 1942
            return $connection->ping();
1675
        }
1676
1677
        try {
1678 6865
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1679
1680 6865
            return true;
1681
        } catch (DBALException $e) {
1682
            return false;
1683
        }
1684
    }
1685
}
1686