Issues (3885)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

doctrine/dbal/lib/Doctrine/DBAL/Connection.php (15 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL;
21
22
use Doctrine\DBAL\Driver\ResultStatement;
23
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
24
use Doctrine\DBAL\Exception\InvalidArgumentException;
25
use Closure;
26
use Exception;
27
use Doctrine\DBAL\Types\Type;
28
use Doctrine\DBAL\Driver\Connection as DriverConnection;
29
use Doctrine\DBAL\Driver\Statement as DriverStatement;
30
use Doctrine\Common\EventManager;
31
use Doctrine\DBAL\Cache\ResultCacheStatement;
32
use Doctrine\DBAL\Cache\QueryCacheProfile;
33
use Doctrine\DBAL\Cache\ArrayStatement;
34
use Doctrine\DBAL\Cache\CacheException;
35
use Doctrine\DBAL\Driver\PingableConnection;
36
use Throwable;
37
use function assert;
38
use function array_key_exists;
39
use function array_merge;
40
use function func_get_args;
41
use function implode;
42
use function is_int;
43
use function is_string;
44
use function key;
45
46
/**
47
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
48
 * events, transaction isolation levels, configuration, emulated transaction nesting,
49
 * lazy connecting and more.
50
 *
51
 * @link   www.doctrine-project.org
52
 * @since  2.0
53
 * @author Guilherme Blanco <[email protected]>
54
 * @author Jonathan Wage <[email protected]>
55
 * @author Roman Borschel <[email protected]>
56
 * @author Konsta Vesterinen <[email protected]>
57
 * @author Lukas Smith <[email protected]> (MDB2 library)
58
 * @author Benjamin Eberlei <[email protected]>
59
 */
60
class Connection implements DriverConnection
61
{
62
    /**
63
     * Constant for transaction isolation level READ UNCOMMITTED.
64
     *
65
     * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
66
     */
67
    public const TRANSACTION_READ_UNCOMMITTED = TransactionIsolationLevel::READ_UNCOMMITTED;
68
69
    /**
70
     * Constant for transaction isolation level READ COMMITTED.
71
     *
72
     * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
73
     */
74
    public const TRANSACTION_READ_COMMITTED = TransactionIsolationLevel::READ_COMMITTED;
75
76
    /**
77
     * Constant for transaction isolation level REPEATABLE READ.
78
     *
79
     * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
80
     */
81
    public const TRANSACTION_REPEATABLE_READ = TransactionIsolationLevel::REPEATABLE_READ;
82
83
    /**
84
     * Constant for transaction isolation level SERIALIZABLE.
85
     *
86
     * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
87
     */
88
    public const TRANSACTION_SERIALIZABLE = TransactionIsolationLevel::SERIALIZABLE;
89
90
    /**
91
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
92
     *
93
     * @var int
94
     */
95
    public const PARAM_INT_ARRAY = ParameterType::INTEGER + self::ARRAY_PARAM_OFFSET;
96
97
    /**
98
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
99
     *
100
     * @var int
101
     */
102
    public const PARAM_STR_ARRAY = ParameterType::STRING + self::ARRAY_PARAM_OFFSET;
103
104
    /**
105
     * Offset by which PARAM_* constants are detected as arrays of the param type.
106
     *
107
     * @var int
108
     */
109
    const ARRAY_PARAM_OFFSET = 100;
110
111
    /**
112
     * The wrapped driver connection.
113
     *
114
     * @var \Doctrine\DBAL\Driver\Connection|null
115
     */
116
    protected $_conn;
117
118
    /**
119
     * @var \Doctrine\DBAL\Configuration
120
     */
121
    protected $_config;
122
123
    /**
124
     * @var \Doctrine\Common\EventManager
125
     */
126
    protected $_eventManager;
127
128
    /**
129
     * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
130
     */
131
    protected $_expr;
132
133
    /**
134
     * Whether or not a connection has been established.
135
     *
136
     * @var bool
137
     */
138
    private $_isConnected = false;
139
140
    /**
141
     * The current auto-commit mode of this connection.
142
     *
143
     * @var bool
144
     */
145
    private $autoCommit = true;
146
147
    /**
148
     * The transaction nesting level.
149
     *
150
     * @var int
151
     */
152
    private $_transactionNestingLevel = 0;
153
154
    /**
155
     * The currently active transaction isolation level.
156
     *
157
     * @var int
158
     */
159
    private $_transactionIsolationLevel;
160
161
    /**
162
     * If nested transactions should use savepoints.
163
     *
164
     * @var bool
165
     */
166
    private $_nestTransactionsWithSavepoints = false;
167
168
    /**
169
     * The parameters used during creation of the Connection instance.
170
     *
171
     * @var array
172
     */
173
    private $_params = [];
174
175
    /**
176
     * The DatabasePlatform object that provides information about the
177
     * database platform used by the connection.
178
     *
179
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
180
     */
181
    private $platform;
182
183
    /**
184
     * The schema manager.
185
     *
186
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
187
     */
188
    protected $_schemaManager;
189
190
    /**
191
     * The used DBAL driver.
192
     *
193
     * @var \Doctrine\DBAL\Driver
194
     */
195
    protected $_driver;
196
197
    /**
198
     * Flag that indicates whether the current transaction is marked for rollback only.
199
     *
200
     * @var bool
201
     */
202
    private $_isRollbackOnly = false;
203
204
    /**
205
     * @var int
206
     */
207
    protected $defaultFetchMode = FetchMode::ASSOCIATIVE;
208
209
    /**
210
     * Initializes a new instance of the Connection class.
211
     *
212
     * @param array                              $params       The connection parameters.
213
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
214
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
215
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
216
     *
217
     * @throws \Doctrine\DBAL\DBALException
218
     */
219
    public function __construct(array $params, Driver $driver, Configuration $config = null,
220
            EventManager $eventManager = null)
221
    {
222
        $this->_driver = $driver;
223
        $this->_params = $params;
224
225
        if (isset($params['pdo'])) {
226
            $this->_conn = $params['pdo'];
227
            $this->_isConnected = true;
228
            unset($this->_params['pdo']);
229
        }
230
231
        if (isset($params["platform"])) {
232
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
233
                throw DBALException::invalidPlatformType($params['platform']);
234
            }
235
236
            $this->platform = $params["platform"];
237
            unset($this->_params["platform"]);
238
        }
239
240
        // Create default config and event manager if none given
241
        if ( ! $config) {
242
            $config = new Configuration();
243
        }
244
245
        if ( ! $eventManager) {
246
            $eventManager = new EventManager();
247
        }
248
249
        $this->_config = $config;
250
        $this->_eventManager = $eventManager;
251
252
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
253
254
        $this->autoCommit = $config->getAutoCommit();
255
    }
256
257
    /**
258
     * Gets the parameters used during instantiation.
259
     *
260
     * @return array
261
     */
262
    public function getParams()
263
    {
264
        return $this->_params;
265
    }
266
267
    /**
268
     * Gets the name of the database this Connection is connected to.
269
     *
270
     * @return string
271
     */
272
    public function getDatabase()
273
    {
274
        return $this->_driver->getDatabase($this);
275
    }
276
277
    /**
278
     * Gets the hostname of the currently connected database.
279
     *
280
     * @return string|null
281
     */
282
    public function getHost()
283
    {
284
        return $this->_params['host'] ?? null;
285
    }
286
287
    /**
288
     * Gets the port of the currently connected database.
289
     *
290
     * @return mixed
291
     */
292
    public function getPort()
293
    {
294
        return $this->_params['port'] ?? null;
295
    }
296
297
    /**
298
     * Gets the username used by this connection.
299
     *
300
     * @return string|null
301
     */
302
    public function getUsername()
303
    {
304
        return $this->_params['user'] ?? null;
305
    }
306
307
    /**
308
     * Gets the password used by this connection.
309
     *
310
     * @return string|null
311
     */
312
    public function getPassword()
313
    {
314
        return $this->_params['password'] ?? null;
315
    }
316
317
    /**
318
     * Gets the DBAL driver instance.
319
     *
320
     * @return \Doctrine\DBAL\Driver
321
     */
322
    public function getDriver()
323
    {
324
        return $this->_driver;
325
    }
326
327
    /**
328
     * Gets the Configuration used by the Connection.
329
     *
330
     * @return \Doctrine\DBAL\Configuration
331
     */
332
    public function getConfiguration()
333
    {
334
        return $this->_config;
335
    }
336
337
    /**
338
     * Gets the EventManager used by the Connection.
339
     *
340
     * @return \Doctrine\Common\EventManager
341
     */
342
    public function getEventManager()
343
    {
344
        return $this->_eventManager;
345
    }
346
347
    /**
348
     * Gets the DatabasePlatform for the connection.
349
     *
350
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
351
     *
352
     * @throws \Doctrine\DBAL\DBALException
353
     */
354
    public function getDatabasePlatform()
355
    {
356
        if (null === $this->platform) {
357
            $this->detectDatabasePlatform();
358
        }
359
360
        return $this->platform;
361
    }
362
363
    /**
364
     * Gets the ExpressionBuilder for the connection.
365
     *
366
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
367
     */
368
    public function getExpressionBuilder()
369
    {
370
        return $this->_expr;
371
    }
372
373
    /**
374
     * Establishes the connection with the database.
375
     *
376
     * @return bool TRUE if the connection was successfully established, FALSE if
377
     *              the connection is already open.
378
     */
379
    public function connect()
380
    {
381
        if ($this->_isConnected) {
382
            return false;
383
        }
384
385
        $driverOptions = $this->_params['driverOptions'] ?? [];
386
        $user = $this->_params['user'] ?? null;
387
        $password = $this->_params['password'] ?? null;
388
389
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
390
        $this->_isConnected = true;
391
392
        if (false === $this->autoCommit) {
393
            $this->beginTransaction();
394
        }
395
396
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
397
            $eventArgs = new Event\ConnectionEventArgs($this);
398
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
399
        }
400
401
        return true;
402
    }
