Failed Conditions
Push — master ( 656579...2742cd )
by Marco
11:55
created

lib/Doctrine/DBAL/Connection.php (4 issues)

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 = [];
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 97
    public function getParams()
246
    {
247 97
        return $this->_params;
248
    }
249
250
    /**
251
     * Gets the name of the database this Connection is connected to.
252
     *
253
     * @return string
254
     */
255 48
    public function getDatabase()
256
    {
257 48
        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 $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 $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 $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 $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 262
    public function getConfiguration()
316
    {
317 262
        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
     * @throws \Doctrine\DBAL\DBALException
336
     */
337 207
    public function getDatabasePlatform()
338
    {
339 207
        if (null === $this->platform) {
340 27
            $this->detectDatabasePlatform();
341
        }
342
343 206
        return $this->platform;
344
    }
345
346
    /**
347
     * Gets the ExpressionBuilder for the connection.
348
     *
349
     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
350
     */
351
    public function getExpressionBuilder()
352
    {
353
        return $this->_expr;
354
    }
355
356
    /**
357
     * Establishes the connection with the database.
358
     *
359
     * @return boolean TRUE if the connection was successfully established, FALSE if
360
     *                 the connection is already open.
361
     */
362 268
    public function connect()
363
    {
364 268
        if ($this->_isConnected) {
365 251
            return false;
366
        }
367
368 46
        $driverOptions = $this->_params['driverOptions'] ?? [];
369 46
        $user = $this->_params['user'] ?? null;
370 46
        $password = $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)) {
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);
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
     * @throws Exception
418
     */
419 27
    private function getDatabasePlatformVersion()
420
    {
421
        // Driver does not support version specific platforms.
422 27
        if ( ! $this->_driver instanceof VersionAwarePlatformDriver) {
423 24
            return null;
424
        }
425
426
        // Explicit platform version requested (supersedes auto-detection).
427 3
        if (isset($this->_params['serverVersion'])) {
428
            return $this->_params['serverVersion'];
429
        }
430
431
        // If not connected, we need to connect now to determine the platform version.
432 3
        if (null === $this->_conn) {
433
            try {
434 3
                $this->connect();
435 1
            } catch (\Exception $originalException) {
436 1
                if (empty($this->_params['dbname'])) {
437
                    throw $originalException;
438
                }
439
440
                // The database to connect to might not yet exist.
441
                // Retry detection without database name connection parameter.
442 1
                $databaseName = $this->_params['dbname'];
443 1
                $this->_params['dbname'] = null;
444
445
                try {
446 1
                    $this->connect();
447 1
                } catch (\Exception $fallbackException) {
448
                    // Either the platform does not support database-less connections
449
                    // or something else went wrong.
450
                    // Reset connection parameters and rethrow the original exception.
451 1
                    $this->_params['dbname'] = $databaseName;
452
453 1
                    throw $originalException;
454
                }
455
456
                // Reset connection parameters.
457
                $this->_params['dbname'] = $databaseName;
458
                $serverVersion = $this->getServerVersion();
459
460
                // Close "temporary" connection to allow connecting to the real database again.
461
                $this->close();
462
463
                return $serverVersion;
464
            }
465
466
        }
467
468 2
        return $this->getServerVersion();
469
    }
470
471
    /**
472
     * Returns the database server version if the underlying driver supports it.
473
     *
474
     * @return string|null
475
     */
476 2
    private function getServerVersion()
477
    {
478
        // Automatic platform version detection.
479 2
        if ($this->_conn instanceof ServerInfoAwareConnection &&
480 2
            ! $this->_conn->requiresQueryForServerVersion()
481
        ) {
482 1
            return $this->_conn->getServerVersion();
483
        }
484
485
        // Unable to detect platform version.
486 1
        return null;
487
    }
488
489
    /**
490
     * Returns the current auto-commit mode for this connection.
491
     *
492
     * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise.
493
     *
494
     * @see    setAutoCommit
495
     */
496 2
    public function isAutoCommit()
497
    {
498 2
        return true === $this->autoCommit;
499
    }
500
501
    /**
502
     * Sets auto-commit mode for this connection.
503
     *
504
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
505
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
506
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
507
     *
508
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
509
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
510
     *
511
     * @param boolean $autoCommit True to enable auto-commit mode; false to disable it.
512
     *
513
     * @see   isAutoCommit
514
     */
515 5
    public function setAutoCommit($autoCommit)
516
    {
517 5
        $autoCommit = (boolean) $autoCommit;
518
519
        // Mode not changed, no-op.
520 5
        if ($autoCommit === $this->autoCommit) {
521 1
            return;
522
        }
523
524 5
        $this->autoCommit = $autoCommit;
525
526
        // Commit all currently active transactions if any when switching auto-commit mode.
527 5
        if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) {
528 1
            $this->commitAll();
529
        }
530 5
    }
531
532
    /**
533
     * Sets the fetch mode.
534
     *
535
     * @param integer $fetchMode
536
     *
537
     * @return void
538
     */
539 1
    public function setFetchMode($fetchMode)
540
    {
541 1
        $this->defaultFetchMode = $fetchMode;
542 1
    }
