Failed Conditions
Push — 2.6 ( 324f3a...367b73 )
by Marco
11:56 queued 06:11
created

Connection::getParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL;
21
22
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
23
use Doctrine\DBAL\Exception\InvalidArgumentException;
24
use PDO;
25
use Closure;
26
use Exception;
27
use Doctrine\DBAL\Types\Type;
28
use Doctrine\DBAL\Driver\Connection as DriverConnection;
29
use Doctrine\Common\EventManager;
30
use Doctrine\DBAL\Cache\ResultCacheStatement;
31
use Doctrine\DBAL\Cache\QueryCacheProfile;
32
use Doctrine\DBAL\Cache\ArrayStatement;
33
use Doctrine\DBAL\Cache\CacheException;
34
use Doctrine\DBAL\Driver\PingableConnection;
35
use Throwable;
36
37
/**
38
 * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
39
 * events, transaction isolation levels, configuration, emulated transaction nesting,
40
 * lazy connecting and more.
41
 *
42
 * @link   www.doctrine-project.org
43
 * @since  2.0
44
 * @author Guilherme Blanco <[email protected]>
45
 * @author Jonathan Wage <[email protected]>
46
 * @author Roman Borschel <[email protected]>
47
 * @author Konsta Vesterinen <[email protected]>
48
 * @author Lukas Smith <[email protected]> (MDB2 library)
49
 * @author Benjamin Eberlei <[email protected]>
50
 */