403
404
    /**
405
     * Detects and sets the database platform.
406
     *
407
     * Evaluates custom platform class and version in order to set the correct platform.
408
     *
409
     * @throws DBALException if an invalid platform was specified for this connection.
410
     */
411
    private function detectDatabasePlatform()
412
    {
413
        $version = $this->getDatabasePlatformVersion();
414
415
        if ($version !== null) {
416
            assert($this->_driver instanceof VersionAwarePlatformDriver);
417
418
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
419
        } else {
420
            $this->platform = $this->_driver->getDatabasePlatform();
421
        }
422
423
        $this->platform->setEventManager($this->_eventManager);
424
    }
425
426
    /**
427
     * Returns the version of the related platform if applicable.
428
     *
429
     * Returns null if either the driver is not capable to create version
430
     * specific platform instances, no explicit server version was specified
431
     * or the underlying driver connection cannot determine the platform
432
     * version without having to query it (performance reasons).
433
     *
434
     * @return string|null
435
     *
436
     * @throws Exception
437
     */
438
    private function getDatabasePlatformVersion()
439
    {
440
        // Driver does not support version specific platforms.
441
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
442
            return null;
443
        }
444
445
        // Explicit platform version requested (supersedes auto-detection).
446
        if (isset($this->_params['serverVersion'])) {
447
            return $this->_params['serverVersion'];
448
        }
449
450
        // If not connected, we need to connect now to determine the platform version.