543
544
    /**
545
     * Prepares and executes an SQL query and returns the first row of the result
546
     * as an associative array.
547
     *
548
     * @param string $statement The SQL query.
549
     * @param array  $params    The query parameters.
550
     * @param array  $types     The query parameter types.
551
     *
552
     * @return array|bool False is returned if no rows are found.
553
     *
554
     * @throws \Doctrine\DBAL\DBALException
555
     */
556 42
    public function fetchAssoc($statement, array $params = [], array $types = [])
557
    {
558 42
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC);
559
    }
560
561
    /**
562
     * Prepares and executes an SQL query and returns the first row of the result
563
     * as a numerically indexed array.
564
     *
565
     * @param string $statement The SQL query to be executed.
566
     * @param array  $params    The prepared statement params.
567
     * @param array  $types     The query parameter types.
568
     *
569
     * @return array|bool False is returned if no rows are found.
570
     */
571 4
    public function fetchArray($statement, array $params = [], array $types = [])
572
    {
573 4
        return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM);
574
    }
575
576
    /**
577
     * Prepares and executes an SQL query and returns the value of a single column
578
     * of the first row of the result.
579
     *
580
     * @param string  $statement The SQL query to be executed.
581
     * @param array   $params    The prepared statement params.
582
     * @param integer $column    The 0-indexed column number to retrieve.
583
     * @param array  $types      The query parameter types.
584
     *
585
     * @return mixed|bool False is returned if no rows are found.
586
     *
587
     * @throws \Doctrine\DBAL\DBALException
588
     */
589 28
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
590
    {
591 28
        return $this->executeQuery($statement, $params, $types)->fetchColumn($column);
592
    }
593
594
    /**
595
     * Whether an actual connection to the database is established.
596
     *
597
     * @return boolean
598
     */
599 4
    public function isConnected()
600
    {
601 4
        return $this->_isConnected;
602
    }
603
604
    /**
605
     * Checks whether a transaction is currently active.
606
     *
607
     * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
608
     */
609 261
    public function isTransactionActive()
610
    {
611 261
        return $this->_transactionNestingLevel > 0;
612
    }
613
614
    /**
615
     * Gathers conditions for an update or delete call.
616
     *
617
     * @param array $identifiers Input array of columns to values
618
     *
619
     * @return string[][] a triplet with:
620
     *                    - the first key being the columns
621
     *                    - the second key being the values
622
     *                    - the third key being the conditions
623
     */
624 13
    private function gatherConditions(array $identifiers)
625
    {
626 13
        $columns = [];
627 13
        $values = [];
628 13
        $conditions = [];
629
630 13
        foreach ($identifiers as $columnName => $value) {
631 13
            if (null === $value) {
632 4
                $conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
633 4
                continue;
634
            }
635
636 11
            $columns[] = $columnName;
637 11
            $values[] = $value;
638 11
            $conditions[] = $columnName . ' = ?';
639
        }
640
641 13
        return [$columns, $values, $conditions];
642
    }
643
644
    /**
645
     * Executes an SQL DELETE statement on a table.
646
     *
647
     * Table expression and columns are not escaped and are not safe for user-input.
648
     *
649
     * @param string $tableExpression  The expression of the table on which to delete.
650
     * @param array  $identifier The deletion criteria. An associative array containing column-value pairs.
651
     * @param array  $types      The types of identifiers.
652
     *
653
     * @return integer The number of affected rows.
654
     *
655
     * @throws \Doctrine\DBAL\DBALException
656
     * @throws InvalidArgumentException
657
     */
658 6
    public function delete($tableExpression, array $identifier, array $types = [])
659
    {
660 6
        if (empty($identifier)) {
661 1
            throw InvalidArgumentException::fromEmptyCriteria();
662
        }
663
664 5
        list($columns, $values, $conditions) = $this->gatherConditions($identifier);
665
666 5
        return $this->executeUpdate(
667 5
            'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
668 5
            $values,
669 5
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
670
        );
671
    }
672
673
    /**
674
     * Closes the connection.
675
     *
676
     * @return void
677
     */
678 28
    public function close()
679
    {
680 28
        $this->_conn = null;
681
682 28
        $this->_isConnected = false;
683 28
    }
684
685
    /**
686
     * Sets the transaction isolation level.
687
     *
688
     * @param integer $level The level to set.
689
     *
690
     * @return integer
691
     */
692
    public function setTransactionIsolation($level)
693
    {
694
        $this->_transactionIsolationLevel = $level;
695
696
        return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
697
    }
698
699
    /**
700
     * Gets the currently active transaction isolation level.
701
     *
702
     * @return integer The current transaction isolation level.
703
     */
704
    public function getTransactionIsolation()
705
    {
706
        if (null === $this->_transactionIsolationLevel) {
707
            $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
708
        }
709
710
        return $this->_transactionIsolationLevel;
711
    }