51
class Connection implements DriverConnection
52
{
53
    /**
54
     * Constant for transaction isolation level READ UNCOMMITTED.
55
     */
56
    const TRANSACTION_READ_UNCOMMITTED = 1;
57
58
    /**
59
     * Constant for transaction isolation level READ COMMITTED.
60
     */
61
    const TRANSACTION_READ_COMMITTED = 2;
62
63
    /**
64
     * Constant for transaction isolation level REPEATABLE READ.
65
     */
66
    const TRANSACTION_REPEATABLE_READ = 3;
67
68
    /**
69
     * Constant for transaction isolation level SERIALIZABLE.
70
     */
71
    const TRANSACTION_SERIALIZABLE = 4;
72
73
    /**
74
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
75
     *
76
     * @var integer
77
     */
78
    const PARAM_INT_ARRAY = 101;
79
80
    /**
81
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
82
     *
83
     * @var integer
84
     */
85
    const PARAM_STR_ARRAY = 102;
86
87
    /**
88
     * Offset by which PARAM_* constants are detected as arrays of the param type.
89
     *
90
     * @var integer
91
     */
92
    const ARRAY_PARAM_OFFSET = 100;
93
94
    /**
95
     * The wrapped driver connection.
96
     *
97
     * @var \Doctrine\DBAL\Driver\Connection
98
     */
99
    protected $_conn;
100
101
    /**
102
     * @var \Doctrine\DBAL\Configuration
103
     */
104
    protected $_config;
105
106
    /**
107
     * @var \Doctrine\Common\EventManager
108
     */
109
    protected $_eventManager;
110
111
    /**
112
     * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
113
     */
114
    protected $_expr;
115
116
    /**
117
     * Whether or not a connection has been established.
118
     *
119
     * @var boolean
120
     */
121
    private $_isConnected = false;
122
123
    /**
124
     * The current auto-commit mode of this connection.
125
     *
126
     * @var boolean
127
     */
128
    private $autoCommit = true;
129
130
    /**
131
     * The transaction nesting level.
132
     *
133
     * @var integer
134
     */
135
    private $_transactionNestingLevel = 0;
136
137
    /**
138
     * The currently active transaction isolation level.
139
     *
140
     * @var integer
141
     */
142
    private $_transactionIsolationLevel;
143
144
    /**
145
     * If nested transactions should use savepoints.
146
     *
147
     * @var boolean
148
     */
149
    private $_nestTransactionsWithSavepoints = false;
150
151
    /**
152
     * The parameters used during creation of the Connection instance.
153
     *
154
     * @var array
155
     */
156
    private $_params = array();
157
158
    /**
159
     * The DatabasePlatform object that provides information about the
160
     * database platform used by the connection.
161
     *
162
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
163
     */
164
    private $platform;
165
166
    /**
167
     * The schema manager.
168
     *
169
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
170
     */
171
    protected $_schemaManager;
172
173
    /**
174
     * The used DBAL driver.
175
     *
176
     * @var \Doctrine\DBAL\Driver
177
     */
178
    protected $_driver;
179
180
    /**
181
     * Flag that indicates whether the current transaction is marked for rollback only.
182
     *
183
     * @var boolean
184
     */
185
    private $_isRollbackOnly = false;
186
187
    /**
188
     * @var integer
189
     */
190
    protected $defaultFetchMode = PDO::FETCH_ASSOC;
191
192
    /**
193
     * Initializes a new instance of the Connection class.
194
     *
195
     * @param array                              $params       The connection parameters.
196
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
197
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
198
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
199
     *
200
     * @throws \Doctrine\DBAL\DBALException
201
     */
202 131
    public function __construct(array $params, Driver $driver, Configuration $config = null,
203
            EventManager $eventManager = null)
204
    {
205 131
        $this->_driver = $driver;
206 131
        $this->_params = $params;
207
208 131
        if (isset($params['pdo'])) {
209 12
            $this->_conn = $params['pdo'];
210 12
            $this->_isConnected = true;
211 12
            unset($this->_params['pdo']);
212
        }
213
214 131
        if (isset($params["platform"])) {
215 28
            if ( ! $params['platform'] instanceof Platforms\AbstractPlatform) {
216 1
                throw DBALException::invalidPlatformType($params['platform']);
217
            }
218
219 27
            $this->platform = $params["platform"];
220 27
            unset($this->_params["platform"]);
221
        }
222
223
        // Create default config and event manager if none given
224 131
        if ( ! $config) {
225 32
            $config = new Configuration();
226
        }
227
228 131
        if ( ! $eventManager) {
229 32
            $eventManager = new EventManager();
230
        }
231
232 131
        $this->_config = $config;
233 131
        $this->_eventManager = $eventManager;
234
235 131
        $this->_expr = new Query\Expression\ExpressionBuilder($this);
236
237 131
        $this->autoCommit = $config->getAutoCommit();
238 131
    }
239
240
    /**
241
     * Gets the parameters used during instantiation.
242
     *
243
     * @return array
244
     */
245 95
    public function getParams()
246
    {
247 95
        return $this->_params;
248
    }
249
250
    /**
251
     * Gets the name of the database this Connection is connected to.
252
     *
253
     * @return string
254
     */
255 46
    public function getDatabase()
256
    {
257 46
        return $this->_driver->getDatabase($this);
258
    }
259
260
    /**
261
     * Gets the hostname of the currently connected database.
262
     *
263
     * @return string|null
264
     */
265 1
    public function getHost()
266
    {
267 1
        return isset($this->_params['host']) ? $this->_params['host'] : null;
268
    }
269
270
    /**
271
     * Gets the port of the currently connected database.
272
     *
273
     * @return mixed
274
     */
275 1
    public function getPort()
276
    {
277 1
        return isset($this->_params['port']) ? $this->_params['port'] : null;
278
    }
279
280
    /**
281
     * Gets the username used by this connection.
282
     *
283
     * @return string|null
284
     */
285 1
    public function getUsername()
286
    {
287 1
        return isset($this->_params['user']) ? $this->_params['user'] : null;
288
    }
289
290
    /**
291
     * Gets the password used by this connection.
292
     *
293
     * @return string|null
294
     */
295 1
    public function getPassword()
296
    {
297 1
        return isset($this->_params['password']) ? $this->_params['password'] : null;
298
    }
299
300
    /**
301
     * Gets the DBAL driver instance.
302
     *
303
     * @return \Doctrine\DBAL\Driver
304
     */
305 52
    public function getDriver()
306
    {
307 52
        return $this->_driver;
308
    }
309
310
    /**
311
     * Gets the Configuration used by the Connection.
312
     *
313
     * @return \Doctrine\DBAL\Configuration
314
     */
315 254
    public function getConfiguration()
316
    {
317 254
        return $this->_config;
318
    }
319
320
    /**
321
     * Gets the EventManager used by the Connection.
322
     *
323
     * @return \Doctrine\Common\EventManager
324
     */
325 10
    public function getEventManager()
326
    {
327 10
        return $this->_eventManager;
328
    }
329
330
    /**
331
     * Gets the DatabasePlatform for the connection.
332
     *
333
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
334
     */
335 199
    public function getDatabasePlatform()
336
    {
337 199
        if (null === $this->platform) {
338 27
            $this->detectDatabasePlatform();
339
        }
340
341 198
        return $this->platform;
342
    }
343
344
    /**
345
     * Gets the ExpressionBuilder for the connection.
346
     *
347
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
348
     */
349
    public function getExpressionBuilder()
350
    {
351
        return $this->_expr;
352
    }
353
354
    /**
355
     * Establishes the connection with the database.
356
     *
357
     * @return boolean TRUE if the connection was successfully established, FALSE if
358
     *                 the connection is already open.
359
     */
360 258
    public function connect()
361
    {
362 258
        if ($this->_isConnected) {
363 239
            return false;
364
        }
365
366 46
        $driverOptions = isset($this->_params['driverOptions']) ?
367 46
            $this->_params['driverOptions'] : array();
368 46
        $user = isset($this->_params['user']) ? $this->_params['user'] : null;
369 46
        $password = isset($this->_params['password']) ?
370 46
            $this->_params['password'] : null;
371
372 46
        $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
373 44
        $this->_isConnected = true;
374
375 44
        if (false === $this->autoCommit) {
376 3
            $this->beginTransaction();
377
        }
378
379 44 View Code Duplication
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380 1
            $eventArgs = new Event\ConnectionEventArgs($this);
381 1
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
382
        }
383
384 44
        return true;
385
    }
386
387
    /**
388
     * Detects and sets the database platform.
389
     *
390
     * Evaluates custom platform class and version in order to set the correct platform.
391
     *
392
     * @throws DBALException if an invalid platform was specified for this connection.
393
     */
394 27
    private function detectDatabasePlatform()
395
    {
396 27
        $version = $this->getDatabasePlatformVersion();
397
398 26
        if (null !== $version) {
399 1
            $this->platform = $this->_driver->createDatabasePlatformForVersion($version);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\DBAL\Driver as the method createDatabasePlatformForVersion() does only exist in the following implementations of said interface: Doctrine\DBAL\Driver\AbstractMySQLDriver, Doctrine\DBAL\Driver\AbstractPostgreSQLDriver, Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver, Doctrine\DBAL\Driver\AbstractSQLServerDriver, Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver, Doctrine\DBAL\Driver\Mysqli\Driver, Doctrine\DBAL\Driver\PDOMySql\Driver, Doctrine\DBAL\Driver\PDOPgSql\Driver, Doctrine\DBAL\Driver\PDOSqlsrv\Driver, Doctrine\DBAL\Driver\SQLAnywhere\Driver, Doctrine\DBAL\Driver\SQLSrv\Driver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
400
        } else {
401 25
            $this->platform = $this->_driver->getDatabasePlatform();
402
        }
403
404 26
        $this->platform->setEventManager($this->_eventManager);
405 26
    }