451
        if (null === $this->_conn) {
452
            try {
453
                $this->connect();
454
            } catch (\Exception $originalException) {
455
                if (empty($this->_params['dbname'])) {
456
                    throw $originalException;
457
                }
458
459
                // The database to connect to might not yet exist.
460
                // Retry detection without database name connection parameter.
461
                $databaseName = $this->_params['dbname'];
462
                $this->_params['dbname'] = null;
463
464
                try {
465
                    $this->connect();
466
                } catch (\Exception $fallbackException) {
467
                    // Either the platform does not support database-less connections
468
                    // or something else went wrong.
469
                    // Reset connection parameters and rethrow the original exception.
470
                    $this->_params['dbname'] = $databaseName;
471
472
                    throw $originalException;
473
                }
474
475
                // Reset connection parameters.
476
                $this->_params['dbname'] = $databaseName;
477
                $serverVersion = $this->getServerVersion();
478
479
                // Close "temporary" connection to allow connecting to the real database again.
480
                $this->close();
481
482
                return $serverVersion;
483
            }
484
485
        }
486
487
        return $this->getServerVersion();
488
    }
489
490
    /**
491
     * Returns the database server version if the underlying driver supports it.
492
     *
493
     * @return string|null
494
     */
495
    private function getServerVersion()
496
    {
497
        // Automatic platform version detection.
498
        if ($this->_conn instanceof ServerInfoAwareConnection &&
499
            ! $this->_conn->requiresQueryForServerVersion()
500
        ) {
501
            return $this->_conn->getServerVersion();
502
        }
503
504
        // Unable to detect platform version.
505
        return null;
506
    }
507
508
    /**
509
     * Returns the current auto-commit mode for this connection.
510
     *
511
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
512
     *
513
     * @see    setAutoCommit
514
     */
515
    public function isAutoCommit()
516
    {
517
        return true === $this->autoCommit;
518
    }
519
520
    /**
521
     * Sets auto-commit mode for this connection.
522
     *
523
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
524
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
525
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
526
     *
527
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
528
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
529
     *
530
     * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
531
     *
532
     * @see   isAutoCommit
533
     */
534
    public function setAutoCommit($autoCommit)
535
    {
536
        $autoCommit = (boolean) $autoCommit;
537
538
        // Mode not changed, no-op.
539
        if ($autoCommit === $this->autoCommit) {
540
            return;
541
        }
542
543
        $this->autoCommit = $autoCommit;
544
545
        // Commit all currently active transactions if any when switching auto-commit mode.
546
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
547
            $this->commitAll();
548
        }
549
    }
550
551
    /**
552
     * Sets the fetch mode.
553
     *
554
     * @param int $fetchMode
555
     *
556
     * @return void
557
     */
558
    public function setFetchMode($fetchMode)
559
    {
560
        $this->defaultFetchMode = $fetchMode;
561
    }
562
563
    /**
564
     * Prepares and executes an SQL query and returns the first row of the result
565
     * as an associative array.
566
     *
567
     * @param string $statement The SQL query.
568
     * @param array  $params    The query parameters.
569
     * @param array  $types     The query parameter types.
570
     *
571
     * @return array|bool False is returned if no rows are found.
572
     *
573
     * @throws \Doctrine\DBAL\DBALException
574
     */
575
    public function fetchAssoc($statement, array $params = [], array $types = [])
576
    {
577
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::ASSOCIATIVE);
578
    }
579
580
    /**
581
     * Prepares and executes an SQL query and returns the first row of the result
582
     * as a numerically indexed array.
583
     *
584
     * @param string $statement The SQL query to be executed.
585
     * @param array  $params    The prepared statement params.
586
     * @param array  $types     The query parameter types.
587
     *
588
     * @return array|bool False is returned if no rows are found.
589
     */
590
    public function fetchArray($statement, array $params = [], array $types = [])
591
    {
592
        return $this->executeQuery($statement, $params, $types)->fetch(FetchMode::NUMERIC);
593
    }
594
595
    /**
596
     * Prepares and executes an SQL query and returns the value of a single column
597
     * of the first row of the result.
598
     *
599
     * @param string $statement The SQL query to be executed.
600
     * @param array  $params    The prepared statement params.
601
     * @param int    $column    The 0-indexed column number to retrieve.
602
     * @param array  $types     The query parameter types.
603
     *
604
     * @return mixed|bool False is returned if no rows are found.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use string|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
605
     *
606
     * @throws \Doctrine\DBAL\DBALException
607
     */
608
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
609
    {
610
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
611
    }
612
613
    /**
614
     * Whether an actual connection to the database is established.
615
     *
616
     * @return bool
617
     */
618
    public function isConnected()
619
    {
620
        return $this->_isConnected;
621
    }
622
623
    /**
624
     * Checks whether a transaction is currently active.
625
     *
626
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
627
     */
628
    public function isTransactionActive()
629
    {
630
        return $this->_transactionNestingLevel > 0;
631
    }
632
633
    /**
634
     * Gathers conditions for an update or delete call.
635
     *
636
     * @param array $identifiers Input array of columns to values
637
     *
638
     * @return string[][] a triplet with:
0 ignored issues
show
Should the return type not be array[]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
639
     *                    - the first key being the columns
640
     *                    - the second key being the values
641
     *                    - the third key being the conditions
642
     */
643
    private function gatherConditions(array $identifiers)
644
    {
645
        $columns = [];
646
        $values = [];
647
        $conditions = [];
648
649
        foreach ($identifiers as $columnName => $value) {
650
            if (null === $value) {
651
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
652
                continue;
653
            }
654
655
            $columns[] = $columnName;
656
            $values[] = $value;
657
            $conditions[] = $columnName . ' = ?';
658
        }
659
660
        return [$columns, $values, $conditions];
661
    }
662
663
    /**
664
     * Executes an SQL DELETE statement on a table.
665
     *
666
     * Table expression and columns are not escaped and are not safe for user-input.
667
     *
668
     * @param string $tableExpression The expression of the table on which to delete.
669
     * @param array  $identifier      The deletion criteria. An associative array containing column-value pairs.
670
     * @param array  $types           The types of identifiers.
671
     *
672
     * @return int The number of affected rows.
673
     *
674
     * @throws \Doctrine\DBAL\DBALException
675
     * @throws InvalidArgumentException
676
     */
677
    public function delete($tableExpression, array $identifier, array $types = [])