712
713
    /**
714
     * Executes an SQL UPDATE statement on a table.
715
     *
716
     * Table expression and columns are not escaped and are not safe for user-input.
717
     *
718
     * @param string $tableExpression  The expression of the table to update quoted or unquoted.
719
     * @param array  $data       An associative array containing column-value pairs.
720
     * @param array  $identifier The update criteria. An associative array containing column-value pairs.
721
     * @param array  $types      Types of the merged $data and $identifier arrays in that order.
722
     *
723
     * @return integer The number of affected rows.
724
     *
725
     * @throws \Doctrine\DBAL\DBALException
726
     */
727 8
    public function update($tableExpression, array $data, array $identifier, array $types = [])
728
    {
729 8
        $setColumns = [];
730 8
        $setValues = [];
731 8
        $set = [];
732
733 8
        foreach ($data as $columnName => $value) {
734 8
            $setColumns[] = $columnName;
735 8
            $setValues[] = $value;
736 8
            $set[] = $columnName . ' = ?';
737
        }
738
739 8
        list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
740 8
        $columns = array_merge($setColumns, $conditionColumns);
741 8
        $values = array_merge($setValues, $conditionValues);
742
743 8
        if (is_string(key($types))) {
744 5
            $types = $this->extractTypeValues($columns, $types);
745
        }
746
747 8
        $sql  = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
748 8
                . ' WHERE ' . implode(' AND ', $conditions);
749
750 8
        return $this->executeUpdate($sql, $values, $types);
751
    }
752
753
    /**
754
     * Inserts a table row with specified data.
755
     *
756
     * Table expression and columns are not escaped and are not safe for user-input.
757
     *
758
     * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
759
     * @param array  $data      An associative array containing column-value pairs.
760
     * @param array  $types     Types of the inserted data.
761
     *
762
     * @return integer The number of affected rows.
763
     *
764
     * @throws \Doctrine\DBAL\DBALException
765
     */
766 78
    public function insert($tableExpression, array $data, array $types = [])
767
    {
768 78
        if (empty($data)) {
769 1
            return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
770
        }
771
772 77
        $columns = [];
773 77
        $values = [];
774 77
        $set = [];
775
776 77
        foreach ($data as $columnName => $value) {
777 77
            $columns[] = $columnName;
778 77
            $values[] = $value;
779 77
            $set[] = '?';
780
        }
781
782 77
        return $this->executeUpdate(
783 77
            'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
784 77
            ' VALUES (' . implode(', ', $set) . ')',
785 77
            $values,
786 77
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
787
        );
788
    }
789
790
    /**
791
     * Extract ordered type list from an ordered column list and type map.
792
     *
793
     * @param array $columnList
794
     * @param array $types
795
     *
796
     * @return array
797
     */
798 9
    private function extractTypeValues(array $columnList, array $types)
799
    {
800 9
        $typeValues = [];
801
802 9
        foreach ($columnList as $columnIndex => $columnName) {
803 9
            $typeValues[] = isset($types[$columnName])
804 9
                ? $types[$columnName]
805 9
                : \PDO::PARAM_STR;
806
        }
807
808 9
        return $typeValues;
809
    }
810
811
    /**
812
     * Quotes a string so it can be safely used as a table or column name, even if
813
     * it is a reserved name.
814
     *
815
     * Delimiting style depends on the underlying database platform that is being used.
816
     *
817
     * NOTE: Just because you CAN use quoted identifiers does not mean
818
     * you SHOULD use them. In general, they end up causing way more
819
     * problems than they solve.
820
     *
821
     * @param string $str The name to be quoted.
822
     *
823
     * @return string The quoted name.
824
     */
825 1
    public function quoteIdentifier($str)
826
    {
827 1
        return $this->getDatabasePlatform()->quoteIdentifier($str);
828
    }
829
830
    /**
831
     * Quotes a given input parameter.
832
     *
833
     * @param mixed       $input The parameter to be quoted.
834
     * @param int|null $type  The type of the parameter.
835
     *
836
     * @return string The quoted parameter.
837
     */
838 4
    public function quote($input, $type = null)
839
    {
840 4
        $this->connect();
841
842 4
        list($value, $bindingType) = $this->getBindingInfo($input, $type);
843
844 4
        return $this->_conn->quote($value, $bindingType);
845
    }
846
847
    /**
848
     * Prepares and executes an SQL query and returns the result as an associative array.
849
     *
850
     * @param string $sql    The SQL query.
851
     * @param array  $params The query parameters.
852
     * @param array  $types  The query parameter types.
853
     *
854
     * @return array
855
     */
856 87
    public function fetchAll($sql, array $params = [], $types = [])
857
    {
858 87
        return $this->executeQuery($sql, $params, $types)->fetchAll();
859
    }
860
861
    /**
862
     * Prepares an SQL statement.
863
     *
864
     * @param string $statement The SQL statement to prepare.
865
     *
866
     * @return \Doctrine\DBAL\Statement The prepared statement.
867
     *
868
     * @throws \Doctrine\DBAL\DBALException
869
     */
870 40
    public function prepare($statement)
871
    {
872
        try {
873 40
            $stmt = new Statement($statement, $this);
874 1
        } catch (Exception $ex) {
875 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
876
        }
877
878 39
        $stmt->setFetchMode($this->defaultFetchMode);
879
880 39
        return $stmt;
881
    }