406
407
    /**
408
     * Returns the version of the related platform if applicable.
409
     *
410
     * Returns null if either the driver is not capable to create version
411
     * specific platform instances, no explicit server version was specified
412
     * or the underlying driver connection cannot determine the platform
413
     * version without having to query it (performance reasons).
414
     *
415
     * @return string|null
416
     */
417 27
    private function getDatabasePlatformVersion()
418
    {
419
        // Driver does not support version specific platforms.
420 27
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
421 24
            return null;
422
        }
423
424
        // Explicit platform version requested (supersedes auto-detection).
425 3
        if (isset($this->_params['serverVersion'])) {
426
            return $this->_params['serverVersion'];
427
        }
428
429
        // If not connected, we need to connect now to determine the platform version.
430 3
        if (null === $this->_conn) {
431
            try {
432 3
                $this->connect();
433 1
            } catch (\Exception $originalException) {
434 1
                if (empty($this->_params['dbname'])) {
435
                    throw $originalException;
436
                }
437
438
                // The database to connect to might not yet exist.
439
                // Retry detection without database name connection parameter.
440 1
                $databaseName = $this->_params['dbname'];
441 1
                $this->_params['dbname'] = null;
442
443
                try {
444 1
                    $this->connect();
445 1
                } catch (\Exception $fallbackException) {
446
                    // Either the platform does not support database-less connections
447
                    // or something else went wrong.
448
                    // Reset connection parameters and rethrow the original exception.
449 1
                    $this->_params['dbname'] = $databaseName;
450
451 1
                    throw $originalException;
452
                }
453
454
                // Reset connection parameters.
455
                $this->_params['dbname'] = $databaseName;
456
                $serverVersion = $this->getServerVersion();
457
458
                // Close "temporary" connection to allow connecting to the real database again.
459
                $this->close();
460
461
                return $serverVersion;
462
            }
463
464
        }
465
466 2
        return $this->getServerVersion();
467
    }
468
469
    /**
470
     * Returns the database server version if the underlying driver supports it.
471
     *
472
     * @return string|null
473
     */
474 2
    private function getServerVersion()
475
    {
476
        // Automatic platform version detection.
477 2
        if ($this->_conn instanceof ServerInfoAwareConnection &&
478 2
            ! $this->_conn->requiresQueryForServerVersion()
479
        ) {
480 1
            return $this->_conn->getServerVersion();
481
        }
482
483
        // Unable to detect platform version.
484 1
        return null;
485
    }
486
487
    /**
488
     * Returns the current auto-commit mode for this connection.
489
     *
490
     * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise.
491
     *
492
     * @see    setAutoCommit
493
     */
494 2
    public function isAutoCommit()
495
    {
496 2
        return true === $this->autoCommit;
497
    }
498
499
    /**
500
     * Sets auto-commit mode for this connection.
501
     *
502
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
503
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
504
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
505
     *
506
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
507
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
508
     *
509
     * @param boolean $autoCommit True to enable auto-commit mode; false to disable it.
510
     *
511
     * @see   isAutoCommit
512
     */
513 5
    public function setAutoCommit($autoCommit)
514
    {
515 5
        $autoCommit = (boolean) $autoCommit;
516
517
        // Mode not changed, no-op.
518 5
        if ($autoCommit === $this->autoCommit) {
519 1
            return;
520
        }
521
522 5
        $this->autoCommit = $autoCommit;
523
524
        // Commit all currently active transactions if any when switching auto-commit mode.
525 5
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
526 1
            $this->commitAll();
527
        }
528 5
    }
529
530
    /**
531
     * Sets the fetch mode.
532
     *
533
     * @param integer $fetchMode
534
     *
535
     * @return void
536
     */
537 1
    public function setFetchMode($fetchMode)
538
    {
539 1
        $this->defaultFetchMode = $fetchMode;
540 1
    }
541
542
    /**
543
     * Prepares and executes an SQL query and returns the first row of the result
544
     * as an associative array.
545
     *
546
     * @param string $statement The SQL query.
547
     * @param array  $params    The query parameters.
548
     * @param array  $types     The query parameter types.
549
     *
550
     * @return array|bool False is returned if no rows are found.
551
     */
552 42
    public function fetchAssoc($statement, array $params = array(), array $types = array())
553
    {
554 42
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC);
555
    }
556
557
    /**
558
     * Prepares and executes an SQL query and returns the first row of the result
559
     * as a numerically indexed array.
560
     *
561
     * @param string $statement The SQL query to be executed.
562
     * @param array  $params    The prepared statement params.
563
     * @param array  $types     The query parameter types.
564
     *
565
     * @return array|bool False is returned if no rows are found.
566
     */
567 4
    public function fetchArray($statement, array $params = array(), array $types = array())
568
    {
569 4
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM);
570
    }
571
572
    /**
573
     * Prepares and executes an SQL query and returns the value of a single column
574
     * of the first row of the result.
575
     *
576
     * @param string  $statement The SQL query to be executed.
577
     * @param array   $params    The prepared statement params.
578
     * @param integer $column    The 0-indexed column number to retrieve.
579
     * @param array  $types      The query parameter types.
580
     *
581
     * @return mixed|bool False is returned if no rows are found.
582
     */