678
    {
679
        if (empty($identifier)) {
680
            throw InvalidArgumentException::fromEmptyCriteria();
681
        }
682
683
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
684
685
        return $this->executeUpdate(
686
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
0 ignored issues
show
'DELETE FROM ' . $tableE...e(' AND ', $conditions) is used as a query on line 685. If $tableExpression can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $tableExpression).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
687
            $values,
688
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
689
        );
690
    }
691
692
    /**
693
     * Closes the connection.
694
     *
695
     * @return void
696
     */
697
    public function close()
698
    {
699
        $this->_conn = null;
700
701
        $this->_isConnected = false;
702
    }
703
704
    /**
705
     * Sets the transaction isolation level.
706
     *
707
     * @param int $level The level to set.
708
     *
709
     * @return int
710
     */
711
    public function setTransactionIsolation($level)
712
    {
713
        $this->_transactionIsolationLevel = $level;
714
715
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
716
    }
717
718
    /**
719
     * Gets the currently active transaction isolation level.
720
     *
721
     * @return int The current transaction isolation level.
722
     */
723
    public function getTransactionIsolation()
724
    {
725
        if (null === $this->_transactionIsolationLevel) {
726
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
727
        }
728
729
        return $this->_transactionIsolationLevel;
730
    }
731
732
    /**
733
     * Executes an SQL UPDATE statement on a table.
734
     *
735
     * Table expression and columns are not escaped and are not safe for user-input.
736
     *
737
     * @param string $tableExpression The expression of the table to update quoted or unquoted.
738
     * @param array  $data            An associative array containing column-value pairs.
739
     * @param array  $identifier      The update criteria. An associative array containing column-value pairs.
740
     * @param array  $types           Types of the merged $data and $identifier arrays in that order.
741
     *
742
     * @return int The number of affected rows.
743
     *
744
     * @throws \Doctrine\DBAL\DBALException
745
     */
746
    public function update($tableExpression, array $data, array $identifier, array $types = [])
747
    {
748
        $setColumns = [];
749
        $setValues = [];
750
        $set = [];
751
752
        foreach ($data as $columnName => $value) {
753
            $setColumns[] = $columnName;
754
            $setValues[] = $value;
755
            $set[] = $columnName . ' = ?';
756
        }
757
758
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
759
        $columns = array_merge($setColumns, $conditionColumns);
760
        $values = array_merge($setValues, $conditionValues);
761
762
        if (is_string(key($types))) {
763
            $types = $this->extractTypeValues($columns, $types);
764
        }
765
766
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
0 ignored issues
show
'UPDATE ' . $tableExpres...e(' AND ', $conditions) is used as a query on line 769. If $tableExpression can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $tableExpression).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
767
                . ' WHERE ' . implode(' AND ', $conditions);
768
769
        return $this->executeUpdate($sql, $values, $types);
770
    }
771
772
    /**
773
     * Inserts a table row with specified data.
774
     *
775
     * Table expression and columns are not escaped and are not safe for user-input.
776
     *
777
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
778
     * @param array  $data            An associative array containing column-value pairs.
779
     * @param array  $types           Types of the inserted data.
780
     *
781
     * @return int The number of affected rows.
782
     *
783
     * @throws \Doctrine\DBAL\DBALException
784
     */
785
    public function insert($tableExpression, array $data, array $types = [])
786
    {
787
        if (empty($data)) {
788
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
0 ignored issues
show
If $tableExpression can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $tableExpression).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
789
        }
790
791
        $columns = [];
792
        $values = [];
793
        $set = [];
794
795
        foreach ($data as $columnName => $value) {
796
            $columns[] = $columnName;
797
            $values[] = $value;
798
            $set[] = '?';
799
        }
800
801
        return $this->executeUpdate(
802
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
0 ignored issues
show
'INSERT INTO ' . $tableE...plode(', ', $set) . ')' is used as a query on line 801. If $tableExpression can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $tableExpression).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
803
            ' VALUES (' . implode(', ', $set) . ')',
804
            $values,
805
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
806
        );
807
    }
808
809
    /**
810
     * Extract ordered type list from an ordered column list and type map.
811
     *
812
     * @param array $columnList
813
     * @param array $types
814
     *
815
     * @return array
816
     */
817
    private function extractTypeValues(array $columnList, array $types)
818
    {
819
        $typeValues = [];
820
821
        foreach ($columnList as $columnIndex => $columnName) {
822
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
823
        }
824
825
        return $typeValues;
826
    }
827
828
    /**
829
     * Quotes a string so it can be safely used as a table or column name, even if
830
     * it is a reserved name.
831
     *
832
     * Delimiting style depends on the underlying database platform that is being used.
833
     *
834
     * NOTE: Just because you CAN use quoted identifiers does not mean
835
     * you SHOULD use them. In general, they end up causing way more
836
     * problems than they solve.
837
     *
838
     * @param string $str The name to be quoted.
839
     *
840
     * @return string The quoted name.
841
     */
842
    public function quoteIdentifier($str)
843
    {
844
        return $this->getDatabasePlatform()->quoteIdentifier($str);
845
    }
846
847
    /**
848
     * Quotes a given input parameter.
849
     *
850
     * @param mixed    $input The parameter to be quoted.
851
     * @param int|null $type  The type of the parameter.
852
     *
853
     * @return string The quoted parameter.
854
     */
855
    public function quote($input, $type = null)
856
    {
857
        $this->connect();
858
859
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
860
861
        return $this->_conn->quote($value, $bindingType);
862
    }
863
864
    /**
865
     * Prepares and executes an SQL query and returns the result as an associative array.
866
     *
867
     * @param string $sql    The SQL query.
868
     * @param array  $params The query parameters.
869
     * @param array  $types  The query parameter types.
870
     *
871
     * @return array
872
     */
873
    public function fetchAll($sql, array $params = [], $types = [])
874
    {
875
        return $this->executeQuery($sql, $params, $types)->fetchAll();
876
    }
877
878
    /**
879
     * Prepares an SQL statement.
880
     *
881
     * @param string $statement The SQL statement to prepare.
882
     *
883
     * @return DriverStatement The prepared statement.
884
     *
885
     * @throws \Doctrine\DBAL\DBALException
886
     */