882
883
    /**
884
     * Executes an, optionally parametrized, SQL query.
885
     *
886
     * If the query is parametrized, a prepared statement is used.
887
     * If an SQLLogger is configured, the execution is logged.
888
     *
889
     * @param string                                      $query  The SQL query to execute.
890
     * @param array                                       $params The parameters to bind to the query, if any.
891
     * @param array                                       $types  The types the previous parameters are in.
892
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
893
     *
894
     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
895
     *
896
     * @throws \Doctrine\DBAL\DBALException
897
     */
898 183
    public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null)
899
    {
900 183
        if ($qcp !== null) {
901 10
            return $this->executeCacheQuery($query, $params, $types, $qcp);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->executeCac... $params, $types, $qcp) returns the type Doctrine\DBAL\Cache\Resu...AL\Cache\ArrayStatement which is incompatible with the documented return type Doctrine\DBAL\Driver\Statement.
Loading history...
902
        }
903
904 183
        $this->connect();
905
906 183
        $logger = $this->_config->getSQLLogger();
907 183
        if ($logger) {
908 178
            $logger->startQuery($query, $params, $types);
909
        }
910
911
        try {
912 183 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...
913 31
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
914
915 31
                $stmt = $this->_conn->prepare($query);
916 31
                if ($types) {
917 15
                    $this->_bindTypedValues($stmt, $params, $types);
918 15
                    $stmt->execute();
919
                } else {
920 31
                    $stmt->execute($params);
921
                }
922
            } else {
923 179
                $stmt = $this->_conn->query($query);
924
            }
925 8
        } catch (Exception $ex) {
926 8
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
927
        }
928
929 175
        $stmt->setFetchMode($this->defaultFetchMode);
930
931 175
        if ($logger) {
932 171
            $logger->stopQuery();
933
        }
934
935 175
        return $stmt;
936
    }
937
938
    /**
939
     * Executes a caching query.
940
     *
941
     * @param string                                 $query  The SQL query to execute.
942
     * @param array                                  $params The parameters to bind to the query, if any.
943
     * @param array                                  $types  The types the previous parameters are in.
944
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
945
     *
946
     * @return \Doctrine\DBAL\Driver\ResultStatement
947
     *
948
     * @throws \Doctrine\DBAL\Cache\CacheException
949
     */
950 12
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
951
    {
952 12
        $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
953 12
        if ( ! $resultCache) {
954
            throw CacheException::noResultDriverConfigured();
955
        }
956
957 12
        list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types, $this->getParams());
958
959
        // fetch the row pointers entry
960 12
        if ($data = $resultCache->fetch($cacheKey)) {
961
            // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
962 9
            if (isset($data[$realKey])) {
963 9
                $stmt = new ArrayStatement($data[$realKey]);
964
            } elseif (array_key_exists($realKey, $data)) {
965
                $stmt = new ArrayStatement([]);
966
            }
967
        }
968
969 12
        if (!isset($stmt)) {
970 10
            $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
971
        }
972
973 12
        $stmt->setFetchMode($this->defaultFetchMode);
974
975 12
        return $stmt;
976
    }
977
978
    /**
979
     * Executes an, optionally parametrized, SQL query and returns the result,
980
     * applying a given projection/transformation function on each row of the result.
981
     *
982
     * @param string   $query    The SQL query to execute.
983
     * @param array    $params   The parameters, if any.
984
     * @param \Closure $function The transformation function that is applied on each row.
985
     *                           The function receives a single parameter, an array, that
986
     *                           represents a row of the result set.
987
     *
988
     * @return array The projected result of the query.
989
     */
990
    public function project($query, array $params, Closure $function)
991
    {
992
        $result = [];
993
        $stmt = $this->executeQuery($query, $params);
994
995
        while ($row = $stmt->fetch()) {
996
            $result[] = $function($row);
997
        }
998
999
        $stmt->closeCursor();
1000
1001
        return $result;
1002
    }
1003
1004
    /**
1005
     * Executes an SQL statement, returning a result set as a Statement object.
1006
     *
1007
     * @return \Doctrine\DBAL\Driver\Statement
1008
     *
1009
     * @throws \Doctrine\DBAL\DBALException
1010
     */
1011 8
    public function query()
1012
    {
1013 8
        $this->connect();
1014
1015 8
        $args = func_get_args();
1016
1017 8
        $logger = $this->_config->getSQLLogger();
1018 8
        if ($logger) {
1019 7
            $logger->startQuery($args[0]);
1020
        }
1021
1022
        try {
1023 8
            $statement = $this->_conn->query(...$args);
1024 1
        } catch (Exception $ex) {
1025 1
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
1026
        }
1027
1028 7
        $statement->setFetchMode($this->defaultFetchMode);
1029
1030 7
        if ($logger) {
1031 7
            $logger->stopQuery();
1032
        }
1033
1034 7
        return $statement;
1035
    }
1036
1037
    /**
1038
     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
1039
     * and returns the number of affected rows.
1040
     *
1041
     * This method supports PDO binding types as well as DBAL mapping types.
1042
     *
1043
     * @param string $query  The SQL query.
1044
     * @param array  $params The query parameters.
1045
     * @param array  $types  The parameter types.
1046
     *
1047
     * @return integer The number of affected rows.
1048
     *
1049
     * @throws \Doctrine\DBAL\DBALException
1050
     */