583 28
    public function fetchColumn($statement, array $params = array(), $column = 0, array $types = array())
584
    {
585 28
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
586
    }
587
588
    /**
589
     * Whether an actual connection to the database is established.
590
     *
591
     * @return boolean
592
     */
593 4
    public function isConnected()
594
    {
595 4
        return $this->_isConnected;
596
    }
597
598
    /**
599
     * Checks whether a transaction is currently active.
600
     *
601
     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
602
     */
603 253
    public function isTransactionActive()
604
    {
605 253
        return $this->_transactionNestingLevel > 0;
606
    }
607
608
    /**
609
     * Gathers conditions for an update or delete call.
610
     *
611
     * @param array $identifiers Input array of columns to values
612
     *
613
     * @return string[][] a triplet with:
614
     *                    - the first key being the columns
615
     *                    - the second key being the values
616
     *                    - the third key being the conditions
617
     */
618 13
    private function gatherConditions(array $identifiers)
619
    {
620 13
        $columns = [];
621 13
        $values = [];
622 13
        $conditions = [];
623
624 13
        foreach ($identifiers as $columnName => $value) {
625 13
            if (null === $value) {
626 4
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
627 4
                continue;
628
            }
629
630 11
            $columns[] = $columnName;
631 11
            $values[] = $value;
632 11
            $conditions[] = $columnName . ' = ?';
633
        }
634
635 13
        return [$columns, $values, $conditions];
636
    }
637
638
    /**
639
     * Executes an SQL DELETE statement on a table.
640
     *
641
     * Table expression and columns are not escaped and are not safe for user-input.
642
     *
643
     * @param string $tableExpression  The expression of the table on which to delete.
644
     * @param array  $identifier The deletion criteria. An associative array containing column-value pairs.
645
     * @param array  $types      The types of identifiers.
646
     *
647
     * @return integer The number of affected rows.
648
     *
649
     * @throws InvalidArgumentException
650
     */
651 6
    public function delete($tableExpression, array $identifier, array $types = array())
652
    {
653 6
        if (empty($identifier)) {
654 1
            throw InvalidArgumentException::fromEmptyCriteria();
655
        }
656
657 5
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
658
659 5
        return $this->executeUpdate(
660 5
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
661 5
            $values,
662 5
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
663
        );
664
    }
665
666
    /**
667
     * Closes the connection.
668
     *
669
     * @return void
670
     */
671 28
    public function close()
672
    {
673 28
        $this->_conn = null;
674
675 28
        $this->_isConnected = false;
676 28
    }
677
678
    /**
679
     * Sets the transaction isolation level.
680
     *
681
     * @param integer $level The level to set.
682
     *
683
     * @return integer
684
     */
685
    public function setTransactionIsolation($level)
686
    {
687
        $this->_transactionIsolationLevel = $level;
688
689
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
690
    }
691
692
    /**
693
     * Gets the currently active transaction isolation level.
694
     *
695
     * @return integer The current transaction isolation level.
696
     */
697
    public function getTransactionIsolation()
698
    {
699
        if (null === $this->_transactionIsolationLevel) {
700
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
701
        }
702
703
        return $this->_transactionIsolationLevel;
704
    }
705
706
    /**
707
     * Executes an SQL UPDATE statement on a table.
708
     *
709
     * Table expression and columns are not escaped and are not safe for user-input.
710
     *
711
     * @param string $tableExpression  The expression of the table to update quoted or unquoted.
712
     * @param array  $data       An associative array containing column-value pairs.
713
     * @param array  $identifier The update criteria. An associative array containing column-value pairs.
714
     * @param array  $types      Types of the merged $data and $identifier arrays in that order.
715
     *
716
     * @return integer The number of affected rows.
717
     */
718 8
    public function update($tableExpression, array $data, array $identifier, array $types = array())
719
    {
720 8
        $setColumns = array();
721 8
        $setValues = array();
722 8
        $set = array();
723
724 8
        foreach ($data as $columnName => $value) {
725 8
            $setColumns[] = $columnName;
726 8
            $setValues[] = $value;
727 8
            $set[] = $columnName . ' = ?';
728
        }
729
730 8
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
731 8
        $columns = array_merge($setColumns, $conditionColumns);
732 8
        $values = array_merge($setValues, $conditionValues);
733
734 8
        if (is_string(key($types))) {
735 5
            $types = $this->extractTypeValues($columns, $types);
736
        }
737
738 8
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
739 8
                . ' WHERE ' . implode(' AND ', $conditions);
740
741 8
        return $this->executeUpdate($sql, $values, $types);
742
    }
743
744
    /**
745
     * Inserts a table row with specified data.
746
     *
747
     * Table expression and columns are not escaped and are not safe for user-input.
748
     *
749
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
750
     * @param array  $data      An associative array containing column-value pairs.
751
     * @param array  $types     Types of the inserted data.
752
     *
753
     * @return integer The number of affected rows.
754
     */
755 78
    public function insert($tableExpression, array $data, array $types = array())
756
    {
757 78
        if (empty($data)) {
758 1
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
759
        }
760
761 77
        $columns = array();
762 77
        $values = array();
763 77
        $set = array();
764
765 77
        foreach ($data as $columnName => $value) {
766 77
            $columns[] = $columnName;
767 77
            $values[] = $value;
768 77
            $set[] = '?';
769
        }
770
771 77
        return $this->executeUpdate(
772 77
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
773 77
            ' VALUES (' . implode(', ', $set) . ')',
774 77
            $values,
775 77
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
776
        );
777
    }