887
    public function prepare($statement)
888
    {
889
        try {
890
            $stmt = new Statement($statement, $this);
891
        } catch (Exception $ex) {
892
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
893
        }
894
895
        $stmt->setFetchMode($this->defaultFetchMode);
896
897
        return $stmt;
898
    }
899
900
    /**
901
     * Executes an, optionally parametrized, SQL query.
902
     *
903
     * If the query is parametrized, a prepared statement is used.
904
     * If an SQLLogger is configured, the execution is logged.
905
     *
906
     * @param string                                      $query  The SQL query to execute.
907
     * @param array                                       $params The parameters to bind to the query, if any.
908
     * @param array                                       $types  The types the previous parameters are in.
909
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
910
     *
911
     * @return ResultStatement The executed statement.
912
     *
913
     * @throws \Doctrine\DBAL\DBALException
914
     */
915
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
916
    {
917
        if ($qcp !== null) {
918
            return $this->executeCacheQuery($query, $params, $types, $qcp);
919
        }
920
921
        $this->connect();
922
923
        $logger = $this->_config->getSQLLogger();
924
        if ($logger) {
925
            $logger->startQuery($query, $params, $types);
926
        }
927
928
        try {
929
            if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
930
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
931
932
                $stmt = $this->_conn->prepare($query);
933
                if ($types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
934
                    $this->_bindTypedValues($stmt, $params, $types);
935
                    $stmt->execute();
936
                } else {
937
                    $stmt->execute($params);
938
                }
939
            } else {
940
                $stmt = $this->_conn->query($query);
0 ignored issues
show
The call to Connection::query() has too many arguments starting with $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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
941
            }
942
        } catch (Exception $ex) {
943
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
944
        }
945
946
        $stmt->setFetchMode($this->defaultFetchMode);
947
948
        if ($logger) {
949
            $logger->stopQuery();
950
        }
951
952
        return $stmt;
953
    }
954
955
    /**
956
     * Executes a caching query.
957
     *
958
     * @param string                                 $query  The SQL query to execute.
959
     * @param array                                  $params The parameters to bind to the query, if any.
960
     * @param array                                  $types  The types the previous parameters are in.
961
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
962
     *
963
     * @return ResultStatement
964
     *
965
     * @throws \Doctrine\DBAL\Cache\CacheException
966
     */
967
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
968
    {
969
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
970
        if ( ! $resultCache) {
971
            throw CacheException::noResultDriverConfigured();
972
        }
973
974
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
975
976
        // fetch the row pointers entry
977
        if ($data = $resultCache->fetch($cacheKey)) {
978
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
979
            if (isset($data[$realKey])) {
980
                $stmt = new ArrayStatement($data[$realKey]);
981
            } elseif (array_key_exists($realKey, $data)) {
982
                $stmt = new ArrayStatement([]);
983
            }
984
        }
985
986
        if (!isset($stmt)) {
987
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
0 ignored issues
show
$this->executeQuery($query, $params, $types) of type object<Doctrine\DBAL\Driver\ResultStatement> is not a sub-type of object<Doctrine\DBAL\Driver\Statement>. It seems like you assume a child interface of the interface Doctrine\DBAL\Driver\ResultStatement to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
988
        }
989
990
        $stmt->setFetchMode($this->defaultFetchMode);
991
992
        return $stmt;
993
    }
994
995
    /**
996
     * Executes an, optionally parametrized, SQL query and returns the result,
997
     * applying a given projection/transformation function on each row of the result.
998
     *
999
     * @param string   $query    The SQL query to execute.
1000
     * @param array    $params   The parameters, if any.
1001
     * @param \Closure $function The transformation function that is applied on each row.
1002
     *                           The function receives a single parameter, an array, that
1003
     *                           represents a row of the result set.
1004
     *
1005
     * @return array The projected result of the query.
1006
     */
1007
    public function project($query, array $params, Closure $function)
1008
    {
1009
        $result = [];
1010
        $stmt = $this->executeQuery($query, $params);
1011
1012
        while ($row = $stmt->fetch()) {
1013
            $result[] = $function($row);
1014
        }
1015
1016
        $stmt->closeCursor();
1017
1018
        return $result;
1019
    }
1020
1021
    /**
1022
     * Executes an SQL statement, returning a result set as a Statement object.
1023
     *
1024
     * @return \Doctrine\DBAL\Driver\Statement
1025
     *
1026
     * @throws \Doctrine\DBAL\DBALException
1027
     */
1028
    public function query()
1029
    {
1030
        $this->connect();
1031
1032
        $args = func_get_args();
1033
1034
        $logger = $this->_config->getSQLLogger();
1035
        if ($logger) {
1036
            $logger->startQuery($args[0]);
1037
        }
1038
1039
        try {
1040
            $statement = $this->_conn->query(...$args);
0 ignored issues
show
The call to Connection::query() has too many arguments starting with $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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1041
        } catch (Exception $ex) {
1042
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1043
        }
1044
1045
        $statement->setFetchMode($this->defaultFetchMode);
1046
1047
        if ($logger) {
1048
            $logger->stopQuery();
1049
        }
1050
1051
        return $statement;
1052
    }
1053
1054
    /**
1055
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1056
     * and returns the number of affected rows.
1057
     *
1058
     * This method supports PDO binding types as well as DBAL mapping types.
1059
     *
1060
     * @param string $query  The SQL query.
1061
     * @param array  $params The query parameters.
1062
     * @param array  $types  The parameter types.
1063
     *
1064
     * @return int The number of affected rows.
1065
     *
1066
     * @throws \Doctrine\DBAL\DBALException
1067
     */
1068
    public function executeUpdate($query, array $params = [], array $types = [])