1051 141
    public function executeUpdate($query, array $params = [], array $types = [])
1052
    {
1053 141
        $this->connect();
1054
1055 141
        $logger = $this->_config->getSQLLogger();
1056 141
        if ($logger) {
1057 136
            $logger->startQuery($query, $params, $types);
1058
        }
1059
1060
        try {
1061 141 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...
1062 83
                list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
1063
1064 83
                $stmt = $this->_conn->prepare($query);
1065 82
                if ($types) {
1066 12
                    $this->_bindTypedValues($stmt, $params, $types);
1067 12
                    $stmt->execute();
1068
                } else {
1069 70
                    $stmt->execute($params);
1070
                }
1071 81
                $result = $stmt->rowCount();
1072
            } else {
1073 139
                $result = $this->_conn->exec($query);
1074
            }
1075 52
        } catch (Exception $ex) {
1076 52
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
1077
        }
1078
1079 138
        if ($logger) {
1080 134
            $logger->stopQuery();
1081
        }
1082
1083 138
        return $result;
1084
    }
1085
1086
    /**
1087
     * Executes an SQL statement and return the number of affected rows.
1088
     *
1089
     * @param string $statement
1090
     *
1091
     * @return integer The number of affected rows.
1092
     *
1093
     * @throws \Doctrine\DBAL\DBALException
1094
     */
1095 33
    public function exec($statement)
1096
    {
1097 33
        $this->connect();
1098
1099 32
        $logger = $this->_config->getSQLLogger();
1100 32
        if ($logger) {
1101 28
            $logger->startQuery($statement);
1102
        }
1103
1104
        try {
1105 32
            $result = $this->_conn->exec($statement);
1106 4
        } catch (Exception $ex) {
1107 4
            throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
1108
        }
1109
1110 30
        if ($logger) {
1111 28
            $logger->stopQuery();
1112
        }
1113
1114 30
        return $result;
1115
    }
1116
1117
    /**
1118
     * Returns the current transaction nesting level.
1119
     *
1120
     * @return integer The nesting level. A value of 0 means there's no active transaction.
1121
     */
1122 15
    public function getTransactionNestingLevel()
1123
    {
1124 15
        return $this->_transactionNestingLevel;
1125
    }
1126
1127
    /**
1128
     * Fetches the SQLSTATE associated with the last database operation.
1129
     *
1130
     * @return integer The last error code.
1131
     */
1132
    public function errorCode()
1133
    {
1134
        $this->connect();
1135
1136
        return $this->_conn->errorCode();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_conn->errorCode() also could return the type string which is incompatible with the documented return type integer.
Loading history...
1137
    }
1138
1139
    /**
1140
     * Fetches extended error information associated with the last database operation.
1141
     *
1142
     * @return array The last error information.
1143
     */
1144
    public function errorInfo()
1145
    {
1146
        $this->connect();
1147
1148
        return $this->_conn->errorInfo();
1149
    }
1150
1151
    /**
1152
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1153
     * depending on the underlying driver.
1154
     *
1155
     * Note: This method may not return a meaningful or consistent result across different drivers,
1156
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1157
     * columns or sequences.
1158
     *
1159
     * @param string|null $seqName Name of the sequence object from which the ID should be returned.
1160
     *
1161
     * @return string A string representation of the last inserted ID.
1162
     */
1163 2
    public function lastInsertId($seqName = null)
1164
    {
1165 2
        $this->connect();
1166
1167 2
        return $this->_conn->lastInsertId($seqName);
1168
    }
1169
1170
    /**
1171
     * Executes a function in a transaction.
1172
     *
1173
     * The function gets passed this Connection instance as an (optional) parameter.
1174
     *
1175
     * If an exception occurs during execution of the function or transaction commit,
1176
     * the transaction is rolled back and the exception re-thrown.
1177
     *
1178
     * @param \Closure $func The function to execute transactionally.
1179
     *
1180
     * @return mixed The value returned by $func
1181
     *
1182
     * @throws Exception
1183
     * @throws Throwable
1184
     */
1185 4
    public function transactional(Closure $func)
1186
    {
1187 4
        $this->beginTransaction();
1188
        try {
1189 4
            $res = $func($this);
1190 2
            $this->commit();
1191 2
            return $res;
1192 2
        } catch (Exception $e) {
1193 1
            $this->rollBack();
1194 1
            throw $e;
1195 1
        } catch (Throwable $e) {
1196 1
            $this->rollBack();
1197 1
            throw $e;
1198
        }
1199
    }
1200
1201
    /**
1202
     * Sets if nested transactions should use savepoints.
1203
     *
1204
     * @param boolean $nestTransactionsWithSavepoints
1205
     *
1206
     * @return void
1207
     *
1208
     * @throws \Doctrine\DBAL\ConnectionException
1209
     */
1210 2
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1211
    {
1212 2
        if ($this->_transactionNestingLevel > 0) {
1213 2
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1214
        }
1215
1216 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1217
            throw ConnectionException::savepointsNotSupported();
1218
        }