778
779
    /**
780
     * Extract ordered type list from an ordered column list and type map.
781
     *
782
     * @param array $columnList
783
     * @param array $types
784
     *
785
     * @return array
786
     */
787 9
    private function extractTypeValues(array $columnList, array $types)
788
    {
789 9
        $typeValues = array();
790
791 9
        foreach ($columnList as $columnIndex => $columnName) {
792 9
            $typeValues[] = isset($types[$columnName])
793 9
                ? $types[$columnName]
794 9
                : \PDO::PARAM_STR;
795
        }
796
797 9
        return $typeValues;
798
    }
799
800
    /**
801
     * Quotes a string so it can be safely used as a table or column name, even if
802
     * it is a reserved name.
803
     *
804
     * Delimiting style depends on the underlying database platform that is being used.
805
     *
806
     * NOTE: Just because you CAN use quoted identifiers does not mean
807
     * you SHOULD use them. In general, they end up causing way more
808
     * problems than they solve.
809
     *
810
     * @param string $str The name to be quoted.
811
     *
812
     * @return string The quoted name.
813
     */
814 1
    public function quoteIdentifier($str)
815
    {
816 1
        return $this->getDatabasePlatform()->quoteIdentifier($str);
817
    }
818
819
    /**
820
     * Quotes a given input parameter.
821
     *
822
     * @param mixed       $input The parameter to be quoted.
823
     * @param int|null $type  The type of the parameter.
824
     *
825
     * @return string The quoted parameter.
826
     */
827 4
    public function quote($input, $type = null)
828
    {
829 4
        $this->connect();
830
831 4
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
832
833 4
        return $this->_conn->quote($value, $bindingType);
834
    }
835
836
    /**
837
     * Prepares and executes an SQL query and returns the result as an associative array.
838
     *
839
     * @param string $sql    The SQL query.
840
     * @param array  $params The query parameters.
841
     * @param array  $types  The query parameter types.
842
     *
843
     * @return array
844
     */
845 75
    public function fetchAll($sql, array $params = array(), $types = array())
846
    {
847 75
        return $this->executeQuery($sql, $params, $types)->fetchAll();
848
    }
849
850
    /**
851
     * Prepares an SQL statement.
852
     *
853
     * @param string $statement The SQL statement to prepare.
854
     *
855
     * @return \Doctrine\DBAL\Statement The prepared statement.
856
     *
857
     * @throws \Doctrine\DBAL\DBALException
858
     */
859 40
    public function prepare($statement)
860
    {
861
        try {
862 40
            $stmt = new Statement($statement, $this);
863 1
        } catch (Exception $ex) {
864 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
865
        }
866
867 39
        $stmt->setFetchMode($this->defaultFetchMode);
868
869 39
        return $stmt;
870
    }
871
872
    /**
873
     * Executes an, optionally parametrized, SQL query.
874
     *
875
     * If the query is parametrized, a prepared statement is used.
876
     * If an SQLLogger is configured, the execution is logged.
877
     *
878
     * @param string                                      $query  The SQL query to execute.
879
     * @param array                                       $params The parameters to bind to the query, if any.
880
     * @param array                                       $types  The types the previous parameters are in.
881
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
882
     *
883
     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
884
     *
885
     * @throws \Doctrine\DBAL\DBALException
886
     */
887 171
    public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null)
888
    {
889 171
        if ($qcp !== null) {
890 10
            return $this->executeCacheQuery($query, $params, $types, $qcp);
891
        }
892
893 171
        $this->connect();
894
895 171
        $logger = $this->_config->getSQLLogger();
896 171
        if ($logger) {
897 166
            $logger->startQuery($query, $params, $types);
898
        }
899
900
        try {
901 171 View Code Duplication
            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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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

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

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

Loading history...
906 15
                    $this->_bindTypedValues($stmt, $params, $types);
907 15
                    $stmt->execute();
908
                } else {
909 31
                    $stmt->execute($params);
910
                }
911
            } else {
912 167
                $stmt = $this->_conn->query($query);
0 ignored issues
show
Unused Code introduced by
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...
913
            }
914 8
        } catch (Exception $ex) {
915 8
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
916
        }
917
918 163
        $stmt->setFetchMode($this->defaultFetchMode);
919
920 163
        if ($logger) {
921 159
            $logger->stopQuery();
922
        }
923
924 163
        return $stmt;
925
    }
926
927
    /**
928
     * Executes a caching query.
929
     *
930
     * @param string                                 $query  The SQL query to execute.
931
     * @param array                                  $params The parameters to bind to the query, if any.
932
     * @param array                                  $types  The types the previous parameters are in.
933
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
934
     *
935
     * @return \Doctrine\DBAL\Driver\ResultStatement
936
     *
937
     * @throws \Doctrine\DBAL\Cache\CacheException
938
     */
939 12
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
940
    {
941 12
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
942 12
        if ( ! $resultCache) {
943
            throw CacheException::noResultDriverConfigured();
944
        }
945
946 12
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
947
948
        // fetch the row pointers entry
949 12
        if ($data = $resultCache->fetch($cacheKey)) {
950
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
951 9
            if (isset($data[$realKey])) {
952 9
                $stmt = new ArrayStatement($data[$realKey]);
953
            } elseif (array_key_exists($realKey, $data)) {
954
                $stmt = new ArrayStatement(array());
955
            }
956
        }
957
958 12
        if (!isset($stmt)) {
959 10
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
960
        }
961
962 12
        $stmt->setFetchMode($this->defaultFetchMode);
963
964 12
        return $stmt;
965
    }