1069
    {
1070
        $this->connect();
1071
1072
        $logger = $this->_config->getSQLLogger();
1073
        if ($logger) {
1074
            $logger->startQuery($query, $params, $types);
1075
        }
1076
1077
        try {
1078
            if ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1079
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1080
1081
                $stmt = $this->_conn->prepare($query);
1082
                if ($types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1083
                    $this->_bindTypedValues($stmt, $params, $types);
1084
                    $stmt->execute();
1085
                } else {
1086
                    $stmt->execute($params);
1087
                }
1088
                $result = $stmt->rowCount();
1089
            } else {
1090
                $result = $this->_conn->exec($query);
1091
            }
1092
        } catch (Exception $ex) {
1093
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1094
        }
1095
1096
        if ($logger) {
1097
            $logger->stopQuery();
1098
        }
1099
1100
        return $result;
1101
    }
1102
1103
    /**
1104
     * Executes an SQL statement and return the number of affected rows.
1105
     *
1106
     * @param string $statement
1107
     *
1108
     * @return int The number of affected rows.
1109
     *
1110
     * @throws \Doctrine\DBAL\DBALException
1111
     */
1112
    public function exec($statement)
1113
    {
1114
        $this->connect();
1115
1116
        $logger = $this->_config->getSQLLogger();
1117
        if ($logger) {
1118
            $logger->startQuery($statement);
1119
        }
1120
1121
        try {
1122
            $result = $this->_conn->exec($statement);
1123
        } catch (Exception $ex) {
1124
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1125
        }
1126
1127
        if ($logger) {
1128
            $logger->stopQuery();
1129
        }
1130
1131
        return $result;
1132
    }
1133
1134
    /**
1135
     * Returns the current transaction nesting level.
1136
     *
1137
     * @return int The nesting level. A value of 0 means there's no active transaction.
1138
     */
1139
    public function getTransactionNestingLevel()
1140
    {
1141
        return $this->_transactionNestingLevel;
1142
    }
1143
1144
    /**
1145
     * Fetches the SQLSTATE associated with the last database operation.
1146
     *
1147
     * @return string|null The last error code.
1148
     */
1149
    public function errorCode()
1150
    {
1151
        $this->connect();
1152
1153
        return $this->_conn->errorCode();
1154
    }
1155
1156
    /**
1157
     * Fetches extended error information associated with the last database operation.
1158
     *
1159
     * @return array The last error information.
1160
     */
1161
    public function errorInfo()
1162
    {
1163
        $this->connect();
1164
1165
        return $this->_conn->errorInfo();
1166
    }
1167
1168
    /**
1169
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1170
     * depending on the underlying driver.
1171
     *
1172
     * Note: This method may not return a meaningful or consistent result across different drivers,
1173
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1174
     * columns or sequences.
1175
     *
1176
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
1177
     *
1178
     * @return string A string representation of the last inserted ID.
1179
     */
1180
    public function lastInsertId($seqName = null)
1181
    {
1182
        $this->connect();
1183
1184
        return $this->_conn->lastInsertId($seqName);
1185
    }
1186
1187
    /**
1188
     * Executes a function in a transaction.
1189
     *
1190
     * The function gets passed this Connection instance as an (optional) parameter.
1191
     *
1192
     * If an exception occurs during execution of the function or transaction commit,
1193
     * the transaction is rolled back and the exception re-thrown.
1194
     *
1195
     * @param \Closure $func The function to execute transactionally.
1196
     *
1197
     * @return mixed The value returned by $func
1198
     *
1199
     * @throws Exception
1200
     * @throws Throwable
1201
     */
1202
    public function transactional(Closure $func)
1203
    {
1204
        $this->beginTransaction();
1205
        try {
1206
            $res = $func($this);
1207
            $this->commit();
1208
            return $res;
1209
        } catch (Exception $e) {
1210
            $this->rollBack();
1211
            throw $e;
1212
        } catch (Throwable $e) {
1213
            $this->rollBack();
1214
            throw $e;
1215
        }
1216
    }
1217
1218
    /**
1219
     * Sets if nested transactions should use savepoints.
1220
     *
1221
     * @param bool $nestTransactionsWithSavepoints
1222
     *
1223
     * @return void
1224
     *
1225
     * @throws \Doctrine\DBAL\ConnectionException
1226
     */
1227
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1228
    {
1229
        if ($this->_transactionNestingLevel > 0) {
1230
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1231
        }
1232
1233
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1234
            throw ConnectionException::savepointsNotSupported();
1235
        }
1236
1237
        $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1238
    }
1239
1240
    /**
1241
     * Gets if nested transactions should use savepoints.
1242
     *
1243
     * @return bool
1244
     */
1245
    public function getNestTransactionsWithSavepoints()
1246
    {
1247
        return $this->_nestTransactionsWithSavepoints;
1248
    }
1249
1250
    /**
1251
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1252
     * "savepointFormat" parameter is not set
1253
     *
1254
     * @return mixed A string with the savepoint name or false.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1255
     */
1256
    protected function _getNestedTransactionSavePointName()
1257
    {
1258
        return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
1259
    }
1260
1261
    /**
1262
     * Starts a transaction by suspending auto-commit mode.
1263
     *
1264
     * @return void
1265
     */
1266
    public function beginTransaction()
1267
    {
1268
        $this->connect();
1269
1270
        ++$this->_transactionNestingLevel;
1271
1272
        $logger = $this->_config->getSQLLogger();
1273
1274
        if ($this->_transactionNestingLevel == 1) {
1275
            if ($logger) {
1276
                $logger->startQuery('"START TRANSACTION"');
1277
            }
1278
            $this->_conn->beginTransaction();
1279
            if ($logger) {
1280
                $logger->stopQuery();
1281
            }
1282
        } elseif ($this->_nestTransactionsWithSavepoints) {
1283
            if ($logger) {
1284
                $logger->startQuery('"SAVEPOINT"');
1285
            }
1286
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1287
            if ($logger) {
1288
                $logger->stopQuery();
1289
            }
1290
        }
1291
    }
1292
1293
    /**
1294
     * Commits the current transaction.
1295
     *
1296
     * @return void
1297
     *
1298
     * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
1299
     *                                            because the transaction was marked for rollback only.
1300
     */