1219
1220 1
        $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1221 1
    }
1222
1223
    /**
1224
     * Gets if nested transactions should use savepoints.
1225
     *
1226
     * @return boolean
1227
     */
1228 1
    public function getNestTransactionsWithSavepoints()
1229
    {
1230 1
        return $this->_nestTransactionsWithSavepoints;
1231
    }
1232
1233
    /**
1234
     * Returns the savepoint name to use for nested transactions are false if they are not supported
1235
     * "savepointFormat" parameter is not set
1236
     *
1237
     * @return mixed A string with the savepoint name or false.
1238
     */
1239 1
    protected function _getNestedTransactionSavePointName()
1240
    {
1241 1
        return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
1242
    }
1243
1244
    /**
1245
     * Starts a transaction by suspending auto-commit mode.
1246
     *
1247
     * @return void
1248
     */
1249 18
    public function beginTransaction()
1250
    {
1251 18
        $this->connect();
1252
1253 18
        ++$this->_transactionNestingLevel;
1254
1255 18
        $logger = $this->_config->getSQLLogger();
1256
1257 18 View Code Duplication
        if ($this->_transactionNestingLevel == 1) {
1258 18
            if ($logger) {
1259 13
                $logger->startQuery('"START TRANSACTION"');
1260
            }
1261 18
            $this->_conn->beginTransaction();
1262 18
            if ($logger) {
1263 18
                $logger->stopQuery();
1264
            }
1265 3
        } elseif ($this->_nestTransactionsWithSavepoints) {
1266 1
            if ($logger) {
1267 1
                $logger->startQuery('"SAVEPOINT"');
1268
            }
1269 1
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1270 1
            if ($logger) {
1271 1
                $logger->stopQuery();
1272
            }
1273
        }
1274 18
    }
1275
1276
    /**
1277
     * Commits the current transaction.
1278
     *
1279
     * @return void
1280
     *
1281
     * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or
1282
     *                                            because the transaction was marked for rollback only.
1283
     */
1284 9
    public function commit()
1285
    {
1286 9
        if ($this->_transactionNestingLevel == 0) {
1287 1
            throw ConnectionException::noActiveTransaction();
1288
        }
1289 8
        if ($this->_isRollbackOnly) {
1290 2
            throw ConnectionException::commitFailedRollbackOnly();
1291
        }
1292
1293 6
        $this->connect();
1294
1295 6
        $logger = $this->_config->getSQLLogger();
1296
1297 6 View Code Duplication
        if ($this->_transactionNestingLevel == 1) {
1298 6
            if ($logger) {
1299 4
                $logger->startQuery('"COMMIT"');
1300
            }
1301 6
            $this->_conn->commit();
1302 6
            if ($logger) {
1303 6
                $logger->stopQuery();
1304
            }
1305 2
        } elseif ($this->_nestTransactionsWithSavepoints) {
1306 1
            if ($logger) {
1307 1
                $logger->startQuery('"RELEASE SAVEPOINT"');
1308
            }
1309 1
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1310 1
            if ($logger) {
1311 1
                $logger->stopQuery();
1312
            }
1313
        }
1314
1315 6
        --$this->_transactionNestingLevel;
1316
1317 6
        if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) {
1318 2
            $this->beginTransaction();
1319
        }
1320 6
    }
1321
1322
    /**
1323
     * Commits all current nesting transactions.
1324
     */
1325 1
    private function commitAll()
1326
    {
1327 1
        while (0 !== $this->_transactionNestingLevel) {
1328 1
            if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) {
1329
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1330
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1331 1
                $this->commit();
1332
1333 1
                return;
1334
            }
1335
1336 1
            $this->commit();
1337
        }
1338 1
    }
1339
1340
    /**
1341
     * Cancels any database changes done during the current transaction.
1342
     *
1343
     * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
1344
     */
1345 12
    public function rollBack()
1346
    {
1347 12
        if ($this->_transactionNestingLevel == 0) {
1348 1
            throw ConnectionException::noActiveTransaction();
1349
        }
1350
1351 11
        $this->connect();
1352
1353 11
        $logger = $this->_config->getSQLLogger();
1354
1355 11
        if ($this->_transactionNestingLevel == 1) {
1356 10
            if ($logger) {
1357 9
                $logger->startQuery('"ROLLBACK"');
1358
            }
1359 10
            $this->_transactionNestingLevel = 0;
1360 10
            $this->_conn->rollBack();
1361 10
            $this->_isRollbackOnly = false;
1362 10
            if ($logger) {
1363 9
                $logger->stopQuery();
1364
            }
1365
1366 10
            if (false === $this->autoCommit) {
1367 10
                $this->beginTransaction();
1368
            }
1369 2
        } elseif ($this->_nestTransactionsWithSavepoints) {
1370 1
            if ($logger) {
1371 1
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1372
            }
1373 1
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1374 1
            --$this->_transactionNestingLevel;
1375 1
            if ($logger) {
1376 1
                $logger->stopQuery();
1377
            }
1378
        } else {
1379 1
            $this->_isRollbackOnly = true;
1380 1
            --$this->_transactionNestingLevel;
1381
        }
1382 11
    }