966
967
    /**
968
     * Executes an, optionally parametrized, SQL query and returns the result,
969
     * applying a given projection/transformation function on each row of the result.
970
     *
971
     * @param string   $query    The SQL query to execute.
972
     * @param array    $params   The parameters, if any.
973
     * @param \Closure $function The transformation function that is applied on each row.
974
     *                           The function receives a single parameter, an array, that
975
     *                           represents a row of the result set.
976
     *
977
     * @return array The projected result of the query.
978
     */
979
    public function project($query, array $params, Closure $function)
980
    {
981
        $result = array();
982
        $stmt = $this->executeQuery($query, $params);
983
984
        while ($row = $stmt->fetch()) {
985
            $result[] = $function($row);
986
        }
987
988
        $stmt->closeCursor();
989
990
        return $result;
991
    }
992
993
    /**
994
     * Executes an SQL statement, returning a result set as a Statement object.
995
     *
996
     * @return \Doctrine\DBAL\Driver\Statement
997
     *
998
     * @throws \Doctrine\DBAL\DBALException
999
     */
1000 8
    public function query()
1001
    {
1002 8
        $this->connect();
1003
1004 8
        $args = func_get_args();
1005
1006 8
        $logger = $this->_config->getSQLLogger();
1007 8
        if ($logger) {
1008 7
            $logger->startQuery($args[0]);
1009
        }
1010
1011
        try {
1012 8
            $statement = $this->_conn->query(...$args);
0 ignored issues
show
Unused Code introduced by
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...
1013 1
        } catch (Exception $ex) {
1014 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1015
        }
1016
1017 7
        $statement->setFetchMode($this->defaultFetchMode);
1018
1019 7
        if ($logger) {
1020 7
            $logger->stopQuery();
1021
        }
1022
1023 7
        return $statement;
1024
    }
1025
1026
    /**
1027
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1028
     * and returns the number of affected rows.
1029
     *
1030
     * This method supports PDO binding types as well as DBAL mapping types.
1031
     *
1032
     * @param string $query  The SQL query.
1033
     * @param array  $params The query parameters.
1034
     * @param array  $types  The parameter types.
1035
     *
1036
     * @return integer The number of affected rows.
1037
     *
1038
     * @throws \Doctrine\DBAL\DBALException
1039
     */
1040 139
    public function executeUpdate($query, array $params = array(), array $types = array())
1041
    {
1042 139
        $this->connect();
1043
1044 139
        $logger = $this->_config->getSQLLogger();
1045 139
        if ($logger) {
1046 134
            $logger->startQuery($query, $params, $types);
1047
        }
1048
1049
        try {
1050 139 View Code Duplication
            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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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

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

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

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1246 17
            if ($logger) {
1247 12
                $logger->startQuery('"START TRANSACTION"');
1248
            }
1249 17
            $this->_conn->beginTransaction();
1250 17
            if ($logger) {
1251 17
                $logger->stopQuery();
1252
            }
1253 3
        } elseif ($this->_nestTransactionsWithSavepoints) {
1254 1
            if ($logger) {
1255 1
                $logger->startQuery('"SAVEPOINT"');
1256
            }
1257 1
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1258 1
            if ($logger) {
1259 1
                $logger->stopQuery();
1260
            }
1261
        }
1262 17
    }
1263
1264
    /**
1265
     * Commits the current transaction.
1266
     *
1267
     * @return void
1268
     *
1269
     * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
1270
     *                                            because the transaction was marked for rollback only.
1271
     */
1272 9
    public function commit()
1273
    {
1274 9
        if ($this->_transactionNestingLevel == 0) {
1275 1
            throw ConnectionException::noActiveTransaction();
1276
        }
1277 8
        if ($this->_isRollbackOnly) {
1278 2
            throw ConnectionException::commitFailedRollbackOnly();
1279
        }
1280
1281 6
        $this->connect();
1282
1283 6
        $logger = $this->_config->getSQLLogger();
1284
1285 6 View Code Duplication
        if ($this->_transactionNestingLevel == 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1286 6
            if ($logger) {
1287 4
                $logger->startQuery('"COMMIT"');
1288
            }
1289 6
            $this->_conn->commit();
1290 6
            if ($logger) {
1291 6
                $logger->stopQuery();
1292
            }
1293 2
        } elseif ($this->_nestTransactionsWithSavepoints) {
1294 1
            if ($logger) {
1295 1
                $logger->startQuery('"RELEASE SAVEPOINT"');
1296
            }
1297 1
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1298 1
            if ($logger) {
1299 1
                $logger->stopQuery();
1300
            }
1301
        }
1302
1303 6
        --$this->_transactionNestingLevel;
1304
1305 6
        if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) {
1306 2
            $this->beginTransaction();
1307
        }
1308 6
    }
1309
1310
    /**
1311
     * Commits all current nesting transactions.
1312
     */
1313 1
    private function commitAll()
1314
    {
1315 1
        while (0 !== $this->_transactionNestingLevel) {
1316 1
            if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) {
1317
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1318
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1319 1
                $this->commit();
1320
1321 1
                return;
1322
            }
1323
1324 1
            $this->commit();
1325
        }
1326 1
    }
