vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php line 890

Open in your IDE?
  1. <?php
  2. namespace Doctrine\DBAL;
  3. use Closure;
  4. use Doctrine\Common\EventManager;
  5. use Doctrine\DBAL\Cache\ArrayStatement;
  6. use Doctrine\DBAL\Cache\CacheException;
  7. use Doctrine\DBAL\Cache\QueryCacheProfile;
  8. use Doctrine\DBAL\Cache\ResultCacheStatement;
  9. use Doctrine\DBAL\Driver\Connection as DriverConnection;
  10. use Doctrine\DBAL\Driver\PingableConnection;
  11. use Doctrine\DBAL\Driver\ResultStatement;
  12. use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
  13. use Doctrine\DBAL\Driver\Statement as DriverStatement;
  14. use Doctrine\DBAL\Exception\InvalidArgumentException;
  15. use Doctrine\DBAL\Platforms\AbstractPlatform;
  16. use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
  17. use Doctrine\DBAL\Query\QueryBuilder;
  18. use Doctrine\DBAL\Schema\AbstractSchemaManager;
  19. use Doctrine\DBAL\Types\Type;
  20. use Exception;
  21. use Throwable;
  22. use function array_key_exists;
  23. use function assert;
  24. use function func_get_args;
  25. use function implode;
  26. use function is_int;
  27. use function is_string;
  28. use function key;
  29. /**
  30.  * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
  31.  * events, transaction isolation levels, configuration, emulated transaction nesting,
  32.  * lazy connecting and more.
  33.  */
  34. class Connection implements DriverConnection
  35. {
  36.     /**
  37.      * Constant for transaction isolation level READ UNCOMMITTED.
  38.      *
  39.      * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED.
  40.      */
  41.     public const TRANSACTION_READ_UNCOMMITTED TransactionIsolationLevel::READ_UNCOMMITTED;
  42.     /**
  43.      * Constant for transaction isolation level READ COMMITTED.
  44.      *
  45.      * @deprecated Use TransactionIsolationLevel::READ_COMMITTED.
  46.      */
  47.     public const TRANSACTION_READ_COMMITTED TransactionIsolationLevel::READ_COMMITTED;
  48.     /**
  49.      * Constant for transaction isolation level REPEATABLE READ.
  50.      *
  51.      * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ.
  52.      */
  53.     public const TRANSACTION_REPEATABLE_READ TransactionIsolationLevel::REPEATABLE_READ;
  54.     /**
  55.      * Constant for transaction isolation level SERIALIZABLE.
  56.      *
  57.      * @deprecated Use TransactionIsolationLevel::SERIALIZABLE.
  58.      */
  59.     public const TRANSACTION_SERIALIZABLE TransactionIsolationLevel::SERIALIZABLE;
  60.     /**
  61.      * Represents an array of ints to be expanded by Doctrine SQL parsing.
  62.      */
  63.     public const PARAM_INT_ARRAY ParameterType::INTEGER self::ARRAY_PARAM_OFFSET;
  64.     /**
  65.      * Represents an array of strings to be expanded by Doctrine SQL parsing.
  66.      */
  67.     public const PARAM_STR_ARRAY ParameterType::STRING self::ARRAY_PARAM_OFFSET;
  68.     /**
  69.      * Offset by which PARAM_* constants are detected as arrays of the param type.
  70.      */
  71.     public const ARRAY_PARAM_OFFSET 100;
  72.     /**
  73.      * The wrapped driver connection.
  74.      *
  75.      * @var \Doctrine\DBAL\Driver\Connection|null
  76.      */
  77.     protected $_conn;
  78.     /** @var Configuration */
  79.     protected $_config;
  80.     /** @var EventManager */
  81.     protected $_eventManager;
  82.     /** @var ExpressionBuilder */
  83.     protected $_expr;
  84.     /**
  85.      * Whether or not a connection has been established.
  86.      *
  87.      * @var bool
  88.      */
  89.     private $isConnected false;
  90.     /**
  91.      * The current auto-commit mode of this connection.
  92.      *
  93.      * @var bool
  94.      */
  95.     private $autoCommit true;
  96.     /**
  97.      * The transaction nesting level.
  98.      *
  99.      * @var int
  100.      */
  101.     private $transactionNestingLevel 0;
  102.     /**
  103.      * The currently active transaction isolation level.
  104.      *
  105.      * @var int
  106.      */
  107.     private $transactionIsolationLevel;
  108.     /**
  109.      * If nested transactions should use savepoints.
  110.      *
  111.      * @var bool
  112.      */
  113.     private $nestTransactionsWithSavepoints false;
  114.     /**
  115.      * The parameters used during creation of the Connection instance.
  116.      *
  117.      * @var mixed[]
  118.      */
  119.     private $params = [];
  120.     /**
  121.      * The DatabasePlatform object that provides information about the
  122.      * database platform used by the connection.
  123.      *
  124.      * @var AbstractPlatform
  125.      */
  126.     private $platform;
  127.     /**
  128.      * The schema manager.
  129.      *
  130.      * @var AbstractSchemaManager|null
  131.      */
  132.     protected $_schemaManager;
  133.     /**
  134.      * The used DBAL driver.
  135.      *
  136.      * @var Driver
  137.      */
  138.     protected $_driver;
  139.     /**
  140.      * Flag that indicates whether the current transaction is marked for rollback only.
  141.      *
  142.      * @var bool
  143.      */
  144.     private $isRollbackOnly false;
  145.     /** @var int */
  146.     protected $defaultFetchMode FetchMode::ASSOCIATIVE;
  147.     /**
  148.      * Initializes a new instance of the Connection class.
  149.      *
  150.      * @param mixed[]            $params       The connection parameters.
  151.      * @param Driver             $driver       The driver to use.
  152.      * @param Configuration|null $config       The configuration, optional.
  153.      * @param EventManager|null  $eventManager The event manager, optional.
  154.      *
  155.      * @throws DBALException
  156.      */
  157.     public function __construct(
  158.         array $params,
  159.         Driver $driver,
  160.         ?Configuration $config null,
  161.         ?EventManager $eventManager null
  162.     ) {
  163.         $this->_driver $driver;
  164.         $this->params  $params;
  165.         if (isset($params['pdo'])) {
  166.             $this->_conn       $params['pdo'];
  167.             $this->isConnected true;
  168.             unset($this->params['pdo']);
  169.         }
  170.         if (isset($params['platform'])) {
  171.             if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
  172.                 throw DBALException::invalidPlatformType($params['platform']);
  173.             }
  174.             $this->platform $params['platform'];
  175.         }
  176.         // Create default config and event manager if none given
  177.         if (! $config) {
  178.             $config = new Configuration();
  179.         }
  180.         if (! $eventManager) {
  181.             $eventManager = new EventManager();
  182.         }
  183.         $this->_config       $config;
  184.         $this->_eventManager $eventManager;
  185.         $this->_expr = new Query\Expression\ExpressionBuilder($this);
  186.         $this->autoCommit $config->getAutoCommit();
  187.     }
  188.     /**
  189.      * Gets the parameters used during instantiation.
  190.      *
  191.      * @return mixed[]
  192.      */
  193.     public function getParams()
  194.     {
  195.         return $this->params;
  196.     }
  197.     /**
  198.      * Gets the name of the database this Connection is connected to.
  199.      *
  200.      * @return string
  201.      */
  202.     public function getDatabase()
  203.     {
  204.         return $this->_driver->getDatabase($this);
  205.     }
  206.     /**
  207.      * Gets the hostname of the currently connected database.
  208.      *
  209.      * @deprecated
  210.      *
  211.      * @return string|null
  212.      */
  213.     public function getHost()
  214.     {
  215.         return $this->params['host'] ?? null;
  216.     }
  217.     /**
  218.      * Gets the port of the currently connected database.
  219.      *
  220.      * @deprecated
  221.      *
  222.      * @return mixed
  223.      */
  224.     public function getPort()
  225.     {
  226.         return $this->params['port'] ?? null;
  227.     }
  228.     /**
  229.      * Gets the username used by this connection.
  230.      *
  231.      * @deprecated
  232.      *
  233.      * @return string|null
  234.      */
  235.     public function getUsername()
  236.     {
  237.         return $this->params['user'] ?? null;
  238.     }
  239.     /**
  240.      * Gets the password used by this connection.
  241.      *
  242.      * @deprecated
  243.      *
  244.      * @return string|null
  245.      */
  246.     public function getPassword()
  247.     {
  248.         return $this->params['password'] ?? null;
  249.     }
  250.     /**
  251.      * Gets the DBAL driver instance.
  252.      *
  253.      * @return Driver
  254.      */
  255.     public function getDriver()
  256.     {
  257.         return $this->_driver;
  258.     }
  259.     /**
  260.      * Gets the Configuration used by the Connection.
  261.      *
  262.      * @return Configuration
  263.      */
  264.     public function getConfiguration()
  265.     {
  266.         return $this->_config;
  267.     }
  268.     /**
  269.      * Gets the EventManager used by the Connection.
  270.      *
  271.      * @return EventManager
  272.      */
  273.     public function getEventManager()
  274.     {
  275.         return $this->_eventManager;
  276.     }
  277.     /**
  278.      * Gets the DatabasePlatform for the connection.
  279.      *
  280.      * @return AbstractPlatform
  281.      *
  282.      * @throws DBALException
  283.      */
  284.     public function getDatabasePlatform()
  285.     {
  286.         if ($this->platform === null) {
  287.             $this->detectDatabasePlatform();
  288.         }
  289.         return $this->platform;
  290.     }
  291.     /**
  292.      * Gets the ExpressionBuilder for the connection.
  293.      *
  294.      * @return ExpressionBuilder
  295.      */
  296.     public function getExpressionBuilder()
  297.     {
  298.         return $this->_expr;
  299.     }
  300.     /**
  301.      * Establishes the connection with the database.
  302.      *
  303.      * @return bool TRUE if the connection was successfully established, FALSE if
  304.      *              the connection is already open.
  305.      */
  306.     public function connect()
  307.     {
  308.         if ($this->isConnected) {
  309.             return false;
  310.         }
  311.         $driverOptions $this->params['driverOptions'] ?? [];
  312.         $user          $this->params['user'] ?? null;
  313.         $password      $this->params['password'] ?? null;
  314.         $this->_conn       $this->_driver->connect($this->params$user$password$driverOptions);
  315.         $this->isConnected true;
  316.         $this->transactionNestingLevel 0;
  317.         if ($this->autoCommit === false) {
  318.             $this->beginTransaction();
  319.         }
  320.         if ($this->_eventManager->hasListeners(Events::postConnect)) {
  321.             $eventArgs = new Event\ConnectionEventArgs($this);
  322.             $this->_eventManager->dispatchEvent(Events::postConnect$eventArgs);
  323.         }
  324.         return true;
  325.     }
  326.     /**
  327.      * Detects and sets the database platform.
  328.      *
  329.      * Evaluates custom platform class and version in order to set the correct platform.
  330.      *
  331.      * @throws DBALException If an invalid platform was specified for this connection.
  332.      */
  333.     private function detectDatabasePlatform()
  334.     {
  335.         $version $this->getDatabasePlatformVersion();
  336.         if ($version !== null) {
  337.             assert($this->_driver instanceof VersionAwarePlatformDriver);
  338.             $this->platform $this->_driver->createDatabasePlatformForVersion($version);
  339.         } else {
  340.             $this->platform $this->_driver->getDatabasePlatform();
  341.         }
  342.         $this->platform->setEventManager($this->_eventManager);
  343.     }
  344.     /**
  345.      * Returns the version of the related platform if applicable.
  346.      *
  347.      * Returns null if either the driver is not capable to create version
  348.      * specific platform instances, no explicit server version was specified
  349.      * or the underlying driver connection cannot determine the platform
  350.      * version without having to query it (performance reasons).
  351.      *
  352.      * @return string|null
  353.      *
  354.      * @throws Exception
  355.      */
  356.     private function getDatabasePlatformVersion()
  357.     {
  358.         // Driver does not support version specific platforms.
  359.         if (! $this->_driver instanceof VersionAwarePlatformDriver) {
  360.             return null;
  361.         }
  362.         // Explicit platform version requested (supersedes auto-detection).
  363.         if (isset($this->params['serverVersion'])) {
  364.             return $this->params['serverVersion'];
  365.         }
  366.         // If not connected, we need to connect now to determine the platform version.
  367.         if ($this->_conn === null) {
  368.             try {
  369.                 $this->connect();
  370.             } catch (Throwable $originalException) {
  371.                 if (empty($this->params['dbname'])) {
  372.                     throw $originalException;
  373.                 }
  374.                 // The database to connect to might not yet exist.
  375.                 // Retry detection without database name connection parameter.
  376.                 $databaseName           $this->params['dbname'];
  377.                 $this->params['dbname'] = null;
  378.                 try {
  379.                     $this->connect();
  380.                 } catch (Throwable $fallbackException) {
  381.                     // Either the platform does not support database-less connections
  382.                     // or something else went wrong.
  383.                     // Reset connection parameters and rethrow the original exception.
  384.                     $this->params['dbname'] = $databaseName;
  385.                     throw $originalException;
  386.                 }
  387.                 // Reset connection parameters.
  388.                 $this->params['dbname'] = $databaseName;
  389.                 $serverVersion          $this->getServerVersion();
  390.                 // Close "temporary" connection to allow connecting to the real database again.
  391.                 $this->close();
  392.                 return $serverVersion;
  393.             }
  394.         }
  395.         return $this->getServerVersion();
  396.     }
  397.     /**
  398.      * Returns the database server version if the underlying driver supports it.
  399.      *
  400.      * @return string|null
  401.      */
  402.     private function getServerVersion()
  403.     {
  404.         $connection $this->getWrappedConnection();
  405.         // Automatic platform version detection.
  406.         if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) {
  407.             return $connection->getServerVersion();
  408.         }
  409.         // Unable to detect platform version.
  410.         return null;
  411.     }
  412.     /**
  413.      * Returns the current auto-commit mode for this connection.
  414.      *
  415.      * @see    setAutoCommit
  416.      *
  417.      * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
  418.      */
  419.     public function isAutoCommit()
  420.     {
  421.         return $this->autoCommit === true;
  422.     }
  423.     /**
  424.      * Sets auto-commit mode for this connection.
  425.      *
  426.      * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
  427.      * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
  428.      * the method commit or the method rollback. By default, new connections are in auto-commit mode.
  429.      *
  430.      * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
  431.      * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
  432.      *
  433.      * @see   isAutoCommit
  434.      *
  435.      * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
  436.      */
  437.     public function setAutoCommit($autoCommit)
  438.     {
  439.         $autoCommit = (bool) $autoCommit;
  440.         // Mode not changed, no-op.
  441.         if ($autoCommit === $this->autoCommit) {
  442.             return;
  443.         }
  444.         $this->autoCommit $autoCommit;
  445.         // Commit all currently active transactions if any when switching auto-commit mode.
  446.         if ($this->isConnected !== true || $this->transactionNestingLevel === 0) {
  447.             return;
  448.         }
  449.         $this->commitAll();
  450.     }
  451.     /**
  452.      * Sets the fetch mode.
  453.      *
  454.      * @param int $fetchMode
  455.      *
  456.      * @return void
  457.      */
  458.     public function setFetchMode($fetchMode)
  459.     {
  460.         $this->defaultFetchMode $fetchMode;
  461.     }
  462.     /**
  463.      * Prepares and executes an SQL query and returns the first row of the result
  464.      * as an associative array.
  465.      *
  466.      * @param string         $statement The SQL query.
  467.      * @param mixed[]        $params    The query parameters.
  468.      * @param int[]|string[] $types     The query parameter types.
  469.      *
  470.      * @return mixed[]|false False is returned if no rows are found.
  471.      *
  472.      * @throws DBALException
  473.      */
  474.     public function fetchAssoc($statement, array $params = [], array $types = [])
  475.     {
  476.         return $this->executeQuery($statement$params$types)->fetch(FetchMode::ASSOCIATIVE);
  477.     }
  478.     /**
  479.      * Prepares and executes an SQL query and returns the first row of the result
  480.      * as a numerically indexed array.
  481.      *
  482.      * @param string         $statement The SQL query to be executed.
  483.      * @param mixed[]        $params    The prepared statement params.
  484.      * @param int[]|string[] $types     The query parameter types.
  485.      *
  486.      * @return mixed[]|false False is returned if no rows are found.
  487.      */
  488.     public function fetchArray($statement, array $params = [], array $types = [])
  489.     {
  490.         return $this->executeQuery($statement$params$types)->fetch(FetchMode::NUMERIC);
  491.     }
  492.     /**
  493.      * Prepares and executes an SQL query and returns the value of a single column
  494.      * of the first row of the result.
  495.      *
  496.      * @param string         $statement The SQL query to be executed.
  497.      * @param mixed[]        $params    The prepared statement params.
  498.      * @param int            $column    The 0-indexed column number to retrieve.
  499.      * @param int[]|string[] $types     The query parameter types.
  500.      *
  501.      * @return mixed|false False is returned if no rows are found.
  502.      *
  503.      * @throws DBALException
  504.      */
  505.     public function fetchColumn($statement, array $params = [], $column 0, array $types = [])
  506.     {
  507.         return $this->executeQuery($statement$params$types)->fetchColumn($column);
  508.     }
  509.     /**
  510.      * Whether an actual connection to the database is established.
  511.      *
  512.      * @return bool
  513.      */
  514.     public function isConnected()
  515.     {
  516.         return $this->isConnected;
  517.     }
  518.     /**
  519.      * Checks whether a transaction is currently active.
  520.      *
  521.      * @return bool TRUE if a transaction is currently active, FALSE otherwise.
  522.      */
  523.     public function isTransactionActive()
  524.     {
  525.         return $this->transactionNestingLevel 0;
  526.     }
  527.     /**
  528.      * Adds identifier condition to the query components
  529.      *
  530.      * @param mixed[]  $identifier Map of key columns to their values
  531.      * @param string[] $columns    Column names
  532.      * @param mixed[]  $values     Column values
  533.      * @param string[] $conditions Key conditions
  534.      *
  535.      * @throws DBALException
  536.      */
  537.     private function addIdentifierCondition(
  538.         array $identifier,
  539.         array &$columns,
  540.         array &$values,
  541.         array &$conditions
  542.     ) : void {
  543.         $platform $this->getDatabasePlatform();
  544.         foreach ($identifier as $columnName => $value) {
  545.             if ($value === null) {
  546.                 $conditions[] = $platform->getIsNullExpression($columnName);
  547.                 continue;
  548.             }
  549.             $columns[]    = $columnName;
  550.             $values[]     = $value;
  551.             $conditions[] = $columnName ' = ?';
  552.         }
  553.     }
  554.     /**
  555.      * Executes an SQL DELETE statement on a table.
  556.      *
  557.      * Table expression and columns are not escaped and are not safe for user-input.
  558.      *
  559.      * @param string         $tableExpression The expression of the table on which to delete.
  560.      * @param mixed[]        $identifier      The deletion criteria. An associative array containing column-value pairs.
  561.      * @param int[]|string[] $types           The types of identifiers.
  562.      *
  563.      * @return int The number of affected rows.
  564.      *
  565.      * @throws DBALException
  566.      * @throws InvalidArgumentException
  567.      */
  568.     public function delete($tableExpression, array $identifier, array $types = [])
  569.     {
  570.         if (empty($identifier)) {
  571.             throw InvalidArgumentException::fromEmptyCriteria();
  572.         }
  573.         $columns $values $conditions = [];
  574.         $this->addIdentifierCondition($identifier$columns$values$conditions);
  575.         return $this->executeUpdate(
  576.             'DELETE FROM ' $tableExpression ' WHERE ' implode(' AND '$conditions),
  577.             $values,
  578.             is_string(key($types)) ? $this->extractTypeValues($columns$types) : $types
  579.         );
  580.     }
  581.     /**
  582.      * Closes the connection.
  583.      *
  584.      * @return void
  585.      */
  586.     public function close()
  587.     {
  588.         $this->_conn null;
  589.         $this->isConnected false;
  590.     }
  591.     /**
  592.      * Sets the transaction isolation level.
  593.      *
  594.      * @param int $level The level to set.
  595.      *
  596.      * @return int
  597.      */
  598.     public function setTransactionIsolation($level)
  599.     {
  600.         $this->transactionIsolationLevel $level;
  601.         return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
  602.     }
  603.     /**
  604.      * Gets the currently active transaction isolation level.
  605.      *
  606.      * @return int The current transaction isolation level.
  607.      */
  608.     public function getTransactionIsolation()
  609.     {
  610.         if ($this->transactionIsolationLevel === null) {
  611.             $this->transactionIsolationLevel $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
  612.         }
  613.         return $this->transactionIsolationLevel;
  614.     }
  615.     /**
  616.      * Executes an SQL UPDATE statement on a table.
  617.      *
  618.      * Table expression and columns are not escaped and are not safe for user-input.
  619.      *
  620.      * @param string         $tableExpression The expression of the table to update quoted or unquoted.
  621.      * @param mixed[]        $data            An associative array containing column-value pairs.
  622.      * @param mixed[]        $identifier      The update criteria. An associative array containing column-value pairs.
  623.      * @param int[]|string[] $types           Types of the merged $data and $identifier arrays in that order.
  624.      *
  625.      * @return int The number of affected rows.
  626.      *
  627.      * @throws DBALException
  628.      */
  629.     public function update($tableExpression, array $data, array $identifier, array $types = [])
  630.     {
  631.         $columns $values $conditions $set = [];
  632.         foreach ($data as $columnName => $value) {
  633.             $columns[] = $columnName;
  634.             $values[]  = $value;
  635.             $set[]     = $columnName ' = ?';
  636.         }
  637.         $this->addIdentifierCondition($identifier$columns$values$conditions);
  638.         if (is_string(key($types))) {
  639.             $types $this->extractTypeValues($columns$types);
  640.         }
  641.         $sql 'UPDATE ' $tableExpression ' SET ' implode(', '$set)
  642.                 . ' WHERE ' implode(' AND '$conditions);
  643.         return $this->executeUpdate($sql$values$types);
  644.     }
  645.     /**
  646.      * Inserts a table row with specified data.
  647.      *
  648.      * Table expression and columns are not escaped and are not safe for user-input.
  649.      *
  650.      * @param string         $tableExpression The expression of the table to insert data into, quoted or unquoted.
  651.      * @param mixed[]        $data            An associative array containing column-value pairs.
  652.      * @param int[]|string[] $types           Types of the inserted data.
  653.      *
  654.      * @return int The number of affected rows.
  655.      *
  656.      * @throws DBALException
  657.      */
  658.     public function insert($tableExpression, array $data, array $types = [])
  659.     {
  660.         if (empty($data)) {
  661.             return $this->executeUpdate('INSERT INTO ' $tableExpression ' () VALUES ()');
  662.         }
  663.         $columns = [];
  664.         $values  = [];
  665.         $set     = [];
  666.         foreach ($data as $columnName => $value) {
  667.             $columns[] = $columnName;
  668.             $values[]  = $value;
  669.             $set[]     = '?';
  670.         }
  671.         return $this->executeUpdate(
  672.             'INSERT INTO ' $tableExpression ' (' implode(', '$columns) . ')' .
  673.             ' VALUES (' implode(', '$set) . ')',
  674.             $values,
  675.             is_string(key($types)) ? $this->extractTypeValues($columns$types) : $types
  676.         );
  677.     }
  678.     /**
  679.      * Extract ordered type list from an ordered column list and type map.
  680.      *
  681.      * @param int[]|string[] $columnList
  682.      * @param int[]|string[] $types
  683.      *
  684.      * @return int[]|string[]
  685.      */
  686.     private function extractTypeValues(array $columnList, array $types)
  687.     {
  688.         $typeValues = [];
  689.         foreach ($columnList as $columnIndex => $columnName) {
  690.             $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
  691.         }
  692.         return $typeValues;
  693.     }
  694.     /**
  695.      * Quotes a string so it can be safely used as a table or column name, even if
  696.      * it is a reserved name.
  697.      *
  698.      * Delimiting style depends on the underlying database platform that is being used.
  699.      *
  700.      * NOTE: Just because you CAN use quoted identifiers does not mean
  701.      * you SHOULD use them. In general, they end up causing way more
  702.      * problems than they solve.
  703.      *
  704.      * @param string $str The name to be quoted.
  705.      *
  706.      * @return string The quoted name.
  707.      */
  708.     public function quoteIdentifier($str)
  709.     {
  710.         return $this->getDatabasePlatform()->quoteIdentifier($str);
  711.     }
  712.     /**
  713.      * {@inheritDoc}
  714.      */
  715.     public function quote($input$type null)
  716.     {
  717.         $connection $this->getWrappedConnection();
  718.         [$value$bindingType] = $this->getBindingInfo($input$type);
  719.         return $connection->quote($value$bindingType);
  720.     }
  721.     /**
  722.      * Prepares and executes an SQL query and returns the result as an associative array.
  723.      *
  724.      * @param string         $sql    The SQL query.
  725.      * @param mixed[]        $params The query parameters.
  726.      * @param int[]|string[] $types  The query parameter types.
  727.      *
  728.      * @return mixed[]
  729.      */
  730.     public function fetchAll($sql, array $params = [], $types = [])
  731.     {
  732.         return $this->executeQuery($sql$params$types)->fetchAll();
  733.     }
  734.     /**
  735.      * Prepares an SQL statement.
  736.      *
  737.      * @param string $statement The SQL statement to prepare.
  738.      *
  739.      * @return DriverStatement The prepared statement.
  740.      *
  741.      * @throws DBALException
  742.      */
  743.     public function prepare($statement)
  744.     {
  745.         try {
  746.             $stmt = new Statement($statement$this);
  747.         } catch (Throwable $ex) {
  748.             throw DBALException::driverExceptionDuringQuery($this->_driver$ex$statement);
  749.         }
  750.         $stmt->setFetchMode($this->defaultFetchMode);
  751.         return $stmt;
  752.     }
  753.     /**
  754.      * Executes an, optionally parametrized, SQL query.
  755.      *
  756.      * If the query is parametrized, a prepared statement is used.
  757.      * If an SQLLogger is configured, the execution is logged.
  758.      *
  759.      * @param string                 $query  The SQL query to execute.
  760.      * @param mixed[]                $params The parameters to bind to the query, if any.
  761.      * @param int[]|string[]         $types  The types the previous parameters are in.
  762.      * @param QueryCacheProfile|null $qcp    The query cache profile, optional.
  763.      *
  764.      * @return ResultStatement The executed statement.
  765.      *
  766.      * @throws DBALException
  767.      */
  768.     public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp null)
  769.     {
  770.         if ($qcp !== null) {
  771.             return $this->executeCacheQuery($query$params$types$qcp);
  772.         }
  773.         $connection $this->getWrappedConnection();
  774.         $logger $this->_config->getSQLLogger();
  775.         if ($logger) {
  776.             $logger->startQuery($query$params$types);
  777.         }
  778.         try {
  779.             if ($params) {
  780.                 [$query$params$types] = SQLParserUtils::expandListParameters($query$params$types);
  781.                 $stmt $connection->prepare($query);
  782.                 if ($types) {
  783.                     $this->_bindTypedValues($stmt$params$types);
  784.                     $stmt->execute();
  785.                 } else {
  786.                     $stmt->execute($params);
  787.                 }
  788.             } else {
  789.                 $stmt $connection->query($query);
  790.             }
  791.         } catch (Throwable $ex) {
  792.             throw DBALException::driverExceptionDuringQuery($this->_driver$ex$query$this->resolveParams($params$types));
  793.         }
  794.         $stmt->setFetchMode($this->defaultFetchMode);
  795.         if ($logger) {
  796.             $logger->stopQuery();
  797.         }
  798.         return $stmt;
  799.     }
  800.     /**
  801.      * Executes a caching query.
  802.      *
  803.      * @param string            $query  The SQL query to execute.
  804.      * @param mixed[]           $params The parameters to bind to the query, if any.
  805.      * @param int[]|string[]    $types  The types the previous parameters are in.
  806.      * @param QueryCacheProfile $qcp    The query cache profile.
  807.      *
  808.      * @return ResultStatement
  809.      *
  810.      * @throws CacheException
  811.      */
  812.     public function executeCacheQuery($query$params$typesQueryCacheProfile $qcp)
  813.     {
  814.         $resultCache $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
  815.         if ($resultCache === null) {
  816.             throw CacheException::noResultDriverConfigured();
  817.         }
  818.         $connectionParams $this->getParams();
  819.         unset($connectionParams['platform']);
  820.         [$cacheKey$realKey] = $qcp->generateCacheKeys($query$params$types$connectionParams);
  821.         // fetch the row pointers entry
  822.         $data $resultCache->fetch($cacheKey);
  823.         if ($data !== false) {
  824.             // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
  825.             if (isset($data[$realKey])) {
  826.                 $stmt = new ArrayStatement($data[$realKey]);
  827.             } elseif (array_key_exists($realKey$data)) {
  828.                 $stmt = new ArrayStatement([]);
  829.             }
  830.         }
  831.         if (! isset($stmt)) {
  832.             $stmt = new ResultCacheStatement($this->executeQuery($query$params$types), $resultCache$cacheKey$realKey$qcp->getLifetime());
  833.         }
  834.         $stmt->setFetchMode($this->defaultFetchMode);
  835.         return $stmt;
  836.     }
  837.     /**
  838.      * Executes an, optionally parametrized, SQL query and returns the result,
  839.      * applying a given projection/transformation function on each row of the result.
  840.      *
  841.      * @param string  $query    The SQL query to execute.
  842.      * @param mixed[] $params   The parameters, if any.
  843.      * @param Closure $function The transformation function that is applied on each row.
  844.      *                           The function receives a single parameter, an array, that
  845.      *                           represents a row of the result set.
  846.      *
  847.      * @return mixed[] The projected result of the query.
  848.      */
  849.     public function project($query, array $paramsClosure $function)
  850.     {
  851.         $result = [];
  852.         $stmt   $this->executeQuery($query$params);
  853.         while ($row $stmt->fetch()) {
  854.             $result[] = $function($row);
  855.         }
  856.         $stmt->closeCursor();
  857.         return $result;
  858.     }
  859.     /**
  860.      * Executes an SQL statement, returning a result set as a Statement object.
  861.      *
  862.      * @return \Doctrine\DBAL\Driver\Statement
  863.      *
  864.      * @throws DBALException
  865.      */
  866.     public function query()
  867.     {
  868.         $connection $this->getWrappedConnection();
  869.         $args func_get_args();
  870.         $logger $this->_config->getSQLLogger();
  871.         if ($logger) {
  872.             $logger->startQuery($args[0]);
  873.         }
  874.         try {
  875.             $statement $connection->query(...$args);
  876.         } catch (Throwable $ex) {
  877.             throw DBALException::driverExceptionDuringQuery($this->_driver$ex$args[0]);
  878.         }
  879.         $statement->setFetchMode($this->defaultFetchMode);
  880.         if ($logger) {
  881.             $logger->stopQuery();
  882.         }
  883.         return $statement;
  884.     }
  885.     /**
  886.      * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
  887.      * and returns the number of affected rows.
  888.      *
  889.      * This method supports PDO binding types as well as DBAL mapping types.
  890.      *
  891.      * @param string         $query  The SQL query.
  892.      * @param mixed[]        $params The query parameters.
  893.      * @param int[]|string[] $types  The parameter types.
  894.      *
  895.      * @return int The number of affected rows.
  896.      *
  897.      * @throws DBALException
  898.      */
  899.     public function executeUpdate($query, array $params = [], array $types = [])
  900.     {
  901.         $connection $this->getWrappedConnection();
  902.         $logger $this->_config->getSQLLogger();
  903.         if ($logger) {
  904.             $logger->startQuery($query$params$types);
  905.         }
  906.         try {
  907.             if ($params) {
  908.                 [$query$params$types] = SQLParserUtils::expandListParameters($query$params$types);
  909.                 $stmt $connection->prepare($query);
  910.                 if ($types) {
  911.                     $this->_bindTypedValues($stmt$params$types);
  912.                     $stmt->execute();
  913.                 } else {
  914.                     $stmt->execute($params);
  915.                 }
  916.                 $result $stmt->rowCount();
  917.             } else {
  918.                 $result $connection->exec($query);
  919.             }
  920.         } catch (Throwable $ex) {
  921.             throw DBALException::driverExceptionDuringQuery($this->_driver$ex$query$this->resolveParams($params$types));
  922.         }
  923.         if ($logger) {
  924.             $logger->stopQuery();
  925.         }
  926.         return $result;
  927.     }
  928.     /**
  929.      * Executes an SQL statement and return the number of affected rows.
  930.      *
  931.      * @param string $statement
  932.      *
  933.      * @return int The number of affected rows.
  934.      *
  935.      * @throws DBALException
  936.      */
  937.     public function exec($statement)
  938.     {
  939.         $connection $this->getWrappedConnection();
  940.         $logger $this->_config->getSQLLogger();
  941.         if ($logger) {
  942.             $logger->startQuery($statement);
  943.         }
  944.         try {
  945.             $result $connection->exec($statement);
  946.         } catch (Throwable $ex) {
  947.             throw DBALException::driverExceptionDuringQuery($this->_driver$ex$statement);
  948.         }
  949.         if ($logger) {
  950.             $logger->stopQuery();
  951.         }
  952.         return $result;
  953.     }
  954.     /**
  955.      * Returns the current transaction nesting level.
  956.      *
  957.      * @return int The nesting level. A value of 0 means there's no active transaction.
  958.      */
  959.     public function getTransactionNestingLevel()
  960.     {
  961.         return $this->transactionNestingLevel;
  962.     }
  963.     /**
  964.      * Fetches the SQLSTATE associated with the last database operation.
  965.      *
  966.      * @return string|null The last error code.
  967.      */
  968.     public function errorCode()
  969.     {
  970.         return $this->getWrappedConnection()->errorCode();
  971.     }
  972.     /**
  973.      * {@inheritDoc}
  974.      */
  975.     public function errorInfo()
  976.     {
  977.         return $this->getWrappedConnection()->errorInfo();
  978.     }
  979.     /**
  980.      * Returns the ID of the last inserted row, or the last value from a sequence object,
  981.      * depending on the underlying driver.
  982.      *
  983.      * Note: This method may not return a meaningful or consistent result across different drivers,
  984.      * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
  985.      * columns or sequences.
  986.      *
  987.      * @param string|null $seqName Name of the sequence object from which the ID should be returned.
  988.      *
  989.      * @return string A string representation of the last inserted ID.
  990.      */
  991.     public function lastInsertId($seqName null)
  992.     {
  993.         return $this->getWrappedConnection()->lastInsertId($seqName);
  994.     }
  995.     /**
  996.      * Executes a function in a transaction.
  997.      *
  998.      * The function gets passed this Connection instance as an (optional) parameter.
  999.      *
  1000.      * If an exception occurs during execution of the function or transaction commit,
  1001.      * the transaction is rolled back and the exception re-thrown.
  1002.      *
  1003.      * @param Closure $func The function to execute transactionally.
  1004.      *
  1005.      * @return mixed The value returned by $func
  1006.      *
  1007.      * @throws Exception
  1008.      * @throws Throwable
  1009.      */
  1010.     public function transactional(Closure $func)
  1011.     {
  1012.         $this->beginTransaction();
  1013.         try {
  1014.             $res $func($this);
  1015.             $this->commit();
  1016.             return $res;
  1017.         } catch (Exception $e) {
  1018.             $this->rollBack();
  1019.             throw $e;
  1020.         } catch (Throwable $e) {
  1021.             $this->rollBack();
  1022.             throw $e;
  1023.         }
  1024.     }
  1025.     /**
  1026.      * Sets if nested transactions should use savepoints.
  1027.      *
  1028.      * @param bool $nestTransactionsWithSavepoints
  1029.      *
  1030.      * @return void
  1031.      *
  1032.      * @throws ConnectionException
  1033.      */
  1034.     public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
  1035.     {
  1036.         if ($this->transactionNestingLevel 0) {
  1037.             throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
  1038.         }
  1039.         if (! $this->getDatabasePlatform()->supportsSavepoints()) {
  1040.             throw ConnectionException::savepointsNotSupported();
  1041.         }
  1042.         $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
  1043.     }
  1044.     /**
  1045.      * Gets if nested transactions should use savepoints.
  1046.      *
  1047.      * @return bool
  1048.      */
  1049.     public function getNestTransactionsWithSavepoints()
  1050.     {
  1051.         return $this->nestTransactionsWithSavepoints;
  1052.     }
  1053.     /**
  1054.      * Returns the savepoint name to use for nested transactions are false if they are not supported
  1055.      * "savepointFormat" parameter is not set
  1056.      *
  1057.      * @return mixed A string with the savepoint name or false.
  1058.      */
  1059.     protected function _getNestedTransactionSavePointName()
  1060.     {
  1061.         return 'DOCTRINE2_SAVEPOINT_' $this->transactionNestingLevel;
  1062.     }
  1063.     /**
  1064.      * {@inheritDoc}
  1065.      */
  1066.     public function beginTransaction()
  1067.     {
  1068.         $connection $this->getWrappedConnection();
  1069.         ++$this->transactionNestingLevel;
  1070.         $logger $this->_config->getSQLLogger();
  1071.         if ($this->transactionNestingLevel === 1) {
  1072.             if ($logger) {
  1073.                 $logger->startQuery('"START TRANSACTION"');
  1074.             }
  1075.             $connection->beginTransaction();
  1076.             if ($logger) {
  1077.                 $logger->stopQuery();
  1078.             }
  1079.         } elseif ($this->nestTransactionsWithSavepoints) {
  1080.             if ($logger) {
  1081.                 $logger->startQuery('"SAVEPOINT"');
  1082.             }
  1083.             $this->createSavepoint($this->_getNestedTransactionSavePointName());
  1084.             if ($logger) {
  1085.                 $logger->stopQuery();
  1086.             }
  1087.         }
  1088.         return true;
  1089.     }
  1090.     /**
  1091.      * {@inheritDoc}
  1092.      *
  1093.      * @throws ConnectionException If the commit failed due to no active transaction or
  1094.      *                                            because the transaction was marked for rollback only.
  1095.      */
  1096.     public function commit()
  1097.     {
  1098.         if ($this->transactionNestingLevel === 0) {
  1099.             throw ConnectionException::noActiveTransaction();
  1100.         }
  1101.         if ($this->isRollbackOnly) {
  1102.             throw ConnectionException::commitFailedRollbackOnly();
  1103.         }
  1104.         $result true;
  1105.         $connection $this->getWrappedConnection();
  1106.         $logger $this->_config->getSQLLogger();
  1107.         if ($this->transactionNestingLevel === 1) {
  1108.             if ($logger) {
  1109.                 $logger->startQuery('"COMMIT"');
  1110.             }
  1111.             $result $connection->commit();
  1112.             if ($logger) {
  1113.                 $logger->stopQuery();
  1114.             }
  1115.         } elseif ($this->nestTransactionsWithSavepoints) {
  1116.             if ($logger) {
  1117.                 $logger->startQuery('"RELEASE SAVEPOINT"');
  1118.             }
  1119.             $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
  1120.             if ($logger) {
  1121.                 $logger->stopQuery();
  1122.             }
  1123.         }
  1124.         --$this->transactionNestingLevel;
  1125.         if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
  1126.             return $result;
  1127.         }
  1128.         $this->beginTransaction();
  1129.         return $result;
  1130.     }
  1131.     /**
  1132.      * Commits all current nesting transactions.
  1133.      */
  1134.     private function commitAll()
  1135.     {
  1136.         while ($this->transactionNestingLevel !== 0) {
  1137.             if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
  1138.                 // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
  1139.                 // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
  1140.                 $this->commit();
  1141.                 return;
  1142.             }
  1143.             $this->commit();
  1144.         }
  1145.     }
  1146.     /**
  1147.      * Cancels any database changes done during the current transaction.
  1148.      *
  1149.      * @throws ConnectionException If the rollback operation failed.
  1150.      */
  1151.     public function rollBack()
  1152.     {
  1153.         if ($this->transactionNestingLevel === 0) {
  1154.             throw ConnectionException::noActiveTransaction();
  1155.         }
  1156.         $connection $this->getWrappedConnection();
  1157.         $logger $this->_config->getSQLLogger();
  1158.         if ($this->transactionNestingLevel === 1) {
  1159.             if ($logger) {
  1160.                 $logger->startQuery('"ROLLBACK"');
  1161.             }
  1162.             $this->transactionNestingLevel 0;
  1163.             $connection->rollBack();
  1164.             $this->isRollbackOnly false;
  1165.             if ($logger) {
  1166.                 $logger->stopQuery();
  1167.             }
  1168.             if ($this->autoCommit === false) {
  1169.                 $this->beginTransaction();
  1170.             }
  1171.         } elseif ($this->nestTransactionsWithSavepoints) {
  1172.             if ($logger) {
  1173.                 $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
  1174.             }
  1175.             $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
  1176.             --$this->transactionNestingLevel;
  1177.             if ($logger) {
  1178.                 $logger->stopQuery();
  1179.             }
  1180.         } else {
  1181.             $this->isRollbackOnly true;
  1182.             --$this->transactionNestingLevel;
  1183.         }
  1184.     }
  1185.     /**
  1186.      * Creates a new savepoint.
  1187.      *
  1188.      * @param string $savepoint The name of the savepoint to create.
  1189.      *
  1190.      * @return void
  1191.      *
  1192.      * @throws ConnectionException
  1193.      */
  1194.     public function createSavepoint($savepoint)
  1195.     {
  1196.         if (! $this->getDatabasePlatform()->supportsSavepoints()) {
  1197.             throw ConnectionException::savepointsNotSupported();
  1198.         }
  1199.         $this->getWrappedConnection()->exec($this->platform->createSavePoint($savepoint));
  1200.     }
  1201.     /**
  1202.      * Releases the given savepoint.
  1203.      *
  1204.      * @param string $savepoint The name of the savepoint to release.
  1205.      *
  1206.      * @return void
  1207.      *
  1208.      * @throws ConnectionException
  1209.      */
  1210.     public function releaseSavepoint($savepoint)
  1211.     {
  1212.         if (! $this->getDatabasePlatform()->supportsSavepoints()) {
  1213.             throw ConnectionException::savepointsNotSupported();
  1214.         }
  1215.         if (! $this->platform->supportsReleaseSavepoints()) {
  1216.             return;
  1217.         }
  1218.         $this->getWrappedConnection()->exec($this->platform->releaseSavePoint($savepoint));
  1219.     }
  1220.     /**
  1221.      * Rolls back to the given savepoint.
  1222.      *
  1223.      * @param string $savepoint The name of the savepoint to rollback to.
  1224.      *
  1225.      * @return void
  1226.      *
  1227.      * @throws ConnectionException
  1228.      */
  1229.     public function rollbackSavepoint($savepoint)
  1230.     {
  1231.         if (! $this->getDatabasePlatform()->supportsSavepoints()) {
  1232.             throw ConnectionException::savepointsNotSupported();
  1233.         }
  1234.         $this->getWrappedConnection()->exec($this->platform->rollbackSavePoint($savepoint));
  1235.     }
  1236.     /**
  1237.      * Gets the wrapped driver connection.
  1238.      *
  1239.      * @return DriverConnection
  1240.      */
  1241.     public function getWrappedConnection()
  1242.     {
  1243.         $this->connect();
  1244.         return $this->_conn;
  1245.     }
  1246.     /**
  1247.      * Gets the SchemaManager that can be used to inspect or change the
  1248.      * database schema through the connection.
  1249.      *
  1250.      * @return AbstractSchemaManager
  1251.      */
  1252.     public function getSchemaManager()
  1253.     {
  1254.         if ($this->_schemaManager === null) {
  1255.             $this->_schemaManager $this->_driver->getSchemaManager($this);
  1256.         }
  1257.         return $this->_schemaManager;
  1258.     }
  1259.     /**
  1260.      * Marks the current transaction so that the only possible
  1261.      * outcome for the transaction to be rolled back.
  1262.      *
  1263.      * @return void
  1264.      *
  1265.      * @throws ConnectionException If no transaction is active.
  1266.      */
  1267.     public function setRollbackOnly()
  1268.     {
  1269.         if ($this->transactionNestingLevel === 0) {
  1270.             throw ConnectionException::noActiveTransaction();
  1271.         }
  1272.         $this->isRollbackOnly true;
  1273.     }
  1274.     /**
  1275.      * Checks whether the current transaction is marked for rollback only.
  1276.      *
  1277.      * @return bool
  1278.      *
  1279.      * @throws ConnectionException If no transaction is active.
  1280.      */
  1281.     public function isRollbackOnly()
  1282.     {
  1283.         if ($this->transactionNestingLevel === 0) {
  1284.             throw ConnectionException::noActiveTransaction();
  1285.         }
  1286.         return $this->isRollbackOnly;
  1287.     }
  1288.     /**
  1289.      * Converts a given value to its database representation according to the conversion
  1290.      * rules of a specific DBAL mapping type.
  1291.      *
  1292.      * @param mixed  $value The value to convert.
  1293.      * @param string $type  The name of the DBAL mapping type.
  1294.      *
  1295.      * @return mixed The converted value.
  1296.      */
  1297.     public function convertToDatabaseValue($value$type)
  1298.     {
  1299.         return Type::getType($type)->convertToDatabaseValue($value$this->getDatabasePlatform());
  1300.     }
  1301.     /**
  1302.      * Converts a given value to its PHP representation according to the conversion
  1303.      * rules of a specific DBAL mapping type.
  1304.      *
  1305.      * @param mixed  $value The value to convert.
  1306.      * @param string $type  The name of the DBAL mapping type.
  1307.      *
  1308.      * @return mixed The converted type.
  1309.      */
  1310.     public function convertToPHPValue($value$type)
  1311.     {
  1312.         return Type::getType($type)->convertToPHPValue($value$this->getDatabasePlatform());
  1313.     }
  1314.     /**
  1315.      * Binds a set of parameters, some or all of which are typed with a PDO binding type
  1316.      * or DBAL mapping type, to a given statement.
  1317.      *
  1318.      * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
  1319.      *           raw PDOStatement instances.
  1320.      *
  1321.      * @param \Doctrine\DBAL\Driver\Statement $stmt   The statement to bind the values to.
  1322.      * @param mixed[]                         $params The map/list of named/positional parameters.
  1323.      * @param int[]|string[]                  $types  The parameter types (PDO binding types or DBAL mapping types).
  1324.      *
  1325.      * @return void
  1326.      */
  1327.     private function _bindTypedValues($stmt, array $params, array $types)
  1328.     {
  1329.         // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  1330.         if (is_int(key($params))) {
  1331.             // Positional parameters
  1332.             $typeOffset array_key_exists(0$types) ? -0;
  1333.             $bindIndex  1;
  1334.             foreach ($params as $value) {
  1335.                 $typeIndex $bindIndex $typeOffset;
  1336.                 if (isset($types[$typeIndex])) {
  1337.                     $type                  $types[$typeIndex];
  1338.                     [$value$bindingType] = $this->getBindingInfo($value$type);
  1339.                     $stmt->bindValue($bindIndex$value$bindingType);
  1340.                 } else {
  1341.                     $stmt->bindValue($bindIndex$value);
  1342.                 }
  1343.                 ++$bindIndex;
  1344.             }
  1345.         } else {
  1346.             // Named parameters
  1347.             foreach ($params as $name => $value) {
  1348.                 if (isset($types[$name])) {
  1349.                     $type                  $types[$name];
  1350.                     [$value$bindingType] = $this->getBindingInfo($value$type);
  1351.                     $stmt->bindValue($name$value$bindingType);
  1352.                 } else {
  1353.                     $stmt->bindValue($name$value);
  1354.                 }
  1355.             }
  1356.         }
  1357.     }
  1358.     /**
  1359.      * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
  1360.      *
  1361.      * @param mixed           $value The value to bind.
  1362.      * @param int|string|null $type  The type to bind (PDO or DBAL).
  1363.      *
  1364.      * @return mixed[] [0] => the (escaped) value, [1] => the binding type.
  1365.      */
  1366.     private function getBindingInfo($value$type)
  1367.     {
  1368.         if (is_string($type)) {
  1369.             $type Type::getType($type);
  1370.         }
  1371.         if ($type instanceof Type) {
  1372.             $value       $type->convertToDatabaseValue($value$this->getDatabasePlatform());
  1373.             $bindingType $type->getBindingType();
  1374.         } else {
  1375.             $bindingType $type;
  1376.         }
  1377.         return [$value$bindingType];
  1378.     }
  1379.     /**
  1380.      * Resolves the parameters to a format which can be displayed.
  1381.      *
  1382.      * @internal This is a purely internal method. If you rely on this method, you are advised to
  1383.      *           copy/paste the code as this method may change, or be removed without prior notice.
  1384.      *
  1385.      * @param mixed[]        $params
  1386.      * @param int[]|string[] $types
  1387.      *
  1388.      * @return mixed[]
  1389.      */
  1390.     public function resolveParams(array $params, array $types)
  1391.     {
  1392.         $resolvedParams = [];
  1393.         // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  1394.         if (is_int(key($params))) {
  1395.             // Positional parameters
  1396.             $typeOffset array_key_exists(0$types) ? -0;
  1397.             $bindIndex  1;
  1398.             foreach ($params as $value) {
  1399.                 $typeIndex $bindIndex $typeOffset;
  1400.                 if (isset($types[$typeIndex])) {
  1401.                     $type                       $types[$typeIndex];
  1402.                     [$value]                    = $this->getBindingInfo($value$type);
  1403.                     $resolvedParams[$bindIndex] = $value;
  1404.                 } else {
  1405.                     $resolvedParams[$bindIndex] = $value;
  1406.                 }
  1407.                 ++$bindIndex;
  1408.             }
  1409.         } else {
  1410.             // Named parameters
  1411.             foreach ($params as $name => $value) {
  1412.                 if (isset($types[$name])) {
  1413.                     $type                  $types[$name];
  1414.                     [$value]               = $this->getBindingInfo($value$type);
  1415.                     $resolvedParams[$name] = $value;
  1416.                 } else {
  1417.                     $resolvedParams[$name] = $value;
  1418.                 }
  1419.             }
  1420.         }
  1421.         return $resolvedParams;
  1422.     }
  1423.     /**
  1424.      * Creates a new instance of a SQL query builder.
  1425.      *
  1426.      * @return QueryBuilder
  1427.      */
  1428.     public function createQueryBuilder()
  1429.     {
  1430.         return new Query\QueryBuilder($this);
  1431.     }
  1432.     /**
  1433.      * Ping the server
  1434.      *
  1435.      * When the server is not available the method returns FALSE.
  1436.      * It is responsibility of the developer to handle this case
  1437.      * and abort the request or reconnect manually:
  1438.      *
  1439.      * @return bool
  1440.      *
  1441.      * @example
  1442.      *
  1443.      *   if ($conn->ping() === false) {
  1444.      *      $conn->close();
  1445.      *      $conn->connect();
  1446.      *   }
  1447.      *
  1448.      * It is undefined if the underlying driver attempts to reconnect
  1449.      * or disconnect when the connection is not available anymore
  1450.      * as long it returns TRUE when a reconnect succeeded and
  1451.      * FALSE when the connection was dropped.
  1452.      */
  1453.     public function ping()
  1454.     {
  1455.         $connection $this->getWrappedConnection();
  1456.         if ($connection instanceof PingableConnection) {
  1457.             return $connection->ping();
  1458.         }
  1459.         try {
  1460.             $this->query($this->getDatabasePlatform()->getDummySelectSQL());
  1461.             return true;
  1462.         } catch (DBALException $e) {
  1463.             return false;
  1464.         }
  1465.     }
  1466. }