1383
1384
    /**
1385
     * Creates a new savepoint.
1386
     *
1387
     * @param string $savepoint The name of the savepoint to create.
1388
     *
1389
     * @return void
1390
     *
1391
     * @throws \Doctrine\DBAL\ConnectionException
1392
     */
1393 1
    public function createSavepoint($savepoint)
1394
    {
1395 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1396
            throw ConnectionException::savepointsNotSupported();
1397
        }
1398
1399 1
        $this->_conn->exec($this->platform->createSavePoint($savepoint));
1400 1
    }
1401
1402
    /**
1403
     * Releases the given savepoint.
1404
     *
1405
     * @param string $savepoint The name of the savepoint to release.
1406
     *
1407
     * @return void
1408
     *
1409
     * @throws \Doctrine\DBAL\ConnectionException
1410
     */
1411 1
    public function releaseSavepoint($savepoint)
1412
    {
1413 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1414
            throw ConnectionException::savepointsNotSupported();
1415
        }
1416
1417 1
        if ($this->platform->supportsReleaseSavepoints()) {
1418 1
            $this->_conn->exec($this->platform->releaseSavePoint($savepoint));
1419
        }
1420 1
    }
1421
1422
    /**
1423
     * Rolls back to the given savepoint.
1424
     *
1425
     * @param string $savepoint The name of the savepoint to rollback to.
1426
     *
1427
     * @return void
1428
     *
1429
     * @throws \Doctrine\DBAL\ConnectionException
1430
     */
1431 1
    public function rollbackSavepoint($savepoint)
1432
    {
1433 1
        if ( ! $this->getDatabasePlatform()->supportsSavepoints()) {
1434
            throw ConnectionException::savepointsNotSupported();
1435
        }
1436
1437 1
        $this->_conn->exec($this->platform->rollbackSavePoint($savepoint));
1438 1
    }
1439
1440
    /**
1441
     * Gets the wrapped driver connection.
1442
     *
1443
     * @return \Doctrine\DBAL\Driver\Connection
1444
     */
1445 47
    public function getWrappedConnection()
1446
    {
1447 47
        $this->connect();
1448
1449 47
        return $this->_conn;
1450
    }
1451
1452
    /**
1453
     * Gets the SchemaManager that can be used to inspect or change the
1454
     * database schema through the connection.
1455
     *
1456
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
1457
     */
1458 141
    public function getSchemaManager()
1459
    {
1460 141
        if ( ! $this->_schemaManager) {
1461 11
            $this->_schemaManager = $this->_driver->getSchemaManager($this);
1462
        }
1463
1464 141
        return $this->_schemaManager;
1465
    }
1466
1467
    /**
1468
     * Marks the current transaction so that the only possible
1469
     * outcome for the transaction to be rolled back.
1470
     *
1471
     * @return void
1472
     *
1473
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1474
     */
1475 2
    public function setRollbackOnly()
1476
    {
1477 2
        if ($this->_transactionNestingLevel == 0) {
1478 1
            throw ConnectionException::noActiveTransaction();
1479
        }
1480 1
        $this->_isRollbackOnly = true;
1481 1
    }
1482
1483
    /**
1484
     * Checks whether the current transaction is marked for rollback only.
1485
     *
1486
     * @return boolean
1487
     *
1488
     * @throws \Doctrine\DBAL\ConnectionException If no transaction is active.
1489
     */
1490 3
    public function isRollbackOnly()
1491
    {
1492 3
        if ($this->_transactionNestingLevel == 0) {
1493 1
            throw ConnectionException::noActiveTransaction();
1494
        }
1495
1496 2
        return $this->_isRollbackOnly;
1497
    }
1498
1499
    /**
1500
     * Converts a given value to its database representation according to the conversion
1501
     * rules of a specific DBAL mapping type.
1502
     *
1503
     * @param mixed  $value The value to convert.
1504
     * @param string $type  The name of the DBAL mapping type.
1505
     *
1506
     * @return mixed The converted value.
1507
     */
1508
    public function convertToDatabaseValue($value, $type)
1509
    {
1510
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1511
    }
1512
1513
    /**
1514
     * Converts a given value to its PHP representation according to the conversion
1515
     * rules of a specific DBAL mapping type.
1516
     *
1517
     * @param mixed  $value The value to convert.
1518
     * @param string $type  The name of the DBAL mapping type.
1519
     *
1520
     * @return mixed The converted type.
1521
     */
1522
    public function convertToPHPValue($value, $type)
1523
    {
1524
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1525
    }
1526
1527
    /**
1528
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1529
     * or DBAL mapping type, to a given statement.
1530
     *
1531
     * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
1532
     * @param array                           $params The map/list of named/positional parameters.
1533
     * @param array                           $types  The parameter types (PDO binding types or DBAL mapping types).
1534
     *
1535
     * @return void
1536
     *
1537
     * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
1538
     *           raw PDOStatement instances.
1539
     */
1540 26
    private function _bindTypedValues($stmt, array $params, array $types)