1327
1328
    /**
1329
     * Cancels any database changes done during the current transaction.
1330
     *
1331
     * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
1332
     */
1333 11
    public function rollBack()
1334
    {
1335 11
        if ($this->_transactionNestingLevel == 0) {
1336 1
            throw ConnectionException::noActiveTransaction();
1337
        }
1338
1339 10
        $this->connect();
1340
1341 10
        $logger = $this->_config->getSQLLogger();
1342
1343 10
        if ($this->_transactionNestingLevel == 1) {
1344 9
            if ($logger) {
1345 8
                $logger->startQuery('"ROLLBACK"');
1346
            }
1347 9
            $this->_transactionNestingLevel = 0;
1348 9
            $this->_conn->rollBack();
1349 9
            $this->_isRollbackOnly = false;
1350 9
            if ($logger) {
1351 8
                $logger->stopQuery();
1352
            }
1353
1354 9
            if (false === $this->autoCommit) {
1355 9
                $this->beginTransaction();
1356
            }
1357 2
        } elseif ($this->_nestTransactionsWithSavepoints) {
1358 1
            if ($logger) {
1359 1
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1360
            }
1361 1
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1362 1
            --$this->_transactionNestingLevel;
1363 1
            if ($logger) {
1364 1
                $logger->stopQuery();
1365
            }
1366
        } else {
1367 1
            $this->_isRollbackOnly = true;
1368 1
            --$this->_transactionNestingLevel;
1369
        }
1370 10
    }
1371
1372
    /**
1373
     * Creates a new savepoint.
1374
     *
1375
     * @param string $savepoint The name of the savepoint to create.
1376
     *
1377
     * @return void
1378
     *
1379
     * @throws \Doctrine\DBAL\ConnectionException
1380
     */
1381 1
    public function createSavepoint($savepoint)
1382
    {
1383 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1384
            throw ConnectionException::savepointsNotSupported();
1385
        }
1386
1387 1
        $this->_conn->exec($this->platform->createSavePoint($savepoint));
1388 1
    }
1389
1390
    /**
1391
     * Releases the given savepoint.
1392
     *
1393
     * @param string $savepoint The name of the savepoint to release.
1394
     *
1395
     * @return void
1396
     *
1397
     * @throws \Doctrine\DBAL\ConnectionException
1398
     */
1399 1
    public function releaseSavepoint($savepoint)
1400
    {
1401 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1402
            throw ConnectionException::savepointsNotSupported();
1403
        }
1404
1405 1
        if ($this->platform->supportsReleaseSavepoints()) {
1406 1
            $this->_conn->exec($this->platform->releaseSavePoint($savepoint));
1407
        }
1408 1
    }
1409
1410
    /**
1411
     * Rolls back to the given savepoint.
1412
     *
1413
     * @param string $savepoint The name of the savepoint to rollback to.
1414
     *
1415
     * @return void
1416
     *
1417
     * @throws \Doctrine\DBAL\ConnectionException
1418
     */
1419 1
    public function rollbackSavepoint($savepoint)
1420
    {
1421 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1422
            throw ConnectionException::savepointsNotSupported();
1423
        }
1424
1425 1
        $this->_conn->exec($this->platform->rollbackSavePoint($savepoint));
1426 1
    }
1427
1428
    /**
1429
     * Gets the wrapped driver connection.
1430
     *
1431
     * @return \Doctrine\DBAL\Driver\Connection
1432
     */
1433 47
    public function getWrappedConnection()
1434
    {
1435 47
        $this->connect();
1436
1437 47
        return $this->_conn;
1438
    }
1439
1440
    /**
1441
     * Gets the SchemaManager that can be used to inspect or change the
1442
     * database schema through the connection.
1443
     *
1444
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
1445
     */
1446 133
    public function getSchemaManager()
1447
    {
1448 133
        if ( ! $this->_schemaManager) {
1449 11
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1450
        }
1451
1452 133
        return $this->_schemaManager;
1453
    }
1454
1455
    /**
1456
     * Marks the current transaction so that the only possible
1457
     * outcome for the transaction to be rolled back.
1458
     *
1459
     * @return void
1460
     *
1461
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1462
     */
1463 2
    public function setRollbackOnly()
1464
    {
1465 2
        if ($this->_transactionNestingLevel == 0) {
1466 1
            throw ConnectionException::noActiveTransaction();
1467
        }
1468 1
        $this->_isRollbackOnly = true;
1469 1
    }
1470
1471
    /**
1472
     * Checks whether the current transaction is marked for rollback only.
1473
     *
1474
     * @return boolean
1475
     *
1476
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1477
     */
1478 3
    public function isRollbackOnly()
1479
    {
1480 3
        if ($this->_transactionNestingLevel == 0) {
1481 1
            throw ConnectionException::noActiveTransaction();
1482
        }
1483
1484 2
        return $this->_isRollbackOnly;
1485
    }
1486
1487
    /**
1488
     * Converts a given value to its database representation according to the conversion
1489
     * rules of a specific DBAL mapping type.
1490
     *
1491
     * @param mixed  $value The value to convert.
1492
     * @param string $type  The name of the DBAL mapping type.
1493
     *
1494
     * @return mixed The converted value.
1495
     */
1496
    public function convertToDatabaseValue($value, $type)
1497
    {
1498
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1499
    }
1500
1501
    /**
1502
     * Converts a given value to its PHP representation according to the conversion
1503
     * rules of a specific DBAL mapping type.
1504
     *
1505
     * @param mixed  $value The value to convert.
1506
     * @param string $type  The name of the DBAL mapping type.
1507
     *
1508
     * @return mixed The converted type.
1509
     */