1301
    public function commit()
1302
    {
1303
        if ($this->_transactionNestingLevel == 0) {
1304
            throw ConnectionException::noActiveTransaction();
1305
        }
1306
        if ($this->_isRollbackOnly) {
1307
            throw ConnectionException::commitFailedRollbackOnly();
1308
        }
1309
1310
        $this->connect();
1311
1312
        $logger = $this->_config->getSQLLogger();
1313
1314
        if ($this->_transactionNestingLevel == 1) {
1315
            if ($logger) {
1316
                $logger->startQuery('"COMMIT"');
1317
            }
1318
            $this->_conn->commit();
1319
            if ($logger) {
1320
                $logger->stopQuery();
1321
            }
1322
        } elseif ($this->_nestTransactionsWithSavepoints) {
1323
            if ($logger) {
1324
                $logger->startQuery('"RELEASE SAVEPOINT"');
1325
            }
1326
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1327
            if ($logger) {
1328
                $logger->stopQuery();
1329
            }
1330
        }
1331
1332
        --$this->_transactionNestingLevel;
1333
1334
        if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) {
1335
            $this->beginTransaction();
1336
        }
1337
    }
1338
1339
    /**
1340
     * Commits all current nesting transactions.
1341
     */
1342
    private function commitAll()
1343
    {
1344
        while (0 !== $this->_transactionNestingLevel) {
1345
            if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) {
1346
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1347
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1348
                $this->commit();
1349
1350
                return;
1351
            }
1352
1353
            $this->commit();
1354
        }
1355
    }
1356
1357
    /**
1358
     * Cancels any database changes done during the current transaction.
1359
     *
1360
     * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
1361
     */
1362
    public function rollBack()
1363
    {
1364
        if ($this->_transactionNestingLevel == 0) {
1365
            throw ConnectionException::noActiveTransaction();
1366
        }
1367
1368
        $this->connect();
1369
1370
        $logger = $this->_config->getSQLLogger();
1371
1372
        if ($this->_transactionNestingLevel == 1) {
1373
            if ($logger) {
1374
                $logger->startQuery('"ROLLBACK"');
1375
            }
1376
            $this->_transactionNestingLevel = 0;
1377
            $this->_conn->rollBack();
1378
            $this->_isRollbackOnly = false;
1379
            if ($logger) {
1380
                $logger->stopQuery();
1381
            }
1382
1383
            if (false === $this->autoCommit) {
1384
                $this->beginTransaction();
1385
            }
1386
        } elseif ($this->_nestTransactionsWithSavepoints) {
1387
            if ($logger) {
1388
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1389
            }
1390
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1391
            --$this->_transactionNestingLevel;
1392
            if ($logger) {
1393
                $logger->stopQuery();
1394
            }
1395
        } else {
1396
            $this->_isRollbackOnly = true;
1397
            --$this->_transactionNestingLevel;
1398
        }
1399
    }
1400
1401
    /**
1402
     * Creates a new savepoint.
1403
     *
1404
     * @param string $savepoint The name of the savepoint to create.
1405
     *
1406
     * @return void
1407
     *
1408
     * @throws \Doctrine\DBAL\ConnectionException
1409
     */
1410
    public function createSavepoint($savepoint)
1411
    {
1412
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1413
            throw ConnectionException::savepointsNotSupported();
1414
        }
1415
1416
        $this->_conn->exec($this->platform->createSavePoint($savepoint));
1417
    }
1418
1419
    /**
1420
     * Releases the given savepoint.
1421
     *
1422
     * @param string $savepoint The name of the savepoint to release.
1423
     *
1424
     * @return void
1425
     *
1426
     * @throws \Doctrine\DBAL\ConnectionException
1427
     */
1428
    public function releaseSavepoint($savepoint)
1429
    {
1430
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1431
            throw ConnectionException::savepointsNotSupported();
1432
        }
1433
1434
        if ($this->platform->supportsReleaseSavepoints()) {
1435
            $this->_conn->exec($this->platform->releaseSavePoint($savepoint));
1436
        }
1437
    }
1438
1439
    /**
1440
     * Rolls back to the given savepoint.
1441
     *
1442
     * @param string $savepoint The name of the savepoint to rollback to.
1443
     *
1444
     * @return void
1445
     *
1446
     * @throws \Doctrine\DBAL\ConnectionException
1447
     */
1448
    public function rollbackSavepoint($savepoint)
1449
    {
1450
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1451
            throw ConnectionException::savepointsNotSupported();
1452
        }
1453
1454
        $this->_conn->exec($this->platform->rollbackSavePoint($savepoint));
1455
    }
1456
1457
    /**
1458
     * Gets the wrapped driver connection.
1459
     *
1460
     * @return \Doctrine\DBAL\Driver\Connection
0 ignored issues
show
Should the return type not be DriverConnection|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1461
     */
1462
    public function getWrappedConnection()
1463
    {
1464
        $this->connect();
1465
1466
        return $this->_conn;
1467
    }
1468
1469
    /**
1470
     * Gets the SchemaManager that can be used to inspect or change the
1471
     * database schema through the connection.
1472
     *
1473
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
1474
     */
1475
    public function getSchemaManager()
1476
    {
1477
        if ( ! $this->_schemaManager) {
1478
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1479
        }
1480
1481
        return $this->_schemaManager;
1482
    }
1483
1484
    /**
1485
     * Marks the current transaction so that the only possible
1486
     * outcome for the transaction to be rolled back.
1487
     *
1488
     * @return void
1489
     *
1490
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1491
     */
1492
    public function setRollbackOnly()
1493
    {
1494
        if ($this->_transactionNestingLevel == 0) {
1495
            throw ConnectionException::noActiveTransaction();
1496
        }
1497
        $this->_isRollbackOnly = true;
1498
    }
1499
1500
    /**
1501
     * Checks whether the current transaction is marked for rollback only.
1502
     *
1503
     * @return bool
1504
     *
1505
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1506
     */
1507
    public function isRollbackOnly()