1541
    {
1542
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1543 26
        if (is_int(key($params))) {
1544
            // Positional parameters
1545 26
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1546 26
            $bindIndex = 1;
1547 26
            foreach ($params as $value) {
1548 26
                $typeIndex = $bindIndex + $typeOffset;
1549 26 View Code Duplication
                if (isset($types[$typeIndex])) {
1550 26
                    $type = $types[$typeIndex];
1551 26
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1552 26
                    $stmt->bindValue($bindIndex, $value, $bindingType);
1553
                } else {
1554 1
                    $stmt->bindValue($bindIndex, $value);
1555
                }
1556 26
                ++$bindIndex;
1557
            }
1558
        } else {
1559
            // Named parameters
1560
            foreach ($params as $name => $value) {
1561 View Code Duplication
                if (isset($types[$name])) {
1562
                    $type = $types[$name];
1563
                    list($value, $bindingType) = $this->getBindingInfo($value, $type);
1564
                    $stmt->bindValue($name, $value, $bindingType);
1565
                } else {
1566
                    $stmt->bindValue($name, $value);
1567
                }
1568
            }
1569
        }
1570 26
    }
1571
1572
    /**
1573
     * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
1574
     *
1575
     * @param mixed $value The value to bind.
1576
     * @param mixed $type  The type to bind (PDO or DBAL).
1577
     *
1578
     * @return array [0] => the (escaped) value, [1] => the binding type.
1579
     */
1580 30
    private function getBindingInfo($value, $type)
1581
    {
1582 30
        if (is_string($type)) {
1583 12
            $type = Type::getType($type);
1584
        }
1585 30 View Code Duplication
        if ($type instanceof Type) {
1586 12
            $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1587 12
            $bindingType = $type->getBindingType();
1588
        } else {
1589 24
            $bindingType = $type; // PDO::PARAM_* constants
1590
        }
1591
1592 30
        return [$value, $bindingType];
1593
    }
1594
1595
    /**
1596
     * Resolves the parameters to a format which can be displayed.
1597
     *
1598
     * @internal This is a purely internal method. If you rely on this method, you are advised to
1599
     *           copy/paste the code as this method may change, or be removed without prior notice.
1600
     *
1601
     * @param array $params
1602
     * @param array $types
1603
     *
1604
     * @return array
1605
     */
1606 60
    public function resolveParams(array $params, array $types)
1607
    {
1608 60
        $resolvedParams = [];
1609
1610
        // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
1611 60
        if (is_int(key($params))) {
1612
            // Positional parameters
1613 8
            $typeOffset = array_key_exists(0, $types) ? -1 : 0;
1614 8
            $bindIndex = 1;
1615 8
            foreach ($params as $value) {
1616 8
                $typeIndex = $bindIndex + $typeOffset;
1617 8 View Code Duplication
                if (isset($types[$typeIndex])) {
1618
                    $type = $types[$typeIndex];
1619
                    list($value,) = $this->getBindingInfo($value, $type);
1620
                    $resolvedParams[$bindIndex] = $value;
1621
                } else {
1622 8
                    $resolvedParams[$bindIndex] = $value;
1623
                }
1624 8
                ++$bindIndex;
1625
            }
1626
        } else {
1627
            // Named parameters
1628 52
            foreach ($params as $name => $value) {
1629 View Code Duplication
                if (isset($types[$name])) {
1630
                    $type = $types[$name];
1631
                    list($value,) = $this->getBindingInfo($value, $type);
1632
                    $resolvedParams[$name] = $value;
1633
                } else {
1634
                    $resolvedParams[$name] = $value;
1635
                }
1636
            }
1637
        }
1638
1639 60
        return $resolvedParams;
1640
    }
1641
1642
    /**
1643
     * Creates a new instance of a SQL query builder.
1644
     *
1645
     * @return \Doctrine\DBAL\Query\QueryBuilder
1646
     */
1647
    public function createQueryBuilder()
1648
    {
1649
        return new Query\QueryBuilder($this);
1650
    }
1651
1652
    /**
1653
     * Ping the server
1654
     *
1655
     * When the server is not available the method returns FALSE.
1656
     * It is responsibility of the developer to handle this case
1657
     * and abort the request or reconnect manually:
1658
     *
1659
     * @example
1660
     *
1661
     *   if ($conn->ping() === false) {
1662
     *      $conn->close();
1663
     *      $conn->connect();
1664
     *   }
1665
     *
1666
     * It is undefined if the underlying driver attempts to reconnect
1667
     * or disconnect when the connection is not available anymore
1668
     * as long it returns TRUE when a reconnect succeeded and
1669
     * FALSE when the connection was dropped.
1670
     *
1671
     * @return bool
1672
     */
1673 1
    public function ping()
1674
    {
1675 1
        $this->connect();
1676
1677 1
        if ($this->_conn instanceof PingableConnection) {
1678
            return $this->_conn->ping();
1679
        }
1680
1681
        try {
1682 1
            $this->query($this->getDatabasePlatform()->getDummySelectSQL());
1683
1684 1
            return true;
1685
        } catch (DBALException $e) {
1686
            return false;
1687
        }
1688
    }
1689
}
1690