1510
    public function convertToPHPValue($value, $type)
1511
    {
1512
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1513
    }
1514
1515
    /**
1516
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1517
     * or DBAL mapping type, to a given statement.
1518
     *
1519
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1520
     * @param array                           $params The map/list of named/positional parameters.
1521
     * @param array                           $types  The parameter types (PDO binding types or DBAL mapping types).
1522
     *
1523
     * @return void
1524
     *
1525
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1526
     *           raw PDOStatement instances.
1527
     */
1528 26
    private function _bindTypedValues($stmt, array $params, array $types)
1529
    {
1530
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1531 26
        if (is_int(key($params))) {
1532
            // Positional parameters
1533 26
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1534 26
            $bindIndex = 1;
1535 26
            foreach ($params as $value) {
1536 26
                $typeIndex = $bindIndex + $typeOffset;
1537 26 View Code Duplication
                if (isset($types[$typeIndex])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1538 26
                    $type = $types[$typeIndex];
1539 26
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1540 26
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1541
                } else {
1542 1
                    $stmt->bindValue($bindIndex, $value);
1543
                }
1544 26
                ++$bindIndex;
1545
            }
1546
        } else {
1547
            // Named parameters
1548
            foreach ($params as $name => $value) {
1549 View Code Duplication
                if (isset($types[$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1550
                    $type = $types[$name];
1551
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1552
                    $stmt->bindValue($name, $value, $bindingType);
1553
                } else {
1554
                    $stmt->bindValue($name, $value);
1555
                }
1556
            }
1557
        }
1558 26
    }
1559
1560
    /**
1561
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1562
     *
1563
     * @param mixed $value The value to bind.
1564
     * @param mixed $type  The type to bind (PDO or DBAL).
1565
     *
1566
     * @return array [0] => the (escaped) value, [1] => the binding type.
1567
     */
1568 30
    private function getBindingInfo($value, $type)
1569
    {
1570 30
        if (is_string($type)) {
1571 12
            $type = Type::getType($type);
1572
        }
1573 30 View Code Duplication
        if ($type instanceof Type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1574 12
            $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1575 12
            $bindingType = $type->getBindingType();
1576
        } else {
1577 24
            $bindingType = $type; // PDO::PARAM_* constants
1578
        }
1579
1580 30
        return array($value, $bindingType);
1581
    }
1582
1583
    /**
1584
     * Resolves the parameters to a format which can be displayed.
1585
     *
1586
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1587
     *           copy/paste the code as this method may change, or be removed without prior notice.
1588
     *
1589
     * @param array $params
1590
     * @param array $types
1591
     *
1592
     * @return array
1593
     */
1594 60
    public function resolveParams(array $params, array $types)
1595
    {
1596 60
        $resolvedParams = array();
1597
1598
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1599 60
        if (is_int(key($params))) {
1600
            // Positional parameters
1601 8
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1602 8
            $bindIndex = 1;
1603 8
            foreach ($params as $value) {
1604 8
                $typeIndex = $bindIndex + $typeOffset;
1605 8 View Code Duplication
                if (isset($types[$typeIndex])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1606
                    $type = $types[$typeIndex];
1607
                    list($value,) = $this->getBindingInfo($value, $type);
1608
                    $resolvedParams[$bindIndex] = $value;
1609
                } else {
1610 8
                    $resolvedParams[$bindIndex] = $value;
1611
                }
1612 8
                ++$bindIndex;
1613
            }
1614
        } else {
1615
            // Named parameters
1616 52
            foreach ($params as $name => $value) {
1617 View Code Duplication
                if (isset($types[$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1618
                    $type = $types[$name];
1619
                    list($value,) = $this->getBindingInfo($value, $type);
1620
                    $resolvedParams[$name] = $value;
1621
                } else {
1622
                    $resolvedParams[$name] = $value;
1623
                }
1624
            }
1625
        }
1626
1627 60
        return $resolvedParams;
1628
    }
1629
1630
    /**
1631
     * Creates a new instance of a SQL query builder.
1632
     *
1633
     * @return \Doctrine\DBAL\Query\QueryBuilder
1634
     */
1635
    public function createQueryBuilder()
1636
    {
1637
        return new Query\QueryBuilder($this);
1638
    }
1639
1640
    /**
1641
     * Ping the server
1642
     *
1643
     * When the server is not available the method returns FALSE.
1644
     * It is responsibility of the developer to handle this case
1645
     * and abort the request or reconnect manually:
1646
     *
1647
     * @example
1648
     *
1649
     *   if ($conn->ping() === false) {
1650
     *      $conn->close();
1651
     *      $conn->connect();
1652
     *   }
1653
     *
1654
     * It is undefined if the underlying driver attempts to reconnect
1655
     * or disconnect when the connection is not available anymore
1656
     * as long it returns TRUE when a reconnect succeeded and
1657
     * FALSE when the connection was dropped.
1658
     *
1659
     * @return bool
1660
     */
1661 1
    public function ping()
1662
    {
1663 1
        $this->connect();
1664
1665 1
        if ($this->_conn instanceof PingableConnection) {
1666
            return $this->_conn->ping();
1667
        }
1668
1669
        try {
1670 1
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1671
1672 1
            return true;
1673
        } catch (DBALException $e) {
1674
            return false;
1675
        }
1676
    }
1677
}
1678