vendor/doctrine/dbal/src/Connection.php line 619

Open in your IDE?
  1. <?php
  2. namespace Doctrine\DBAL;
  3. use Closure;
  4. use Doctrine\Common\EventManager;
  5. use Doctrine\DBAL\Cache\ArrayResult;
  6. use Doctrine\DBAL\Cache\CacheException;
  7. use Doctrine\DBAL\Cache\QueryCacheProfile;
  8. use Doctrine\DBAL\Driver\API\ExceptionConverter;
  9. use Doctrine\DBAL\Driver\Connection as DriverConnection;
  10. use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
  11. use Doctrine\DBAL\Driver\Statement as DriverStatement;
  12. use Doctrine\DBAL\Event\TransactionBeginEventArgs;
  13. use Doctrine\DBAL\Event\TransactionCommitEventArgs;
  14. use Doctrine\DBAL\Event\TransactionRollBackEventArgs;
  15. use Doctrine\DBAL\Exception\ConnectionLost;
  16. use Doctrine\DBAL\Exception\DriverException;
  17. use Doctrine\DBAL\Exception\InvalidArgumentException;
  18. use Doctrine\DBAL\Platforms\AbstractPlatform;
  19. use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
  20. use Doctrine\DBAL\Query\QueryBuilder;
  21. use Doctrine\DBAL\Schema\AbstractSchemaManager;
  22. use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
  23. use Doctrine\DBAL\Schema\LegacySchemaManagerFactory;
  24. use Doctrine\DBAL\Schema\SchemaManagerFactory;
  25. use Doctrine\DBAL\SQL\Parser;
  26. use Doctrine\DBAL\Types\Type;
  27. use Doctrine\Deprecations\Deprecation;
  28. use LogicException;
  29. use SensitiveParameter;
  30. use Throwable;
  31. use Traversable;
  32. use function array_key_exists;
  33. use function assert;
  34. use function count;
  35. use function get_class;
  36. use function implode;
  37. use function is_int;
  38. use function is_string;
  39. use function key;
  40. use function method_exists;
  41. use function sprintf;
  42. /**
  43.  * A database abstraction-level connection that implements features like events, transaction isolation levels,
  44.  * configuration, emulated transaction nesting, lazy connecting and more.
  45.  *
  46.  * @psalm-import-type Params from DriverManager
  47.  * @psalm-consistent-constructor
  48.  */
  49. class Connection
  50. {
  51.     /**
  52.      * Represents an array of ints to be expanded by Doctrine SQL parsing.
  53.      *
  54.      * @deprecated Use {@see ArrayParameterType::INTEGER} instead.
  55.      */
  56.     public const PARAM_INT_ARRAY ArrayParameterType::INTEGER;
  57.     /**
  58.      * Represents an array of strings to be expanded by Doctrine SQL parsing.
  59.      *
  60.      * @deprecated Use {@see ArrayParameterType::STRING} instead.
  61.      */
  62.     public const PARAM_STR_ARRAY ArrayParameterType::STRING;
  63.     /**
  64.      * Represents an array of ascii strings to be expanded by Doctrine SQL parsing.
  65.      *
  66.      * @deprecated Use {@see ArrayParameterType::ASCII} instead.
  67.      */
  68.     public const PARAM_ASCII_STR_ARRAY ArrayParameterType::ASCII;
  69.     /**
  70.      * Offset by which PARAM_* constants are detected as arrays of the param type.
  71.      *
  72.      * @internal Should be used only within the wrapper layer.
  73.      */
  74.     public const ARRAY_PARAM_OFFSET 100;
  75.     /**
  76.      * The wrapped driver connection.
  77.      *
  78.      * @var DriverConnection|null
  79.      */
  80.     protected $_conn;
  81.     /** @var Configuration */
  82.     protected $_config;
  83.     /**
  84.      * @deprecated
  85.      *
  86.      * @var EventManager
  87.      */
  88.     protected $_eventManager;
  89.     /**
  90.      * @deprecated Use {@see createExpressionBuilder()} instead.
  91.      *
  92.      * @var ExpressionBuilder
  93.      */
  94.     protected $_expr;
  95.     /**
  96.      * The current auto-commit mode of this connection.
  97.      */
  98.     private bool $autoCommit true;
  99.     /**
  100.      * The transaction nesting level.
  101.      */
  102.     private int $transactionNestingLevel 0;
  103.     /**
  104.      * The currently active transaction isolation level or NULL before it has been determined.
  105.      *
  106.      * @var TransactionIsolationLevel::*|null
  107.      */
  108.     private $transactionIsolationLevel;
  109.     /**
  110.      * If nested transactions should use savepoints.
  111.      */
  112.     private bool $nestTransactionsWithSavepoints false;
  113.     /**
  114.      * The parameters used during creation of the Connection instance.
  115.      *
  116.      * @var array<string,mixed>
  117.      * @psalm-var Params
  118.      */
  119.     private array $params;
  120.     /**
  121.      * The database platform object used by the connection or NULL before it's initialized.
  122.      */
  123.     private ?AbstractPlatform $platform null;
  124.     private ?ExceptionConverter $exceptionConverter null;
  125.     private ?Parser $parser                         null;
  126.     /**
  127.      * The schema manager.
  128.      *
  129.      * @deprecated Use {@see createSchemaManager()} instead.
  130.      *
  131.      * @var AbstractSchemaManager|null
  132.      */
  133.     protected $_schemaManager;
  134.     /**
  135.      * The used DBAL driver.
  136.      *
  137.      * @var Driver
  138.      */
  139.     protected $_driver;
  140.     /**
  141.      * Flag that indicates whether the current transaction is marked for rollback only.
  142.      */
  143.     private bool $isRollbackOnly false;
  144.     private SchemaManagerFactory $schemaManagerFactory;
  145.     /**
  146.      * Initializes a new instance of the Connection class.
  147.      *
  148.      * @internal The connection can be only instantiated by the driver manager.
  149.      *
  150.      * @param array<string,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.      * @psalm-param Params $params
  155.      *
  156.      * @throws Exception
  157.      */
  158.     public function __construct(
  159.         #[SensitiveParameter]
  160.         array $params,
  161.         Driver $driver,
  162.         ?Configuration $config null,
  163.         ?EventManager $eventManager null
  164.     ) {
  165.         $this->_driver $driver;
  166.         $this->params  $params;
  167.         // Create default config and event manager if none given
  168.         $config       ??= new Configuration();
  169.         $eventManager ??= new EventManager();
  170.         $this->_config       $config;
  171.         $this->_eventManager $eventManager;
  172.         if (isset($params['platform'])) {
  173.             if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
  174.                 throw Exception::invalidPlatformType($params['platform']);
  175.             }
  176.             Deprecation::trigger(
  177.                 'doctrine/dbal',
  178.                 'https://github.com/doctrine/dbal/pull/5699',
  179.                 'The "platform" connection parameter is deprecated.'
  180.                     ' Use a driver middleware that would instantiate the platform instead.',
  181.             );
  182.             $this->platform $params['platform'];
  183.             $this->platform->setEventManager($this->_eventManager);
  184.         }
  185.         $this->_expr $this->createExpressionBuilder();
  186.         $this->autoCommit $config->getAutoCommit();
  187.         $schemaManagerFactory $config->getSchemaManagerFactory();
  188.         if ($schemaManagerFactory === null) {
  189.             Deprecation::trigger(
  190.                 'doctrine/dbal',
  191.                 'https://github.com/doctrine/dbal/issues/5812',
  192.                 'Not configuring a schema manager factory is deprecated.'
  193.                     ' Use %s which is going to be the default in DBAL 4.',
  194.                 DefaultSchemaManagerFactory::class,
  195.             );
  196.             $schemaManagerFactory = new LegacySchemaManagerFactory();
  197.         }
  198.         $this->schemaManagerFactory $schemaManagerFactory;
  199.     }
  200.     /**
  201.      * Gets the parameters used during instantiation.
  202.      *
  203.      * @internal
  204.      *
  205.      * @return array<string,mixed>
  206.      * @psalm-return Params
  207.      */
  208.     public function getParams()
  209.     {
  210.         return $this->params;
  211.     }
  212.     /**
  213.      * Gets the name of the currently selected database.
  214.      *
  215.      * @return string|null The name of the database or NULL if a database is not selected.
  216.      *                     The platforms which don't support the concept of a database (e.g. embedded databases)
  217.      *                     must always return a string as an indicator of an implicitly selected database.
  218.      *
  219.      * @throws Exception
  220.      */
  221.     public function getDatabase()
  222.     {
  223.         $platform $this->getDatabasePlatform();
  224.         $query    $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression());
  225.         $database $this->fetchOne($query);
  226.         assert(is_string($database) || $database === null);
  227.         return $database;
  228.     }
  229.     /**
  230.      * Gets the DBAL driver instance.
  231.      *
  232.      * @return Driver
  233.      */
  234.     public function getDriver()
  235.     {
  236.         return $this->_driver;
  237.     }
  238.     /**
  239.      * Gets the Configuration used by the Connection.
  240.      *
  241.      * @return Configuration
  242.      */
  243.     public function getConfiguration()
  244.     {
  245.         return $this->_config;
  246.     }
  247.     /**
  248.      * Gets the EventManager used by the Connection.
  249.      *
  250.      * @deprecated
  251.      *
  252.      * @return EventManager
  253.      */
  254.     public function getEventManager()
  255.     {
  256.         Deprecation::triggerIfCalledFromOutside(
  257.             'doctrine/dbal',
  258.             'https://github.com/doctrine/dbal/issues/5784',
  259.             '%s is deprecated.',
  260.             __METHOD__,
  261.         );
  262.         return $this->_eventManager;
  263.     }
  264.     /**
  265.      * Gets the DatabasePlatform for the connection.
  266.      *
  267.      * @return AbstractPlatform
  268.      *
  269.      * @throws Exception
  270.      */
  271.     public function getDatabasePlatform()
  272.     {
  273.         if ($this->platform === null) {
  274.             $this->platform $this->detectDatabasePlatform();
  275.             $this->platform->setEventManager($this->_eventManager);
  276.         }
  277.         return $this->platform;
  278.     }
  279.     /**
  280.      * Creates an expression builder for the connection.
  281.      */
  282.     public function createExpressionBuilder(): ExpressionBuilder
  283.     {
  284.         return new ExpressionBuilder($this);
  285.     }
  286.     /**
  287.      * Gets the ExpressionBuilder for the connection.
  288.      *
  289.      * @deprecated Use {@see createExpressionBuilder()} instead.
  290.      *
  291.      * @return ExpressionBuilder
  292.      */
  293.     public function getExpressionBuilder()
  294.     {
  295.         Deprecation::triggerIfCalledFromOutside(
  296.             'doctrine/dbal',
  297.             'https://github.com/doctrine/dbal/issues/4515',
  298.             'Connection::getExpressionBuilder() is deprecated,'
  299.                 ' use Connection::createExpressionBuilder() instead.',
  300.         );
  301.         return $this->_expr;
  302.     }
  303.     /**
  304.      * Establishes the connection with the database.
  305.      *
  306.      * @internal This method will be made protected in DBAL 4.0.
  307.      *
  308.      * @return bool TRUE if the connection was successfully established, FALSE if
  309.      *              the connection is already open.
  310.      *
  311.      * @throws Exception
  312.      *
  313.      * @psalm-assert !null $this->_conn
  314.      */
  315.     public function connect()
  316.     {
  317.         Deprecation::triggerIfCalledFromOutside(
  318.             'doctrine/dbal',
  319.             'https://github.com/doctrine/dbal/issues/4966',
  320.             'Public access to Connection::connect() is deprecated.',
  321.         );
  322.         if ($this->_conn !== null) {
  323.             return false;
  324.         }
  325.         try {
  326.             $this->_conn $this->_driver->connect($this->params);
  327.         } catch (Driver\Exception $e) {
  328.             throw $this->convertException($e);
  329.         }
  330.         if ($this->autoCommit === false) {
  331.             $this->beginTransaction();
  332.         }
  333.         if ($this->_eventManager->hasListeners(Events::postConnect)) {
  334.             Deprecation::trigger(
  335.                 'doctrine/dbal',
  336.                 'https://github.com/doctrine/dbal/issues/5784',
  337.                 'Subscribing to %s events is deprecated. Implement a middleware instead.',
  338.                 Events::postConnect,
  339.             );
  340.             $eventArgs = new Event\ConnectionEventArgs($this);
  341.             $this->_eventManager->dispatchEvent(Events::postConnect$eventArgs);
  342.         }
  343.         return true;
  344.     }
  345.     /**
  346.      * Detects and sets the database platform.
  347.      *
  348.      * Evaluates custom platform class and version in order to set the correct platform.
  349.      *
  350.      * @throws Exception If an invalid platform was specified for this connection.
  351.      */
  352.     private function detectDatabasePlatform(): AbstractPlatform
  353.     {
  354.         $version $this->getDatabasePlatformVersion();
  355.         if ($version !== null) {
  356.             assert($this->_driver instanceof VersionAwarePlatformDriver);
  357.             return $this->_driver->createDatabasePlatformForVersion($version);
  358.         }
  359.         return $this->_driver->getDatabasePlatform();
  360.     }
  361.     /**
  362.      * Returns the version of the related platform if applicable.
  363.      *
  364.      * Returns null if either the driver is not capable to create version
  365.      * specific platform instances, no explicit server version was specified
  366.      * or the underlying driver connection cannot determine the platform
  367.      * version without having to query it (performance reasons).
  368.      *
  369.      * @return string|null
  370.      *
  371.      * @throws Throwable
  372.      */
  373.     private function getDatabasePlatformVersion()
  374.     {
  375.         // Driver does not support version specific platforms.
  376.         if (! $this->_driver instanceof VersionAwarePlatformDriver) {
  377.             return null;
  378.         }
  379.         // Explicit platform version requested (supersedes auto-detection).
  380.         if (isset($this->params['serverVersion'])) {
  381.             return $this->params['serverVersion'];
  382.         }
  383.         if (isset($this->params['primary']) && isset($this->params['primary']['serverVersion'])) {
  384.             return $this->params['primary']['serverVersion'];
  385.         }
  386.         // If not connected, we need to connect now to determine the platform version.
  387.         if ($this->_conn === null) {
  388.             try {
  389.                 $this->connect();
  390.             } catch (Exception $originalException) {
  391.                 if (! isset($this->params['dbname'])) {
  392.                     throw $originalException;
  393.                 }
  394.                 Deprecation::trigger(
  395.                     'doctrine/dbal',
  396.                     'https://github.com/doctrine/dbal/pull/5707',
  397.                     'Relying on a fallback connection used to determine the database platform while connecting'
  398.                         ' to a non-existing database is deprecated. Either use an existing database name in'
  399.                         ' connection parameters or omit the database name if the platform'
  400.                         ' and the server configuration allow that.',
  401.                 );
  402.                 // The database to connect to might not yet exist.
  403.                 // Retry detection without database name connection parameter.
  404.                 $params $this->params;
  405.                 unset($this->params['dbname']);
  406.                 try {
  407.                     $this->connect();
  408.                 } catch (Exception $fallbackException) {
  409.                     // Either the platform does not support database-less connections
  410.                     // or something else went wrong.
  411.                     throw $originalException;
  412.                 } finally {
  413.                     $this->params $params;
  414.                 }
  415.                 $serverVersion $this->getServerVersion();
  416.                 // Close "temporary" connection to allow connecting to the real database again.
  417.                 $this->close();
  418.                 return $serverVersion;
  419.             }
  420.         }
  421.         return $this->getServerVersion();
  422.     }
  423.     /**
  424.      * Returns the database server version if the underlying driver supports it.
  425.      *
  426.      * @return string|null
  427.      *
  428.      * @throws Exception
  429.      */
  430.     private function getServerVersion()
  431.     {
  432.         $connection $this->getWrappedConnection();
  433.         // Automatic platform version detection.
  434.         if ($connection instanceof ServerInfoAwareConnection) {
  435.             try {
  436.                 return $connection->getServerVersion();
  437.             } catch (Driver\Exception $e) {
  438.                 throw $this->convertException($e);
  439.             }
  440.         }
  441.         Deprecation::trigger(
  442.             'doctrine/dbal',
  443.             'https://github.com/doctrine/dbal/pull/4750',
  444.             'Not implementing the ServerInfoAwareConnection interface in %s is deprecated',
  445.             get_class($connection),
  446.         );
  447.         // Unable to detect platform version.
  448.         return null;
  449.     }
  450.     /**
  451.      * Returns the current auto-commit mode for this connection.
  452.      *
  453.      * @see    setAutoCommit
  454.      *
  455.      * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
  456.      */
  457.     public function isAutoCommit()
  458.     {
  459.         return $this->autoCommit === true;
  460.     }
  461.     /**
  462.      * Sets auto-commit mode for this connection.
  463.      *
  464.      * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
  465.      * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
  466.      * the method commit or the method rollback. By default, new connections are in auto-commit mode.
  467.      *
  468.      * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
  469.      * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
  470.      *
  471.      * @see   isAutoCommit
  472.      *
  473.      * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
  474.      *
  475.      * @return void
  476.      */
  477.     public function setAutoCommit($autoCommit)
  478.     {
  479.         $autoCommit = (bool) $autoCommit;
  480.         // Mode not changed, no-op.
  481.         if ($autoCommit === $this->autoCommit) {
  482.             return;
  483.         }
  484.         $this->autoCommit $autoCommit;
  485.         // Commit all currently active transactions if any when switching auto-commit mode.
  486.         if ($this->_conn === null || $this->transactionNestingLevel === 0) {
  487.             return;
  488.         }
  489.         $this->commitAll();
  490.     }
  491.     /**
  492.      * Prepares and executes an SQL query and returns the first row of the result
  493.      * as an associative array.
  494.      *
  495.      * @param string                                                               $query  SQL query
  496.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  497.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  498.      *
  499.      * @return array<string, mixed>|false False is returned if no rows are found.
  500.      *
  501.      * @throws Exception
  502.      */
  503.     public function fetchAssociative(string $query, array $params = [], array $types = [])
  504.     {
  505.         return $this->executeQuery($query$params$types)->fetchAssociative();
  506.     }
  507.     /**
  508.      * Prepares and executes an SQL query and returns the first row of the result
  509.      * as a numerically indexed array.
  510.      *
  511.      * @param string                                                               $query  SQL query
  512.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  513.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  514.      *
  515.      * @return list<mixed>|false False is returned if no rows are found.
  516.      *
  517.      * @throws Exception
  518.      */
  519.     public function fetchNumeric(string $query, array $params = [], array $types = [])
  520.     {
  521.         return $this->executeQuery($query$params$types)->fetchNumeric();
  522.     }
  523.     /**
  524.      * Prepares and executes an SQL query and returns the value of a single column
  525.      * of the first row of the result.
  526.      *
  527.      * @param string                                                               $query  SQL query
  528.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  529.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  530.      *
  531.      * @return mixed|false False is returned if no rows are found.
  532.      *
  533.      * @throws Exception
  534.      */
  535.     public function fetchOne(string $query, array $params = [], array $types = [])
  536.     {
  537.         return $this->executeQuery($query$params$types)->fetchOne();
  538.     }
  539.     /**
  540.      * Whether an actual connection to the database is established.
  541.      *
  542.      * @return bool
  543.      */
  544.     public function isConnected()
  545.     {
  546.         return $this->_conn !== null;
  547.     }
  548.     /**
  549.      * Checks whether a transaction is currently active.
  550.      *
  551.      * @return bool TRUE if a transaction is currently active, FALSE otherwise.
  552.      */
  553.     public function isTransactionActive()
  554.     {
  555.         return $this->transactionNestingLevel 0;
  556.     }
  557.     /**
  558.      * Adds condition based on the criteria to the query components
  559.      *
  560.      * @param array<string,mixed> $criteria   Map of key columns to their values
  561.      * @param string[]            $columns    Column names
  562.      * @param mixed[]             $values     Column values
  563.      * @param string[]            $conditions Key conditions
  564.      *
  565.      * @throws Exception
  566.      */
  567.     private function addCriteriaCondition(
  568.         array $criteria,
  569.         array &$columns,
  570.         array &$values,
  571.         array &$conditions
  572.     ): void {
  573.         $platform $this->getDatabasePlatform();
  574.         foreach ($criteria as $columnName => $value) {
  575.             if ($value === null) {
  576.                 $conditions[] = $platform->getIsNullExpression($columnName);
  577.                 continue;
  578.             }
  579.             $columns[]    = $columnName;
  580.             $values[]     = $value;
  581.             $conditions[] = $columnName ' = ?';
  582.         }
  583.     }
  584.     /**
  585.      * Executes an SQL DELETE statement on a table.
  586.      *
  587.      * Table expression and columns are not escaped and are not safe for user-input.
  588.      *
  589.      * @param string                                                               $table    Table name
  590.      * @param array<string, mixed>                                                 $criteria Deletion criteria
  591.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types    Parameter types
  592.      *
  593.      * @return int|string The number of affected rows.
  594.      *
  595.      * @throws Exception
  596.      */
  597.     public function delete($table, array $criteria, array $types = [])
  598.     {
  599.         if (count($criteria) === 0) {
  600.             throw InvalidArgumentException::fromEmptyCriteria();
  601.         }
  602.         $columns $values $conditions = [];
  603.         $this->addCriteriaCondition($criteria$columns$values$conditions);
  604.         return $this->executeStatement(
  605.             'DELETE FROM ' $table ' WHERE ' implode(' AND '$conditions),
  606.             $values,
  607.             is_string(key($types)) ? $this->extractTypeValues($columns$types) : $types,
  608.         );
  609.     }
  610.     /**
  611.      * Closes the connection.
  612.      *
  613.      * @return void
  614.      */
  615.     public function close()
  616.     {
  617.         $this->_conn                   null;
  618.         $this->transactionNestingLevel 0;
  619.     }
  620.     /**
  621.      * Sets the transaction isolation level.
  622.      *
  623.      * @param TransactionIsolationLevel::* $level The level to set.
  624.      *
  625.      * @return int|string
  626.      *
  627.      * @throws Exception
  628.      */
  629.     public function setTransactionIsolation($level)
  630.     {
  631.         $this->transactionIsolationLevel $level;
  632.         return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
  633.     }
  634.     /**
  635.      * Gets the currently active transaction isolation level.
  636.      *
  637.      * @return TransactionIsolationLevel::* The current transaction isolation level.
  638.      *
  639.      * @throws Exception
  640.      */
  641.     public function getTransactionIsolation()
  642.     {
  643.         return $this->transactionIsolationLevel ??= $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
  644.     }
  645.     /**
  646.      * Executes an SQL UPDATE statement on a table.
  647.      *
  648.      * Table expression and columns are not escaped and are not safe for user-input.
  649.      *
  650.      * @param string                                                               $table    Table name
  651.      * @param array<string, mixed>                                                 $data     Column-value pairs
  652.      * @param array<string, mixed>                                                 $criteria Update criteria
  653.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types    Parameter types
  654.      *
  655.      * @return int|string The number of affected rows.
  656.      *
  657.      * @throws Exception
  658.      */
  659.     public function update($table, array $data, array $criteria, array $types = [])
  660.     {
  661.         $columns $values $conditions $set = [];
  662.         foreach ($data as $columnName => $value) {
  663.             $columns[] = $columnName;
  664.             $values[]  = $value;
  665.             $set[]     = $columnName ' = ?';
  666.         }
  667.         $this->addCriteriaCondition($criteria$columns$values$conditions);
  668.         if (is_string(key($types))) {
  669.             $types $this->extractTypeValues($columns$types);
  670.         }
  671.         $sql 'UPDATE ' $table ' SET ' implode(', '$set)
  672.                 . ' WHERE ' implode(' AND '$conditions);
  673.         return $this->executeStatement($sql$values$types);
  674.     }
  675.     /**
  676.      * Inserts a table row with specified data.
  677.      *
  678.      * Table expression and columns are not escaped and are not safe for user-input.
  679.      *
  680.      * @param string                                                               $table Table name
  681.      * @param array<string, mixed>                                                 $data  Column-value pairs
  682.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types
  683.      *
  684.      * @return int|string The number of affected rows.
  685.      *
  686.      * @throws Exception
  687.      */
  688.     public function insert($table, array $data, array $types = [])
  689.     {
  690.         if (count($data) === 0) {
  691.             return $this->executeStatement('INSERT INTO ' $table ' () VALUES ()');
  692.         }
  693.         $columns = [];
  694.         $values  = [];
  695.         $set     = [];
  696.         foreach ($data as $columnName => $value) {
  697.             $columns[] = $columnName;
  698.             $values[]  = $value;
  699.             $set[]     = '?';
  700.         }
  701.         return $this->executeStatement(
  702.             'INSERT INTO ' $table ' (' implode(', '$columns) . ')' .
  703.             ' VALUES (' implode(', '$set) . ')',
  704.             $values,
  705.             is_string(key($types)) ? $this->extractTypeValues($columns$types) : $types,
  706.         );
  707.     }
  708.     /**
  709.      * Extract ordered type list from an ordered column list and type map.
  710.      *
  711.      * @param array<int, string>                                                   $columnList
  712.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
  713.      *
  714.      * @return array<int, int|string|Type|null>|array<string, int|string|Type|null>
  715.      */
  716.     private function extractTypeValues(array $columnList, array $types): array
  717.     {
  718.         $typeValues = [];
  719.         foreach ($columnList as $columnName) {
  720.             $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
  721.         }
  722.         return $typeValues;
  723.     }
  724.     /**
  725.      * Quotes a string so it can be safely used as a table or column name, even if
  726.      * it is a reserved name.
  727.      *
  728.      * Delimiting style depends on the underlying database platform that is being used.
  729.      *
  730.      * NOTE: Just because you CAN use quoted identifiers does not mean
  731.      * you SHOULD use them. In general, they end up causing way more
  732.      * problems than they solve.
  733.      *
  734.      * @param string $str The name to be quoted.
  735.      *
  736.      * @return string The quoted name.
  737.      */
  738.     public function quoteIdentifier($str)
  739.     {
  740.         return $this->getDatabasePlatform()->quoteIdentifier($str);
  741.     }
  742.     /**
  743.      * The usage of this method is discouraged. Use prepared statements
  744.      * or {@see AbstractPlatform::quoteStringLiteral()} instead.
  745.      *
  746.      * @param mixed                $value
  747.      * @param int|string|Type|null $type
  748.      *
  749.      * @return mixed
  750.      */
  751.     public function quote($value$type ParameterType::STRING)
  752.     {
  753.         $connection $this->getWrappedConnection();
  754.         [$value$bindingType] = $this->getBindingInfo($value$type);
  755.         return $connection->quote($value$bindingType);
  756.     }
  757.     /**
  758.      * Prepares and executes an SQL query and returns the result as an array of numeric arrays.
  759.      *
  760.      * @param string                                                               $query  SQL query
  761.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  762.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  763.      *
  764.      * @return list<list<mixed>>
  765.      *
  766.      * @throws Exception
  767.      */
  768.     public function fetchAllNumeric(string $query, array $params = [], array $types = []): array
  769.     {
  770.         return $this->executeQuery($query$params$types)->fetchAllNumeric();
  771.     }
  772.     /**
  773.      * Prepares and executes an SQL query and returns the result as an array of associative arrays.
  774.      *
  775.      * @param string                                                               $query  SQL query
  776.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  777.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  778.      *
  779.      * @return list<array<string,mixed>>
  780.      *
  781.      * @throws Exception
  782.      */
  783.     public function fetchAllAssociative(string $query, array $params = [], array $types = []): array
  784.     {
  785.         return $this->executeQuery($query$params$types)->fetchAllAssociative();
  786.     }
  787.     /**
  788.      * Prepares and executes an SQL query and returns the result as an associative array with the keys
  789.      * mapped to the first column and the values mapped to the second column.
  790.      *
  791.      * @param string                                                               $query  SQL query
  792.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  793.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  794.      *
  795.      * @return array<mixed,mixed>
  796.      *
  797.      * @throws Exception
  798.      */
  799.     public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array
  800.     {
  801.         return $this->executeQuery($query$params$types)->fetchAllKeyValue();
  802.     }
  803.     /**
  804.      * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped
  805.      * to the first column and the values being an associative array representing the rest of the columns
  806.      * and their values.
  807.      *
  808.      * @param string                                                               $query  SQL query
  809.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  810.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  811.      *
  812.      * @return array<mixed,array<string,mixed>>
  813.      *
  814.      * @throws Exception
  815.      */
  816.     public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array
  817.     {
  818.         return $this->executeQuery($query$params$types)->fetchAllAssociativeIndexed();
  819.     }
  820.     /**
  821.      * Prepares and executes an SQL query and returns the result as an array of the first column values.
  822.      *
  823.      * @param string                                                               $query  SQL query
  824.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  825.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  826.      *
  827.      * @return list<mixed>
  828.      *
  829.      * @throws Exception
  830.      */
  831.     public function fetchFirstColumn(string $query, array $params = [], array $types = []): array
  832.     {
  833.         return $this->executeQuery($query$params$types)->fetchFirstColumn();
  834.     }
  835.     /**
  836.      * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays.
  837.      *
  838.      * @param string                                                               $query  SQL query
  839.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  840.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  841.      *
  842.      * @return Traversable<int,list<mixed>>
  843.      *
  844.      * @throws Exception
  845.      */
  846.     public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable
  847.     {
  848.         return $this->executeQuery($query$params$types)->iterateNumeric();
  849.     }
  850.     /**
  851.      * Prepares and executes an SQL query and returns the result as an iterator over rows represented
  852.      * as associative arrays.
  853.      *
  854.      * @param string                                                               $query  SQL query
  855.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  856.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  857.      *
  858.      * @return Traversable<int,array<string,mixed>>
  859.      *
  860.      * @throws Exception
  861.      */
  862.     public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable
  863.     {
  864.         return $this->executeQuery($query$params$types)->iterateAssociative();
  865.     }
  866.     /**
  867.      * Prepares and executes an SQL query and returns the result as an iterator with the keys
  868.      * mapped to the first column and the values mapped to the second column.
  869.      *
  870.      * @param string                                                               $query  SQL query
  871.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  872.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  873.      *
  874.      * @return Traversable<mixed,mixed>
  875.      *
  876.      * @throws Exception
  877.      */
  878.     public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable
  879.     {
  880.         return $this->executeQuery($query$params$types)->iterateKeyValue();
  881.     }
  882.     /**
  883.      * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped
  884.      * to the first column and the values being an associative array representing the rest of the columns
  885.      * and their values.
  886.      *
  887.      * @param string                                           $query  SQL query
  888.      * @param list<mixed>|array<string, mixed>                 $params Query parameters
  889.      * @param array<int, int|string>|array<string, int|string> $types  Parameter types
  890.      *
  891.      * @return Traversable<mixed,array<string,mixed>>
  892.      *
  893.      * @throws Exception
  894.      */
  895.     public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable
  896.     {
  897.         return $this->executeQuery($query$params$types)->iterateAssociativeIndexed();
  898.     }
  899.     /**
  900.      * Prepares and executes an SQL query and returns the result as an iterator over the first column values.
  901.      *
  902.      * @param string                                                               $query  SQL query
  903.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  904.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  905.      *
  906.      * @return Traversable<int,mixed>
  907.      *
  908.      * @throws Exception
  909.      */
  910.     public function iterateColumn(string $query, array $params = [], array $types = []): Traversable
  911.     {
  912.         return $this->executeQuery($query$params$types)->iterateColumn();
  913.     }
  914.     /**
  915.      * Prepares an SQL statement.
  916.      *
  917.      * @param string $sql The SQL statement to prepare.
  918.      *
  919.      * @throws Exception
  920.      */
  921.     public function prepare(string $sql): Statement
  922.     {
  923.         $connection $this->getWrappedConnection();
  924.         try {
  925.             $statement $connection->prepare($sql);
  926.         } catch (Driver\Exception $e) {
  927.             throw $this->convertExceptionDuringQuery($e$sql);
  928.         }
  929.         return new Statement($this$statement$sql);
  930.     }
  931.     /**
  932.      * Executes an, optionally parameterized, SQL query.
  933.      *
  934.      * If the query is parametrized, a prepared statement is used.
  935.      * If an SQLLogger is configured, the execution is logged.
  936.      *
  937.      * @param string                                                               $sql    SQL query
  938.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  939.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  940.      *
  941.      * @throws Exception
  942.      */
  943.     public function executeQuery(
  944.         string $sql,
  945.         array $params = [],
  946.         $types = [],
  947.         ?QueryCacheProfile $qcp null
  948.     ): Result {
  949.         if ($qcp !== null) {
  950.             return $this->executeCacheQuery($sql$params$types$qcp);
  951.         }
  952.         $connection $this->getWrappedConnection();
  953.         $logger $this->_config->getSQLLogger();
  954.         if ($logger !== null) {
  955.             $logger->startQuery($sql$params$types);
  956.         }
  957.         try {
  958.             if (count($params) > 0) {
  959.                 if ($this->needsArrayParameterConversion($params$types)) {
  960.                     [$sql$params$types] = $this->expandArrayParameters($sql$params$types);
  961.                 }
  962.                 $stmt $connection->prepare($sql);
  963.                 $this->bindParameters($stmt$params$types);
  964.                 $result $stmt->execute();
  965.             } else {
  966.                 $result $connection->query($sql);
  967.             }
  968.             return new Result($result$this);
  969.         } catch (Driver\Exception $e) {
  970.             throw $this->convertExceptionDuringQuery($e$sql$params$types);
  971.         } finally {
  972.             if ($logger !== null) {
  973.                 $logger->stopQuery();
  974.             }
  975.         }
  976.     }
  977.     /**
  978.      * Executes a caching query.
  979.      *
  980.      * @param string                                                               $sql    SQL query
  981.      * @param list<mixed>|array<string, mixed>                                     $params Query parameters
  982.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  983.      *
  984.      * @throws CacheException
  985.      * @throws Exception
  986.      */
  987.     public function executeCacheQuery($sql$params$typesQueryCacheProfile $qcp): Result
  988.     {
  989.         $resultCache $qcp->getResultCache() ?? $this->_config->getResultCache();
  990.         if ($resultCache === null) {
  991.             throw CacheException::noResultDriverConfigured();
  992.         }
  993.         $connectionParams $this->params;
  994.         unset($connectionParams['platform'], $connectionParams['password'], $connectionParams['url']);
  995.         [$cacheKey$realKey] = $qcp->generateCacheKeys($sql$params$types$connectionParams);
  996.         $item $resultCache->getItem($cacheKey);
  997.         if ($item->isHit()) {
  998.             $value $item->get();
  999.             if (isset($value[$realKey])) {
  1000.                 return new Result(new ArrayResult($value[$realKey]), $this);
  1001.             }
  1002.         } else {
  1003.             $value = [];
  1004.         }
  1005.         $data $this->fetchAllAssociative($sql$params$types);
  1006.         $value[$realKey] = $data;
  1007.         $item->set($value);
  1008.         $lifetime $qcp->getLifetime();
  1009.         if ($lifetime 0) {
  1010.             $item->expiresAfter($lifetime);
  1011.         }
  1012.         $resultCache->save($item);
  1013.         return new Result(new ArrayResult($data), $this);
  1014.     }
  1015.     /**
  1016.      * Executes an SQL statement with the given parameters and returns the number of affected rows.
  1017.      *
  1018.      * Could be used for:
  1019.      *  - DML statements: INSERT, UPDATE, DELETE, etc.
  1020.      *  - DDL statements: CREATE, DROP, ALTER, etc.
  1021.      *  - DCL statements: GRANT, REVOKE, etc.
  1022.      *  - Session control statements: ALTER SESSION, SET, DECLARE, etc.
  1023.      *  - Other statements that don't yield a row set.
  1024.      *
  1025.      * This method supports PDO binding types as well as DBAL mapping types.
  1026.      *
  1027.      * @param string                                                               $sql    SQL statement
  1028.      * @param list<mixed>|array<string, mixed>                                     $params Statement parameters
  1029.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1030.      *
  1031.      * @return int|string The number of affected rows.
  1032.      *
  1033.      * @throws Exception
  1034.      */
  1035.     public function executeStatement($sql, array $params = [], array $types = [])
  1036.     {
  1037.         $connection $this->getWrappedConnection();
  1038.         $logger $this->_config->getSQLLogger();
  1039.         if ($logger !== null) {
  1040.             $logger->startQuery($sql$params$types);
  1041.         }
  1042.         try {
  1043.             if (count($params) > 0) {
  1044.                 if ($this->needsArrayParameterConversion($params$types)) {
  1045.                     [$sql$params$types] = $this->expandArrayParameters($sql$params$types);
  1046.                 }
  1047.                 $stmt $connection->prepare($sql);
  1048.                 $this->bindParameters($stmt$params$types);
  1049.                 return $stmt->execute()
  1050.                     ->rowCount();
  1051.             }
  1052.             return $connection->exec($sql);
  1053.         } catch (Driver\Exception $e) {
  1054.             throw $this->convertExceptionDuringQuery($e$sql$params$types);
  1055.         } finally {
  1056.             if ($logger !== null) {
  1057.                 $logger->stopQuery();
  1058.             }
  1059.         }
  1060.     }
  1061.     /**
  1062.      * Returns the current transaction nesting level.
  1063.      *
  1064.      * @return int The nesting level. A value of 0 means there's no active transaction.
  1065.      */
  1066.     public function getTransactionNestingLevel()
  1067.     {
  1068.         return $this->transactionNestingLevel;
  1069.     }
  1070.     /**
  1071.      * Returns the ID of the last inserted row, or the last value from a sequence object,
  1072.      * depending on the underlying driver.
  1073.      *
  1074.      * Note: This method may not return a meaningful or consistent result across different drivers,
  1075.      * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
  1076.      * columns or sequences.
  1077.      *
  1078.      * @param string|null $name Name of the sequence object from which the ID should be returned.
  1079.      *
  1080.      * @return string|int|false A string representation of the last inserted ID.
  1081.      *
  1082.      * @throws Exception
  1083.      */
  1084.     public function lastInsertId($name null)
  1085.     {
  1086.         if ($name !== null) {
  1087.             Deprecation::trigger(
  1088.                 'doctrine/dbal',
  1089.                 'https://github.com/doctrine/dbal/issues/4687',
  1090.                 'The usage of Connection::lastInsertId() with a sequence name is deprecated.',
  1091.             );
  1092.         }
  1093.         try {
  1094.             return $this->getWrappedConnection()->lastInsertId($name);
  1095.         } catch (Driver\Exception $e) {
  1096.             throw $this->convertException($e);
  1097.         }
  1098.     }
  1099.     /**
  1100.      * Executes a function in a transaction.
  1101.      *
  1102.      * The function gets passed this Connection instance as an (optional) parameter.
  1103.      *
  1104.      * If an exception occurs during execution of the function or transaction commit,
  1105.      * the transaction is rolled back and the exception re-thrown.
  1106.      *
  1107.      * @param Closure(self):T $func The function to execute transactionally.
  1108.      *
  1109.      * @return T The value returned by $func
  1110.      *
  1111.      * @throws Throwable
  1112.      *
  1113.      * @template T
  1114.      */
  1115.     public function transactional(Closure $func)
  1116.     {
  1117.         $this->beginTransaction();
  1118.         try {
  1119.             $res $func($this);
  1120.             $this->commit();
  1121.             return $res;
  1122.         } catch (Throwable $e) {
  1123.             $this->rollBack();
  1124.             throw $e;
  1125.         }
  1126.     }
  1127.     /**
  1128.      * Sets if nested transactions should use savepoints.
  1129.      *
  1130.      * @param bool $nestTransactionsWithSavepoints
  1131.      *
  1132.      * @return void
  1133.      *
  1134.      * @throws Exception
  1135.      */
  1136.     public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
  1137.     {
  1138.         if (! $nestTransactionsWithSavepoints) {
  1139.             Deprecation::trigger(
  1140.                 'doctrine/dbal',
  1141.                 'https://github.com/doctrine/dbal/pull/5383',
  1142.                 <<<'DEPRECATION'
  1143.                 Nesting transactions without enabling savepoints is deprecated.
  1144.                 Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints.
  1145.                 DEPRECATION,
  1146.                 self::class,
  1147.             );
  1148.         }
  1149.         if ($this->transactionNestingLevel 0) {
  1150.             throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
  1151.         }
  1152.         if (! $this->getDatabasePlatform()->supportsSavepoints()) {
  1153.             throw ConnectionException::savepointsNotSupported();
  1154.         }
  1155.         $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
  1156.     }
  1157.     /**
  1158.      * Gets if nested transactions should use savepoints.
  1159.      *
  1160.      * @return bool
  1161.      */
  1162.     public function getNestTransactionsWithSavepoints()
  1163.     {
  1164.         return $this->nestTransactionsWithSavepoints;
  1165.     }
  1166.     /**
  1167.      * Returns the savepoint name to use for nested transactions.
  1168.      *
  1169.      * @return string
  1170.      */
  1171.     protected function _getNestedTransactionSavePointName()
  1172.     {
  1173.         return 'DOCTRINE2_SAVEPOINT_' $this->transactionNestingLevel;
  1174.     }
  1175.     /**
  1176.      * @return bool
  1177.      *
  1178.      * @throws Exception
  1179.      */
  1180.     public function beginTransaction()
  1181.     {
  1182.         $connection $this->getWrappedConnection();
  1183.         ++$this->transactionNestingLevel;
  1184.         $logger $this->_config->getSQLLogger();
  1185.         if ($this->transactionNestingLevel === 1) {
  1186.             if ($logger !== null) {
  1187.                 $logger->startQuery('"START TRANSACTION"');
  1188.             }
  1189.             $connection->beginTransaction();
  1190.             if ($logger !== null) {
  1191.                 $logger->stopQuery();
  1192.             }
  1193.         } elseif ($this->nestTransactionsWithSavepoints) {
  1194.             if ($logger !== null) {
  1195.                 $logger->startQuery('"SAVEPOINT"');
  1196.             }
  1197.             $this->createSavepoint($this->_getNestedTransactionSavePointName());
  1198.             if ($logger !== null) {
  1199.                 $logger->stopQuery();
  1200.             }
  1201.         } else {
  1202.             Deprecation::trigger(
  1203.                 'doctrine/dbal',
  1204.                 'https://github.com/doctrine/dbal/pull/5383',
  1205.                 <<<'DEPRECATION'
  1206.                 Nesting transactions without enabling savepoints is deprecated.
  1207.                 Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints.
  1208.                 DEPRECATION,
  1209.                 self::class,
  1210.             );
  1211.         }
  1212.         $eventManager $this->getEventManager();
  1213.         if ($eventManager->hasListeners(Events::onTransactionBegin)) {
  1214.             Deprecation::trigger(
  1215.                 'doctrine/dbal',
  1216.                 'https://github.com/doctrine/dbal/issues/5784',
  1217.                 'Subscribing to %s events is deprecated.',
  1218.                 Events::onTransactionBegin,
  1219.             );
  1220.             $eventManager->dispatchEvent(Events::onTransactionBegin, new TransactionBeginEventArgs($this));
  1221.         }
  1222.         return true;
  1223.     }
  1224.     /**
  1225.      * @return bool
  1226.      *
  1227.      * @throws Exception
  1228.      */
  1229.     public function commit()
  1230.     {
  1231.         if ($this->transactionNestingLevel === 0) {
  1232.             throw ConnectionException::noActiveTransaction();
  1233.         }
  1234.         if ($this->isRollbackOnly) {
  1235.             throw ConnectionException::commitFailedRollbackOnly();
  1236.         }
  1237.         $result true;
  1238.         $connection $this->getWrappedConnection();
  1239.         if ($this->transactionNestingLevel === 1) {
  1240.             $result $this->doCommit($connection);
  1241.         } elseif ($this->nestTransactionsWithSavepoints) {
  1242.             $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
  1243.         }
  1244.         --$this->transactionNestingLevel;
  1245.         $eventManager $this->getEventManager();
  1246.         if ($eventManager->hasListeners(Events::onTransactionCommit)) {
  1247.             Deprecation::trigger(
  1248.                 'doctrine/dbal',
  1249.                 'https://github.com/doctrine/dbal/issues/5784',
  1250.                 'Subscribing to %s events is deprecated.',
  1251.                 Events::onTransactionCommit,
  1252.             );
  1253.             $eventManager->dispatchEvent(Events::onTransactionCommit, new TransactionCommitEventArgs($this));
  1254.         }
  1255.         if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
  1256.             return $result;
  1257.         }
  1258.         $this->beginTransaction();
  1259.         return $result;
  1260.     }
  1261.     /**
  1262.      * @return bool
  1263.      *
  1264.      * @throws DriverException
  1265.      */
  1266.     private function doCommit(DriverConnection $connection)
  1267.     {
  1268.         $logger $this->_config->getSQLLogger();
  1269.         if ($logger !== null) {
  1270.             $logger->startQuery('"COMMIT"');
  1271.         }
  1272.         $result $connection->commit();
  1273.         if ($logger !== null) {
  1274.             $logger->stopQuery();
  1275.         }
  1276.         return $result;
  1277.     }
  1278.     /**
  1279.      * Commits all current nesting transactions.
  1280.      *
  1281.      * @throws Exception
  1282.      */
  1283.     private function commitAll(): void
  1284.     {
  1285.         while ($this->transactionNestingLevel !== 0) {
  1286.             if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
  1287.                 // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
  1288.                 // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
  1289.                 $this->commit();
  1290.                 return;
  1291.             }
  1292.             $this->commit();
  1293.         }
  1294.     }
  1295.     /**
  1296.      * Cancels any database changes done during the current transaction.
  1297.      *
  1298.      * @return bool
  1299.      *
  1300.      * @throws Exception
  1301.      */
  1302.     public function rollBack()
  1303.     {
  1304.         if ($this->transactionNestingLevel === 0) {
  1305.             throw ConnectionException::noActiveTransaction();
  1306.         }
  1307.         $connection $this->getWrappedConnection();
  1308.         $logger $this->_config->getSQLLogger();
  1309.         if ($this->transactionNestingLevel === 1) {
  1310.             if ($logger !== null) {
  1311.                 $logger->startQuery('"ROLLBACK"');
  1312.             }
  1313.             $this->transactionNestingLevel 0;
  1314.             $connection->rollBack();
  1315.             $this->isRollbackOnly false;
  1316.             if ($logger !== null) {
  1317.                 $logger->stopQuery();
  1318.             }
  1319.             if ($this->autoCommit === false) {
  1320.                 $this->beginTransaction();
  1321.             }
  1322.         } elseif ($this->nestTransactionsWithSavepoints) {
  1323.             if ($logger !== null) {
  1324.                 $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
  1325.             }
  1326.             $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
  1327.             --$this->transactionNestingLevel;
  1328.             if ($logger !== null) {
  1329.                 $logger->stopQuery();
  1330.             }
  1331.         } else {
  1332.             $this->isRollbackOnly true;
  1333.             --$this->transactionNestingLevel;
  1334.         }
  1335.         $eventManager $this->getEventManager();
  1336.         if ($eventManager->hasListeners(Events::onTransactionRollBack)) {
  1337.             Deprecation::trigger(
  1338.                 'doctrine/dbal',
  1339.                 'https://github.com/doctrine/dbal/issues/5784',
  1340.                 'Subscribing to %s events is deprecated.',
  1341.                 Events::onTransactionRollBack,
  1342.             );
  1343.             $eventManager->dispatchEvent(Events::onTransactionRollBack, new TransactionRollBackEventArgs($this));
  1344.         }
  1345.         return true;
  1346.     }
  1347.     /**
  1348.      * Creates a new savepoint.
  1349.      *
  1350.      * @param string $savepoint The name of the savepoint to create.
  1351.      *
  1352.      * @return void
  1353.      *
  1354.      * @throws Exception
  1355.      */
  1356.     public function createSavepoint($savepoint)
  1357.     {
  1358.         $platform $this->getDatabasePlatform();
  1359.         if (! $platform->supportsSavepoints()) {
  1360.             throw ConnectionException::savepointsNotSupported();
  1361.         }
  1362.         $this->executeStatement($platform->createSavePoint($savepoint));
  1363.     }
  1364.     /**
  1365.      * Releases the given savepoint.
  1366.      *
  1367.      * @param string $savepoint The name of the savepoint to release.
  1368.      *
  1369.      * @return void
  1370.      *
  1371.      * @throws Exception
  1372.      */
  1373.     public function releaseSavepoint($savepoint)
  1374.     {
  1375.         $logger $this->_config->getSQLLogger();
  1376.         $platform $this->getDatabasePlatform();
  1377.         if (! $platform->supportsSavepoints()) {
  1378.             throw ConnectionException::savepointsNotSupported();
  1379.         }
  1380.         if (! $platform->supportsReleaseSavepoints()) {
  1381.             if ($logger !== null) {
  1382.                 $logger->stopQuery();
  1383.             }
  1384.             return;
  1385.         }
  1386.         if ($logger !== null) {
  1387.             $logger->startQuery('"RELEASE SAVEPOINT"');
  1388.         }
  1389.         $this->executeStatement($platform->releaseSavePoint($savepoint));
  1390.         if ($logger === null) {
  1391.             return;
  1392.         }
  1393.         $logger->stopQuery();
  1394.     }
  1395.     /**
  1396.      * Rolls back to the given savepoint.
  1397.      *
  1398.      * @param string $savepoint The name of the savepoint to rollback to.
  1399.      *
  1400.      * @return void
  1401.      *
  1402.      * @throws Exception
  1403.      */
  1404.     public function rollbackSavepoint($savepoint)
  1405.     {
  1406.         $platform $this->getDatabasePlatform();
  1407.         if (! $platform->supportsSavepoints()) {
  1408.             throw ConnectionException::savepointsNotSupported();
  1409.         }
  1410.         $this->executeStatement($platform->rollbackSavePoint($savepoint));
  1411.     }
  1412.     /**
  1413.      * Gets the wrapped driver connection.
  1414.      *
  1415.      * @deprecated Use {@link getNativeConnection()} to access the native connection.
  1416.      *
  1417.      * @return DriverConnection
  1418.      *
  1419.      * @throws Exception
  1420.      */
  1421.     public function getWrappedConnection()
  1422.     {
  1423.         Deprecation::triggerIfCalledFromOutside(
  1424.             'doctrine/dbal',
  1425.             'https://github.com/doctrine/dbal/issues/4966',
  1426.             'Connection::getWrappedConnection() is deprecated.'
  1427.                 ' Use Connection::getNativeConnection() to access the native connection.',
  1428.         );
  1429.         $this->connect();
  1430.         return $this->_conn;
  1431.     }
  1432.     /** @return resource|object */
  1433.     public function getNativeConnection()
  1434.     {
  1435.         $this->connect();
  1436.         if (! method_exists($this->_conn'getNativeConnection')) {
  1437.             throw new LogicException(sprintf(
  1438.                 'The driver connection %s does not support accessing the native connection.',
  1439.                 get_class($this->_conn),
  1440.             ));
  1441.         }
  1442.         return $this->_conn->getNativeConnection();
  1443.     }
  1444.     /**
  1445.      * Creates a SchemaManager that can be used to inspect or change the
  1446.      * database schema through the connection.
  1447.      *
  1448.      * @throws Exception
  1449.      */
  1450.     public function createSchemaManager(): AbstractSchemaManager
  1451.     {
  1452.         return $this->schemaManagerFactory->createSchemaManager($this);
  1453.     }
  1454.     /**
  1455.      * Gets the SchemaManager that can be used to inspect or change the
  1456.      * database schema through the connection.
  1457.      *
  1458.      * @deprecated Use {@see createSchemaManager()} instead.
  1459.      *
  1460.      * @return AbstractSchemaManager
  1461.      *
  1462.      * @throws Exception
  1463.      */
  1464.     public function getSchemaManager()
  1465.     {
  1466.         Deprecation::triggerIfCalledFromOutside(
  1467.             'doctrine/dbal',
  1468.             'https://github.com/doctrine/dbal/issues/4515',
  1469.             'Connection::getSchemaManager() is deprecated, use Connection::createSchemaManager() instead.',
  1470.         );
  1471.         return $this->_schemaManager ??= $this->createSchemaManager();
  1472.     }
  1473.     /**
  1474.      * Marks the current transaction so that the only possible
  1475.      * outcome for the transaction to be rolled back.
  1476.      *
  1477.      * @return void
  1478.      *
  1479.      * @throws ConnectionException If no transaction is active.
  1480.      */
  1481.     public function setRollbackOnly()
  1482.     {
  1483.         if ($this->transactionNestingLevel === 0) {
  1484.             throw ConnectionException::noActiveTransaction();
  1485.         }
  1486.         $this->isRollbackOnly true;
  1487.     }
  1488.     /**
  1489.      * Checks whether the current transaction is marked for rollback only.
  1490.      *
  1491.      * @return bool
  1492.      *
  1493.      * @throws ConnectionException If no transaction is active.
  1494.      */
  1495.     public function isRollbackOnly()
  1496.     {
  1497.         if ($this->transactionNestingLevel === 0) {
  1498.             throw ConnectionException::noActiveTransaction();
  1499.         }
  1500.         return $this->isRollbackOnly;
  1501.     }
  1502.     /**
  1503.      * Converts a given value to its database representation according to the conversion
  1504.      * rules of a specific DBAL mapping type.
  1505.      *
  1506.      * @param mixed  $value The value to convert.
  1507.      * @param string $type  The name of the DBAL mapping type.
  1508.      *
  1509.      * @return mixed The converted value.
  1510.      *
  1511.      * @throws Exception
  1512.      */
  1513.     public function convertToDatabaseValue($value$type)
  1514.     {
  1515.         return Type::getType($type)->convertToDatabaseValue($value$this->getDatabasePlatform());
  1516.     }
  1517.     /**
  1518.      * Converts a given value to its PHP representation according to the conversion
  1519.      * rules of a specific DBAL mapping type.
  1520.      *
  1521.      * @param mixed  $value The value to convert.
  1522.      * @param string $type  The name of the DBAL mapping type.
  1523.      *
  1524.      * @return mixed The converted type.
  1525.      *
  1526.      * @throws Exception
  1527.      */
  1528.     public function convertToPHPValue($value$type)
  1529.     {
  1530.         return Type::getType($type)->convertToPHPValue($value$this->getDatabasePlatform());
  1531.     }
  1532.     /**
  1533.      * Binds a set of parameters, some or all of which are typed with a PDO binding type
  1534.      * or DBAL mapping type, to a given statement.
  1535.      *
  1536.      * @param DriverStatement                                                      $stmt   Prepared statement
  1537.      * @param list<mixed>|array<string, mixed>                                     $params Statement parameters
  1538.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  1539.      *
  1540.      * @throws Exception
  1541.      */
  1542.     private function bindParameters(DriverStatement $stmt, array $params, array $types): void
  1543.     {
  1544.         // Check whether parameters are positional or named. Mixing is not allowed.
  1545.         if (is_int(key($params))) {
  1546.             $bindIndex 1;
  1547.             foreach ($params as $key => $value) {
  1548.                 if (isset($types[$key])) {
  1549.                     $type                  $types[$key];
  1550.                     [$value$bindingType] = $this->getBindingInfo($value$type);
  1551.                 } else {
  1552.                     if (array_key_exists($key$types)) {
  1553.                         Deprecation::trigger(
  1554.                             'doctrine/dbal',
  1555.                             'https://github.com/doctrine/dbal/pull/5550',
  1556.                             'Using NULL as prepared statement parameter type is deprecated.'
  1557.                                 'Omit or use Parameter::STRING instead',
  1558.                         );
  1559.                     }
  1560.                     $bindingType ParameterType::STRING;
  1561.                 }
  1562.                 $stmt->bindValue($bindIndex$value$bindingType);
  1563.                 ++$bindIndex;
  1564.             }
  1565.         } else {
  1566.             // Named parameters
  1567.             foreach ($params as $name => $value) {
  1568.                 if (isset($types[$name])) {
  1569.                     $type                  $types[$name];
  1570.                     [$value$bindingType] = $this->getBindingInfo($value$type);
  1571.                 } else {
  1572.                     if (array_key_exists($name$types)) {
  1573.                         Deprecation::trigger(
  1574.                             'doctrine/dbal',
  1575.                             'https://github.com/doctrine/dbal/pull/5550',
  1576.                             'Using NULL as prepared statement parameter type is deprecated.'
  1577.                                 'Omit or use Parameter::STRING instead',
  1578.                         );
  1579.                     }
  1580.                     $bindingType ParameterType::STRING;
  1581.                 }
  1582.                 $stmt->bindValue($name$value$bindingType);
  1583.             }
  1584.         }
  1585.     }
  1586.     /**
  1587.      * Gets the binding type of a given type.
  1588.      *
  1589.      * @param mixed                $value The value to bind.
  1590.      * @param int|string|Type|null $type  The type to bind (PDO or DBAL).
  1591.      *
  1592.      * @return array{mixed, int} [0] => the (escaped) value, [1] => the binding type.
  1593.      *
  1594.      * @throws Exception
  1595.      */
  1596.     private function getBindingInfo($value$type): array
  1597.     {
  1598.         if (is_string($type)) {
  1599.             $type Type::getType($type);
  1600.         }
  1601.         if ($type instanceof Type) {
  1602.             $value       $type->convertToDatabaseValue($value$this->getDatabasePlatform());
  1603.             $bindingType $type->getBindingType();
  1604.         } else {
  1605.             $bindingType $type ?? ParameterType::STRING;
  1606.         }
  1607.         return [$value$bindingType];
  1608.     }
  1609.     /**
  1610.      * Creates a new instance of a SQL query builder.
  1611.      *
  1612.      * @return QueryBuilder
  1613.      */
  1614.     public function createQueryBuilder()
  1615.     {
  1616.         return new Query\QueryBuilder($this);
  1617.     }
  1618.     /**
  1619.      * @internal
  1620.      *
  1621.      * @param list<mixed>|array<string, mixed>                                     $params
  1622.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
  1623.      */
  1624.     final public function convertExceptionDuringQuery(
  1625.         Driver\Exception $e,
  1626.         string $sql,
  1627.         array $params = [],
  1628.         array $types = []
  1629.     ): DriverException {
  1630.         return $this->handleDriverException($e, new Query($sql$params$types));
  1631.     }
  1632.     /** @internal */
  1633.     final public function convertException(Driver\Exception $e): DriverException
  1634.     {
  1635.         return $this->handleDriverException($enull);
  1636.     }
  1637.     /**
  1638.      * @param array<int, mixed>|array<string, mixed>                               $params
  1639.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
  1640.      *
  1641.      * @return array{string, list<mixed>, array<int,Type|int|string|null>}
  1642.      */
  1643.     private function expandArrayParameters(string $sql, array $params, array $types): array
  1644.     {
  1645.         $this->parser ??= $this->getDatabasePlatform()->createSQLParser();
  1646.         $visitor        = new ExpandArrayParameters($params$types);
  1647.         $this->parser->parse($sql$visitor);
  1648.         return [
  1649.             $visitor->getSQL(),
  1650.             $visitor->getParameters(),
  1651.             $visitor->getTypes(),
  1652.         ];
  1653.     }
  1654.     /**
  1655.      * @param array<int, mixed>|array<string, mixed>                               $params
  1656.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
  1657.      */
  1658.     private function needsArrayParameterConversion(array $params, array $types): bool
  1659.     {
  1660.         if (is_string(key($params))) {
  1661.             return true;
  1662.         }
  1663.         foreach ($types as $type) {
  1664.             if (
  1665.                 $type === ArrayParameterType::INTEGER
  1666.                 || $type === ArrayParameterType::STRING
  1667.                 || $type === ArrayParameterType::ASCII
  1668.             ) {
  1669.                 return true;
  1670.             }
  1671.         }
  1672.         return false;
  1673.     }
  1674.     private function handleDriverException(
  1675.         Driver\Exception $driverException,
  1676.         ?Query $query
  1677.     ): DriverException {
  1678.         $this->exceptionConverter ??= $this->_driver->getExceptionConverter();
  1679.         $exception                  $this->exceptionConverter->convert($driverException$query);
  1680.         if ($exception instanceof ConnectionLost) {
  1681.             $this->close();
  1682.         }
  1683.         return $exception;
  1684.     }
  1685.     /**
  1686.      * BC layer for a wide-spread use-case of old DBAL APIs
  1687.      *
  1688.      * @deprecated This API is deprecated and will be removed after 2022
  1689.      *
  1690.      * @param array<mixed>           $params The query parameters
  1691.      * @param array<int|string|null> $types  The parameter types
  1692.      */
  1693.     public function executeUpdate(string $sql, array $params = [], array $types = []): int
  1694.     {
  1695.         return $this->executeStatement($sql$params$types);
  1696.     }
  1697.     /**
  1698.      * BC layer for a wide-spread use-case of old DBAL APIs
  1699.      *
  1700.      * @deprecated This API is deprecated and will be removed after 2022
  1701.      */
  1702.     public function query(string $sql): Result
  1703.     {
  1704.         return $this->executeQuery($sql);
  1705.     }
  1706.     /**
  1707.      * BC layer for a wide-spread use-case of old DBAL APIs
  1708.      *
  1709.      * @deprecated This API is deprecated and will be removed after 2022
  1710.      */
  1711.     public function exec(string $sql): int
  1712.     {
  1713.         return $this->executeStatement($sql);
  1714.     }
  1715. }