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

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