1508
    {
1509
        if ($this->_transactionNestingLevel == 0) {
1510
            throw ConnectionException::noActiveTransaction();
1511
        }
1512
1513
        return $this->_isRollbackOnly;
1514
    }
1515
1516
    /**
1517
     * Converts a given value to its database representation according to the conversion
1518
     * rules of a specific DBAL mapping type.
1519
     *
1520
     * @param mixed  $value The value to convert.
1521
     * @param string $type  The name of the DBAL mapping type.
1522
     *
1523
     * @return mixed The converted value.
1524
     */
1525
    public function convertToDatabaseValue($value, $type)
1526
    {
1527
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1528
    }
1529
1530
    /**
1531
     * Converts a given value to its PHP representation according to the conversion
1532
     * rules of a specific DBAL mapping type.
1533
     *
1534
     * @param mixed  $value The value to convert.
1535
     * @param string $type  The name of the DBAL mapping type.
1536
     *
1537
     * @return mixed The converted type.
1538
     */
1539
    public function convertToPHPValue($value, $type)
1540
    {
1541
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1542
    }
1543
1544
    /**
1545
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1546
     * or DBAL mapping type, to a given statement.
1547
     *
1548
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1549
     * @param array                           $params The map/list of named/positional parameters.
1550
     * @param array                           $types  The parameter types (PDO binding types or DBAL mapping types).
1551
     *
1552
     * @return void
1553
     *
1554
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1555
     *           raw PDOStatement instances.
1556
     */
1557
    private function _bindTypedValues($stmt, array $params, array $types)
1558
    {
1559
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1560
        if (is_int(key($params))) {
1561
            // Positional parameters
1562
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1563
            $bindIndex = 1;
1564
            foreach ($params as $value) {
1565
                $typeIndex = $bindIndex + $typeOffset;
1566
                if (isset($types[$typeIndex])) {
1567
                    $type = $types[$typeIndex];
1568
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1569
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1570
                } else {
1571
                    $stmt->bindValue($bindIndex, $value);
1572
                }
1573
                ++$bindIndex;
1574
            }
1575
        } else {
1576
            // Named parameters
1577
            foreach ($params as $name => $value) {
1578
                if (isset($types[$name])) {
1579
                    $type = $types[$name];
1580
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1581
                    $stmt->bindValue($name, $value, $bindingType);
1582
                } else {
1583
                    $stmt->bindValue($name, $value);
1584
                }
1585
            }
1586
        }
1587
    }
1588
1589
    /**
1590
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1591
     *
1592
     * @param mixed $value The value to bind.
1593
     * @param mixed $type  The type to bind (PDO or DBAL).
1594
     *
1595
     * @return array [0] => the (escaped) value, [1] => the binding type.
1596
     */
1597
    private function getBindingInfo($value, $type)
1598
    {
1599
        if (is_string($type)) {
1600
            $type = Type::getType($type);
1601
        }
1602
        if ($type instanceof Type) {
1603
            $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1604
            $bindingType = $type->getBindingType();
1605
        } else {
1606
            $bindingType = $type;
1607
        }
1608
1609
        return [$value, $bindingType];
1610
    }
1611
1612
    /**
1613
     * Resolves the parameters to a format which can be displayed.
1614
     *
1615
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1616
     *           copy/paste the code as this method may change, or be removed without prior notice.
1617
     *
1618
     * @param array $params
1619
     * @param array $types
1620
     *
1621
     * @return array
1622
     */
1623
    public function resolveParams(array $params, array $types)
1624
    {
1625
        $resolvedParams = [];
1626
1627
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1628
        if (is_int(key($params))) {
1629
            // Positional parameters
1630
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1631
            $bindIndex = 1;
1632
            foreach ($params as $value) {
1633
                $typeIndex = $bindIndex + $typeOffset;
1634
                if (isset($types[$typeIndex])) {
1635
                    $type = $types[$typeIndex];
1636
                    list($value,) = $this->getBindingInfo($value, $type);
1637
                    $resolvedParams[$bindIndex] = $value;
1638
                } else {
1639
                    $resolvedParams[$bindIndex] = $value;
1640
                }
1641
                ++$bindIndex;
1642
            }
1643
        } else {
1644
            // Named parameters
1645
            foreach ($params as $name => $value) {
1646
                if (isset($types[$name])) {
1647
                    $type = $types[$name];
1648
                    list($value,) = $this->getBindingInfo($value, $type);
1649
                    $resolvedParams[$name] = $value;
1650
                } else {
1651
                    $resolvedParams[$name] = $value;
1652
                }
1653
            }
1654
        }
1655
1656
        return $resolvedParams;
1657
    }
1658
1659
    /**
1660
     * Creates a new instance of a SQL query builder.
1661
     *
1662
     * @return \Doctrine\DBAL\Query\QueryBuilder
1663
     */
1664
    public function createQueryBuilder()
1665
    {
1666
        return new Query\QueryBuilder($this);
1667
    }
1668
1669
    /**
1670
     * Ping the server
1671
     *
1672
     * When the server is not available the method returns FALSE.
1673
     * It is responsibility of the developer to handle this case
1674
     * and abort the request or reconnect manually:
1675
     *
1676
     * @example
1677
     *
1678
     *   if ($conn->ping() === false) {
1679
     *      $conn->close();
1680
     *      $conn->connect();
1681
     *   }
1682
     *
1683
     * It is undefined if the underlying driver attempts to reconnect
1684
     * or disconnect when the connection is not available anymore
1685
     * as long it returns TRUE when a reconnect succeeded and
1686
     * FALSE when the connection was dropped.
1687
     *
1688
     * @return bool
1689
     */
1690
    public function ping()
1691
    {
1692
        $this->connect();
1693
1694
        if ($this->_conn instanceof PingableConnection) {
1695
            return $this->_conn->ping();
1696
        }
1697
1698
        try {
1699
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1700
1701
            return true;
1702
        } catch (DBALException $e) {
1703
            return false;
1704
        }
1705
    }
1706
}
1707