diff --git a/UPGRADE.md b/UPGRADE.md index 3e2192c8d31..fc84eaabd2a 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,28 @@ # Upgrade to 2.11 +## Driver-level classes have been renamed + +- `DriverException` => `Exception` +- `AbstractDriverException` => `AbstractException` +- `IBMDB2\DB2Driver` => `IBMDB2\Driver` +- `IBMDB2\DB2Connection` => `IBMDB2\Connection` +- `IBMDB2\DB2Statement` => `IBMDB2\Statement` +- `IBMDB2\DB2Exception` => `IBMDB2\Exception` +- `Mysqli\MysqliConnection` => `Mysqli\Connection` +- `Mysqli\MysqliStatement` => `Mysqli\Statement` +- `Mysqli\MysqliException` => `Mysqli\Exception` +- `OCI8\OCI8Connection` => `OCI8\Connection` +- `OCI8\OCI8Statement` => `OCI8\Statement` +- `OCI8\OCI8Exception` => `OCI8\Exception` +- `SQLSrv\SQLSrvConnection` => `SQLSrv\Connection` +- `SQLSrv\SQLSrvStatement` => `SQLSrv\Statement` +- `SQLSrv\SQLSrvException` => `SQLSrv\Exception` +- `PDOConnection` => `PDO\Connection` +- `PDOStatement` => `PDO\Statement` +- `PDOException` => `PDO\Exception` + +The `Driver\AbstractException` class has been marked internal. + ## `Connection::getParams()` has been marked internal Consumers of the Connection class should not rely on connection parameters stored in the connection object. If needed, they should be obtained from a different source, e.g. application configuration. diff --git a/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php b/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php index a3697529e75..34381e23b1c 100644 --- a/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php +++ b/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php @@ -4,7 +4,7 @@ use ArrayIterator; use Doctrine\Common\Cache\Cache; -use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ResultStatement; @@ -297,7 +297,7 @@ public function free(): void /** * @return array|false * - * @throws DriverException + * @throws Exception */ private function doFetch() { diff --git a/lib/Doctrine/DBAL/DBALException.php b/lib/Doctrine/DBAL/DBALException.php index dda63310675..dd8bbe77840 100644 --- a/lib/Doctrine/DBAL/DBALException.php +++ b/lib/Doctrine/DBAL/DBALException.php @@ -2,7 +2,7 @@ namespace Doctrine\DBAL; -use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface; +use Doctrine\DBAL\Driver\Exception as DriverExceptionInterface; use Doctrine\DBAL\Driver\ExceptionConverterDriver; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\AbstractPlatform; diff --git a/lib/Doctrine/DBAL/Driver/AbstractDriverException.php b/lib/Doctrine/DBAL/Driver/AbstractDriverException.php index f57de38f09c..bf104545e41 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractDriverException.php +++ b/lib/Doctrine/DBAL/Driver/AbstractDriverException.php @@ -2,55 +2,11 @@ namespace Doctrine\DBAL\Driver; -use Exception; - /** - * Abstract base implementation of the {@link DriverException} interface. + * @deprecated * * @psalm-immutable */ -abstract class AbstractDriverException extends Exception implements DriverException +class AbstractDriverException extends AbstractException { - /** - * The driver specific error code. - * - * @var int|string|null - */ - private $errorCode; - - /** - * The SQLSTATE of the driver. - * - * @var string|null - */ - private $sqlState; - - /** - * @param string $message The driver error message. - * @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any. - * @param int|string|null $errorCode The driver specific error code if any. - */ - public function __construct($message, $sqlState = null, $errorCode = null) - { - parent::__construct($message); - - $this->errorCode = $errorCode; - $this->sqlState = $sqlState; - } - - /** - * {@inheritdoc} - */ - public function getErrorCode() - { - return $this->errorCode; - } - - /** - * {@inheritdoc} - */ - public function getSQLState() - { - return $this->sqlState; - } } diff --git a/lib/Doctrine/DBAL/Driver/AbstractException.php b/lib/Doctrine/DBAL/Driver/AbstractException.php new file mode 100644 index 00000000000..b89724b0a7c --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/AbstractException.php @@ -0,0 +1,59 @@ +errorCode = $errorCode; + $this->sqlState = $sqlState; + } + + /** + * {@inheritdoc} + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * {@inheritdoc} + */ + public function getSQLState() + { + return $this->sqlState; + } +} diff --git a/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php b/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php index 276322e28cd..c6e9c7ed9ef 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php +++ b/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php @@ -5,7 +5,18 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\ConnectionException; +use Doctrine\DBAL\Exception\DeadlockException; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; +use Doctrine\DBAL\Exception\InvalidFieldNameException; +use Doctrine\DBAL\Exception\LockWaitTimeoutException; +use Doctrine\DBAL\Exception\NonUniqueFieldNameException; +use Doctrine\DBAL\Exception\NotNullConstraintViolationException; +use Doctrine\DBAL\Exception\SyntaxErrorException; +use Doctrine\DBAL\Exception\TableExistsException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\MySQL80Platform; @@ -29,44 +40,44 @@ abstract class AbstractMySQLDriver implements Driver, ExceptionConverterDriver, * @link https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html * @link https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html */ - public function convertException($message, DriverException $exception) + public function convertException($message, Exception $exception) { switch ($exception->getErrorCode()) { case '1213': - return new Exception\DeadlockException($message, $exception); + return new DeadlockException($message, $exception); case '1205': - return new Exception\LockWaitTimeoutException($message, $exception); + return new LockWaitTimeoutException($message, $exception); case '1050': - return new Exception\TableExistsException($message, $exception); + return new TableExistsException($message, $exception); case '1051': case '1146': - return new Exception\TableNotFoundException($message, $exception); + return new TableNotFoundException($message, $exception); case '1216': case '1217': case '1451': case '1452': case '1701': - return new Exception\ForeignKeyConstraintViolationException($message, $exception); + return new ForeignKeyConstraintViolationException($message, $exception); case '1062': case '1557': case '1569': case '1586': - return new Exception\UniqueConstraintViolationException($message, $exception); + return new UniqueConstraintViolationException($message, $exception); case '1054': case '1166': case '1611': - return new Exception\InvalidFieldNameException($message, $exception); + return new InvalidFieldNameException($message, $exception); case '1052': case '1060': case '1110': - return new Exception\NonUniqueFieldNameException($message, $exception); + return new NonUniqueFieldNameException($message, $exception); case '1064': case '1149': @@ -80,7 +91,7 @@ public function convertException($message, DriverException $exception) case '1541': case '1554': case '1626': - return new Exception\SyntaxErrorException($message, $exception); + return new SyntaxErrorException($message, $exception); case '1044': case '1045': @@ -94,7 +105,7 @@ public function convertException($message, DriverException $exception) case '1429': case '2002': case '2005': - return new Exception\ConnectionException($message, $exception); + return new ConnectionException($message, $exception); case '1048': case '1121': @@ -104,10 +115,10 @@ public function convertException($message, DriverException $exception) case '1263': case '1364': case '1566': - return new Exception\NotNullConstraintViolationException($message, $exception); + return new NotNullConstraintViolationException($message, $exception); } - return new Exception\DriverException($message, $exception); + return new DriverException($message, $exception); } /** diff --git a/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php b/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php index bd1802f324e..823f4ee3f7a 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php +++ b/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php @@ -5,7 +5,16 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\ConnectionException; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; +use Doctrine\DBAL\Exception\InvalidFieldNameException; +use Doctrine\DBAL\Exception\NonUniqueFieldNameException; +use Doctrine\DBAL\Exception\NotNullConstraintViolationException; +use Doctrine\DBAL\Exception\SyntaxErrorException; +use Doctrine\DBAL\Exception\TableExistsException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\OracleSchemaManager; @@ -17,44 +26,44 @@ abstract class AbstractOracleDriver implements Driver, ExceptionConverterDriver /** * {@inheritdoc} */ - public function convertException($message, DriverException $exception) + public function convertException($message, Exception $exception) { switch ($exception->getErrorCode()) { case '1': case '2299': case '38911': - return new Exception\UniqueConstraintViolationException($message, $exception); + return new UniqueConstraintViolationException($message, $exception); case '904': - return new Exception\InvalidFieldNameException($message, $exception); + return new InvalidFieldNameException($message, $exception); case '918': case '960': - return new Exception\NonUniqueFieldNameException($message, $exception); + return new NonUniqueFieldNameException($message, $exception); case '923': - return new Exception\SyntaxErrorException($message, $exception); + return new SyntaxErrorException($message, $exception); case '942': - return new Exception\TableNotFoundException($message, $exception); + return new TableNotFoundException($message, $exception); case '955': - return new Exception\TableExistsException($message, $exception); + return new TableExistsException($message, $exception); case '1017': case '12545': - return new Exception\ConnectionException($message, $exception); + return new ConnectionException($message, $exception); case '1400': - return new Exception\NotNullConstraintViolationException($message, $exception); + return new NotNullConstraintViolationException($message, $exception); case '2266': case '2291': case '2292': - return new Exception\ForeignKeyConstraintViolationException($message, $exception); + return new ForeignKeyConstraintViolationException($message, $exception); } - return new Exception\DriverException($message, $exception); + return new DriverException($message, $exception); } /** diff --git a/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php b/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php index 5aabb977f17..29e0cf37035 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php +++ b/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php @@ -5,7 +5,17 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\ConnectionException; +use Doctrine\DBAL\Exception\DeadlockException; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; +use Doctrine\DBAL\Exception\InvalidFieldNameException; +use Doctrine\DBAL\Exception\NonUniqueFieldNameException; +use Doctrine\DBAL\Exception\NotNullConstraintViolationException; +use Doctrine\DBAL\Exception\SyntaxErrorException; +use Doctrine\DBAL\Exception\TableExistsException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\PostgreSQL100Platform; use Doctrine\DBAL\Platforms\PostgreSQL91Platform; use Doctrine\DBAL\Platforms\PostgreSQL92Platform; @@ -29,58 +39,58 @@ abstract class AbstractPostgreSQLDriver implements Driver, ExceptionConverterDri * * @link http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html */ - public function convertException($message, DriverException $exception) + public function convertException($message, Exception $exception) { switch ($exception->getSQLState()) { case '40001': case '40P01': - return new Exception\DeadlockException($message, $exception); + return new DeadlockException($message, $exception); case '0A000': // Foreign key constraint violations during a TRUNCATE operation // are considered "feature not supported" in PostgreSQL. if (strpos($exception->getMessage(), 'truncate') !== false) { - return new Exception\ForeignKeyConstraintViolationException($message, $exception); + return new ForeignKeyConstraintViolationException($message, $exception); } break; case '23502': - return new Exception\NotNullConstraintViolationException($message, $exception); + return new NotNullConstraintViolationException($message, $exception); case '23503': - return new Exception\ForeignKeyConstraintViolationException($message, $exception); + return new ForeignKeyConstraintViolationException($message, $exception); case '23505': - return new Exception\UniqueConstraintViolationException($message, $exception); + return new UniqueConstraintViolationException($message, $exception); case '42601': - return new Exception\SyntaxErrorException($message, $exception); + return new SyntaxErrorException($message, $exception); case '42702': - return new Exception\NonUniqueFieldNameException($message, $exception); + return new NonUniqueFieldNameException($message, $exception); case '42703': - return new Exception\InvalidFieldNameException($message, $exception); + return new InvalidFieldNameException($message, $exception); case '42P01': - return new Exception\TableNotFoundException($message, $exception); + return new TableNotFoundException($message, $exception); case '42P07': - return new Exception\TableExistsException($message, $exception); + return new TableExistsException($message, $exception); case '7': // In some case (mainly connection errors) the PDO exception does not provide a SQLSTATE via its code. // The exception code is always set to 7 here. // We have to match against the SQLSTATE in the error message in these cases. if (strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) { - return new Exception\ConnectionException($message, $exception); + return new ConnectionException($message, $exception); } break; } - return new Exception\DriverException($message, $exception); + return new DriverException($message, $exception); } /** diff --git a/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php b/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php index 12296ab9603..2b1bd1c01a8 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php +++ b/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php @@ -5,7 +5,18 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\ConnectionException; +use Doctrine\DBAL\Exception\DeadlockException; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; +use Doctrine\DBAL\Exception\InvalidFieldNameException; +use Doctrine\DBAL\Exception\LockWaitTimeoutException; +use Doctrine\DBAL\Exception\NonUniqueFieldNameException; +use Doctrine\DBAL\Exception\NotNullConstraintViolationException; +use Doctrine\DBAL\Exception\SyntaxErrorException; +use Doctrine\DBAL\Exception\TableExistsException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\SQLAnywhere11Platform; use Doctrine\DBAL\Platforms\SQLAnywhere12Platform; use Doctrine\DBAL\Platforms\SQLAnywhere16Platform; @@ -27,54 +38,54 @@ abstract class AbstractSQLAnywhereDriver implements Driver, ExceptionConverterDr * * @link http://dcx.sybase.com/index.html#sa160/en/saerrors/sqlerror.html */ - public function convertException($message, DriverException $exception) + public function convertException($message, Exception $exception) { switch ($exception->getErrorCode()) { case '-306': case '-307': case '-684': - return new Exception\DeadlockException($message, $exception); + return new DeadlockException($message, $exception); case '-210': case '-1175': case '-1281': - return new Exception\LockWaitTimeoutException($message, $exception); + return new LockWaitTimeoutException($message, $exception); case '-100': case '-103': case '-832': - return new Exception\ConnectionException($message, $exception); + return new ConnectionException($message, $exception); case '-143': - return new Exception\InvalidFieldNameException($message, $exception); + return new InvalidFieldNameException($message, $exception); case '-193': case '-196': - return new Exception\UniqueConstraintViolationException($message, $exception); + return new UniqueConstraintViolationException($message, $exception); case '-194': case '-198': - return new Exception\ForeignKeyConstraintViolationException($message, $exception); + return new ForeignKeyConstraintViolationException($message, $exception); case '-144': - return new Exception\NonUniqueFieldNameException($message, $exception); + return new NonUniqueFieldNameException($message, $exception); case '-184': case '-195': - return new Exception\NotNullConstraintViolationException($message, $exception); + return new NotNullConstraintViolationException($message, $exception); case '-131': - return new Exception\SyntaxErrorException($message, $exception); + return new SyntaxErrorException($message, $exception); case '-110': - return new Exception\TableExistsException($message, $exception); + return new TableExistsException($message, $exception); case '-141': case '-1041': - return new Exception\TableNotFoundException($message, $exception); + return new TableNotFoundException($message, $exception); } - return new Exception\DriverException($message, $exception); + return new DriverException($message, $exception); } /** diff --git a/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/PortWithoutHost.php b/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php similarity index 55% rename from lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/PortWithoutHost.php rename to lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php index e968421fe7a..ea8dcc461e5 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/PortWithoutHost.php +++ b/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver; +namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception; -use Doctrine\DBAL\Driver\AbstractDriverException; +use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ -final class PortWithoutHost extends AbstractDriverException +final class PortWithoutHost extends AbstractException { public static function new(): self { diff --git a/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php b/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php index d0ec0b687bb..340b7605a7e 100644 --- a/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php +++ b/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php @@ -4,7 +4,18 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\ConnectionException; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; +use Doctrine\DBAL\Exception\InvalidFieldNameException; +use Doctrine\DBAL\Exception\LockWaitTimeoutException; +use Doctrine\DBAL\Exception\NonUniqueFieldNameException; +use Doctrine\DBAL\Exception\NotNullConstraintViolationException; +use Doctrine\DBAL\Exception\ReadOnlyException; +use Doctrine\DBAL\Exception\SyntaxErrorException; +use Doctrine\DBAL\Exception\TableExistsException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\SqliteSchemaManager; @@ -20,10 +31,10 @@ abstract class AbstractSQLiteDriver implements Driver, ExceptionConverterDriver * * @link http://www.sqlite.org/c3ref/c_abort.html */ - public function convertException($message, DriverException $exception) + public function convertException($message, Exception $exception) { if (strpos($exception->getMessage(), 'database is locked') !== false) { - return new Exception\LockWaitTimeoutException($message, $exception); + return new LockWaitTimeoutException($message, $exception); } if ( @@ -32,49 +43,49 @@ public function convertException($message, DriverException $exception) strpos($exception->getMessage(), 'are not unique') !== false || strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false ) { - return new Exception\UniqueConstraintViolationException($message, $exception); + return new UniqueConstraintViolationException($message, $exception); } if ( strpos($exception->getMessage(), 'may not be NULL') !== false || strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false ) { - return new Exception\NotNullConstraintViolationException($message, $exception); + return new NotNullConstraintViolationException($message, $exception); } if (strpos($exception->getMessage(), 'no such table:') !== false) { - return new Exception\TableNotFoundException($message, $exception); + return new TableNotFoundException($message, $exception); } if (strpos($exception->getMessage(), 'already exists') !== false) { - return new Exception\TableExistsException($message, $exception); + return new TableExistsException($message, $exception); } if (strpos($exception->getMessage(), 'has no column named') !== false) { - return new Exception\InvalidFieldNameException($message, $exception); + return new InvalidFieldNameException($message, $exception); } if (strpos($exception->getMessage(), 'ambiguous column name') !== false) { - return new Exception\NonUniqueFieldNameException($message, $exception); + return new NonUniqueFieldNameException($message, $exception); } if (strpos($exception->getMessage(), 'syntax error') !== false) { - return new Exception\SyntaxErrorException($message, $exception); + return new SyntaxErrorException($message, $exception); } if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) { - return new Exception\ReadOnlyException($message, $exception); + return new ReadOnlyException($message, $exception); } if (strpos($exception->getMessage(), 'unable to open database file') !== false) { - return new Exception\ConnectionException($message, $exception); + return new ConnectionException($message, $exception); } if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) { - return new Exception\ForeignKeyConstraintViolationException($message, $exception); + return new ForeignKeyConstraintViolationException($message, $exception); } - return new Exception\DriverException($message, $exception); + return new DriverException($message, $exception); } /** diff --git a/lib/Doctrine/DBAL/Driver/DriverException.php b/lib/Doctrine/DBAL/Driver/DriverException.php index a7f4008e1b7..2e83e82bee1 100644 --- a/lib/Doctrine/DBAL/Driver/DriverException.php +++ b/lib/Doctrine/DBAL/Driver/DriverException.php @@ -2,34 +2,11 @@ namespace Doctrine\DBAL\Driver; -use Throwable; - /** - * Contract for a driver exception. - * - * Driver exceptions provide the SQLSTATE of the driver - * and the driver specific error code at the time the error occurred. + * @deprecated Use {@link Exception} instead * * @psalm-immutable */ -interface DriverException extends Throwable +interface DriverException extends Exception { - /** - * Returns the driver specific error code if available. - * - * Returns null if no driver specific error code is available - * for the error raised by the driver. - * - * @return int|string|null - */ - public function getErrorCode(); - - /** - * Returns the SQLSTATE the driver was in at the time the error occurred. - * - * Returns null if the driver does not provide a SQLSTATE for the error occurred. - * - * @return string|null - */ - public function getSQLState(); } diff --git a/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php b/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php index 058bd19e2d0..6880607f76d 100644 --- a/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php +++ b/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php @@ -2,13 +2,13 @@ namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection as BaseConnection; use Doctrine\DBAL\ParameterType; /** * @deprecated */ -class Connection extends PDOConnection +class Connection extends BaseConnection { /** * {@inheritdoc} diff --git a/lib/Doctrine/DBAL/Driver/Exception.php b/lib/Doctrine/DBAL/Driver/Exception.php new file mode 100644 index 00000000000..03b3ff3f258 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/Exception.php @@ -0,0 +1,30 @@ +> * - * @throws DriverException + * @throws Exception */ public static function fetchAllNumeric(Result $result): array { @@ -44,7 +44,7 @@ public static function fetchAllNumeric(Result $result): array /** * @return array> * - * @throws DriverException + * @throws Exception */ public static function fetchAllAssociative(Result $result): array { @@ -60,7 +60,7 @@ public static function fetchAllAssociative(Result $result): array /** * @return array * - * @throws DriverException + * @throws Exception */ public static function fetchFirstColumn(Result $result): array { diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php b/lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php new file mode 100644 index 00000000000..73f04290435 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php @@ -0,0 +1,209 @@ +conn = $conn; + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + $serverInfo = db2_server_info($this->conn); + assert($serverInfo instanceof stdClass); + + return $serverInfo->DBMS_VER; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function prepare($sql) + { + $stmt = @db2_prepare($this->conn, $sql); + + if ($stmt === false) { + throw PrepareFailed::new(error_get_last()['message']); + } + + return new Statement($stmt); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = ParameterType::STRING) + { + $input = db2_escape_string($input); + + if ($type === ParameterType::INTEGER) { + return $input; + } + + return "'" . $input . "'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = @db2_exec($this->conn, $statement); + + if ($stmt === false) { + throw ConnectionError::new($this->conn); + } + + return db2_num_rows($stmt); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return db2_last_insert_id($this->conn); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_OFF); + assert(is_bool($result)); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (! db2_commit($this->conn)) { + throw ConnectionError::new($this->conn); + } + + $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); + assert(is_bool($result)); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + if (! db2_rollback($this->conn)) { + throw ConnectionError::new($this->conn); + } + + $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); + assert(is_bool($result)); + + return $result; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + return db2_conn_error($this->conn); + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + return [ + 0 => db2_conn_errormsg($this->conn), + 1 => $this->errorCode(), + ]; + } +} diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php index d74f9db795c..0d044421890 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php @@ -2,208 +2,9 @@ namespace Doctrine\DBAL\Driver\IBMDB2; -use Doctrine\DBAL\Driver\Connection; -use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError; -use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed; -use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed; -use Doctrine\DBAL\Driver\ServerInfoAwareConnection; -use Doctrine\DBAL\ParameterType; -use stdClass; - -use function assert; -use function db2_autocommit; -use function db2_commit; -use function db2_conn_error; -use function db2_conn_errormsg; -use function db2_connect; -use function db2_escape_string; -use function db2_exec; -use function db2_last_insert_id; -use function db2_num_rows; -use function db2_pconnect; -use function db2_prepare; -use function db2_rollback; -use function db2_server_info; -use function error_get_last; -use function func_get_args; -use function is_bool; - -use const DB2_AUTOCOMMIT_OFF; -use const DB2_AUTOCOMMIT_ON; - -class DB2Connection implements Connection, ServerInfoAwareConnection +/** + * @deprecated Use {@link Connection} instead + */ +class DB2Connection extends Connection { - /** @var resource */ - private $conn = null; - - /** - * @param mixed[] $params - * @param string $username - * @param string $password - * @param mixed[] $driverOptions - * - * @throws DB2Exception - */ - public function __construct(array $params, $username, $password, $driverOptions = []) - { - $isPersistent = (isset($params['persistent']) && $params['persistent'] === true); - - if ($isPersistent) { - $conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions); - } else { - $conn = db2_connect($params['dbname'], $username, $password, $driverOptions); - } - - if ($conn === false) { - throw ConnectionFailed::new(); - } - - $this->conn = $conn; - } - - /** - * {@inheritdoc} - */ - public function getServerVersion() - { - $serverInfo = db2_server_info($this->conn); - assert($serverInfo instanceof stdClass); - - return $serverInfo->DBMS_VER; - } - - /** - * {@inheritdoc} - */ - public function requiresQueryForServerVersion() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function prepare($sql) - { - $stmt = @db2_prepare($this->conn, $sql); - - if ($stmt === false) { - throw PrepareFailed::new(error_get_last()['message']); - } - - return new DB2Statement($stmt); - } - - /** - * {@inheritdoc} - */ - public function query() - { - $args = func_get_args(); - $sql = $args[0]; - $stmt = $this->prepare($sql); - $stmt->execute(); - - return $stmt; - } - - /** - * {@inheritdoc} - */ - public function quote($input, $type = ParameterType::STRING) - { - $input = db2_escape_string($input); - - if ($type === ParameterType::INTEGER) { - return $input; - } - - return "'" . $input . "'"; - } - - /** - * {@inheritdoc} - */ - public function exec($statement) - { - $stmt = @db2_exec($this->conn, $statement); - - if ($stmt === false) { - throw ConnectionError::new($this->conn); - } - - return db2_num_rows($stmt); - } - - /** - * {@inheritdoc} - */ - public function lastInsertId($name = null) - { - return db2_last_insert_id($this->conn); - } - - /** - * {@inheritdoc} - */ - public function beginTransaction() - { - $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_OFF); - assert(is_bool($result)); - - return $result; - } - - /** - * {@inheritdoc} - */ - public function commit() - { - if (! db2_commit($this->conn)) { - throw ConnectionError::new($this->conn); - } - - $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); - assert(is_bool($result)); - - return $result; - } - - /** - * {@inheritdoc} - */ - public function rollBack() - { - if (! db2_rollback($this->conn)) { - throw ConnectionError::new($this->conn); - } - - $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); - assert(is_bool($result)); - - return $result; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorCode() - { - return db2_conn_error($this->conn); - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorInfo() - { - return [ - 0 => db2_conn_errormsg($this->conn), - 1 => $this->errorCode(), - ]; - } } diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php index 650d75ceac7..6c27619b71a 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php @@ -2,37 +2,9 @@ namespace Doctrine\DBAL\Driver\IBMDB2; -use Doctrine\DBAL\Driver\AbstractDB2Driver; - /** - * IBM DB2 Driver. + * @deprecated Use {@link Driver} instead */ -class DB2Driver extends AbstractDB2Driver +class DB2Driver extends Driver { - /** - * {@inheritdoc} - */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) - { - $params['user'] = $username; - $params['password'] = $password; - $params['dbname'] = DataSourceName::fromConnectionParameters($params)->toString(); - - return new DB2Connection( - $params, - (string) $username, - (string) $password, - $driverOptions - ); - } - - /** - * {@inheritdoc} - * - * @deprecated - */ - public function getName() - { - return 'ibm_db2'; - } } diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php index f66d0e533f9..9fd45c0700d 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php @@ -2,11 +2,11 @@ namespace Doctrine\DBAL\Driver\IBMDB2; -use Doctrine\DBAL\Driver\AbstractDriverException; - /** + * @deprecated Use {@link Exception} instead + * * @psalm-immutable */ -class DB2Exception extends AbstractDriverException +class DB2Exception extends Exception { } diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php index 4f74c2a1f7f..ae093d0219e 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php @@ -2,551 +2,9 @@ namespace Doctrine\DBAL\Driver\IBMDB2; -use Doctrine\DBAL\Driver\FetchUtils; -use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; -use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\Statement; -use Doctrine\DBAL\Driver\StatementIterator; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\ParameterType; -use IteratorAggregate; -use PDO; -use ReflectionClass; -use ReflectionObject; -use ReflectionProperty; -use stdClass; - -use function array_change_key_case; -use function assert; -use function db2_bind_param; -use function db2_execute; -use function db2_fetch_array; -use function db2_fetch_assoc; -use function db2_fetch_both; -use function db2_fetch_object; -use function db2_free_result; -use function db2_num_fields; -use function db2_num_rows; -use function db2_stmt_error; -use function db2_stmt_errormsg; -use function error_get_last; -use function fclose; -use function func_get_args; -use function func_num_args; -use function fwrite; -use function gettype; -use function is_int; -use function is_object; -use function is_resource; -use function is_string; -use function ksort; -use function sprintf; -use function stream_copy_to_stream; -use function stream_get_meta_data; -use function strtolower; -use function tmpfile; - -use const CASE_LOWER; -use const DB2_BINARY; -use const DB2_CHAR; -use const DB2_LONG; -use const DB2_PARAM_FILE; -use const DB2_PARAM_IN; - -class DB2Statement implements IteratorAggregate, Statement, Result +/** + * @deprecated Use {@link Statement} instead + */ +class DB2Statement extends Statement { - /** @var resource */ - private $stmt; - - /** @var mixed[] */ - private $bindParam = []; - - /** - * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement - * and the temporary file handle bound to the underlying statement - * - * @var mixed[][] - */ - private $lobs = []; - - /** @var string Name of the default class to instantiate when fetching class instances. */ - private $defaultFetchClass = '\stdClass'; - - /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */ - private $defaultFetchClassCtorArgs = []; - - /** @var int */ - private $defaultFetchMode = FetchMode::MIXED; - - /** - * Indicates whether the statement is in the state when fetching results is possible - * - * @var bool - */ - private $result = false; - - /** - * @param resource $stmt - */ - public function __construct($stmt) - { - $this->stmt = $stmt; - } - - /** - * {@inheritdoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING) - { - assert(is_int($param)); - - return $this->bindParam($param, $value, $type); - } - - /** - * {@inheritdoc} - */ - public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) - { - assert(is_int($column)); - - switch ($type) { - case ParameterType::INTEGER: - $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG); - break; - - case ParameterType::LARGE_OBJECT: - if (isset($this->lobs[$column])) { - [, $handle] = $this->lobs[$column]; - fclose($handle); - } - - $handle = $this->createTemporaryFile(); - $path = stream_get_meta_data($handle)['uri']; - - $this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY); - - $this->lobs[$column] = [&$variable, $handle]; - break; - - default: - $this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR); - break; - } - - return true; - } - - /** - * @param int $position Parameter position - * @param mixed $variable - * - * @throws DB2Exception - */ - private function bind($position, &$variable, int $parameterType, int $dataType): void - { - $this->bindParam[$position] =& $variable; - - if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) { - throw StatementError::new($this->stmt); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use free() instead. - */ - public function closeCursor() - { - $this->bindParam = []; - - if (! db2_free_result($this->stmt)) { - return false; - } - - $this->result = false; - - return true; - } - - /** - * {@inheritdoc} - */ - public function columnCount() - { - return db2_num_fields($this->stmt) ?: 0; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorCode() - { - return db2_stmt_error(); - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorInfo() - { - return [ - db2_stmt_errormsg(), - db2_stmt_error(), - ]; - } - - /** - * {@inheritdoc} - */ - public function execute($params = null) - { - if ($params === null) { - ksort($this->bindParam); - - $params = []; - - foreach ($this->bindParam as $column => $value) { - $params[] = $value; - } - } - - foreach ($this->lobs as [$source, $target]) { - if (is_resource($source)) { - $this->copyStreamToStream($source, $target); - - continue; - } - - $this->writeStringToStream($source, $target); - } - - $retval = db2_execute($this->stmt, $params); - - foreach ($this->lobs as [, $handle]) { - fclose($handle); - } - - $this->lobs = []; - - if ($retval === false) { - throw StatementError::new($this->stmt); - } - - $this->result = true; - - return $retval; - } - - /** - * {@inheritdoc} - * - * @deprecated Use one of the fetch- or iterate-related methods. - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) - { - $this->defaultFetchMode = $fetchMode; - $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; - $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; - - return true; - } - - /** - * {@inheritdoc} - * - * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. - */ - public function getIterator() - { - return new StatementIterator($this); - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. - */ - public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - // do not try fetching from the statement if it's not expected to contain result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - $fetchMode = $fetchMode ?: $this->defaultFetchMode; - switch ($fetchMode) { - case FetchMode::COLUMN: - return $this->fetchColumn(); - - case FetchMode::MIXED: - return db2_fetch_both($this->stmt); - - case FetchMode::ASSOCIATIVE: - return db2_fetch_assoc($this->stmt); - - case FetchMode::CUSTOM_OBJECT: - $className = $this->defaultFetchClass; - $ctorArgs = $this->defaultFetchClassCtorArgs; - - if (func_num_args() >= 2) { - $args = func_get_args(); - $className = $args[1]; - $ctorArgs = $args[2] ?? []; - } - - $result = db2_fetch_object($this->stmt); - - if ($result instanceof stdClass) { - $result = $this->castObject($result, $className, $ctorArgs); - } - - return $result; - - case FetchMode::NUMERIC: - return db2_fetch_array($this->stmt); - - case FetchMode::STANDARD_OBJECT: - return db2_fetch_object($this->stmt); - - default: - throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.'); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - $rows = []; - - switch ($fetchMode) { - case FetchMode::CUSTOM_OBJECT: - while (($row = $this->fetch(...func_get_args())) !== false) { - $rows[] = $row; - } - - break; - - case FetchMode::COLUMN: - while (($row = $this->fetchColumn()) !== false) { - $rows[] = $row; - } - - break; - - default: - while (($row = $this->fetch($fetchMode)) !== false) { - $rows[] = $row; - } - } - - return $rows; - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchOne() instead. - */ - public function fetchColumn($columnIndex = 0) - { - $row = $this->fetch(FetchMode::NUMERIC); - - if ($row === false) { - return false; - } - - return $row[$columnIndex] ?? null; - } - - /** - * {@inheritDoc} - */ - public function fetchNumeric() - { - if (! $this->result) { - return false; - } - - return db2_fetch_array($this->stmt); - } - - /** - * {@inheritdoc} - */ - public function fetchAssociative() - { - // do not try fetching from the statement if it's not expected to contain the result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - return db2_fetch_assoc($this->stmt); - } - - /** - * {@inheritdoc} - */ - public function fetchOne() - { - return FetchUtils::fetchOne($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllNumeric(): array - { - return FetchUtils::fetchAllNumeric($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllAssociative(): array - { - return FetchUtils::fetchAllAssociative($this); - } - - /** - * {@inheritdoc} - */ - public function fetchFirstColumn(): array - { - return FetchUtils::fetchFirstColumn($this); - } - - /** - * {@inheritdoc} - */ - public function rowCount() - { - return @db2_num_rows($this->stmt) ? : 0; - } - - public function free(): void - { - $this->bindParam = []; - - db2_free_result($this->stmt); - - $this->result = false; - } - - /** - * Casts a stdClass object to the given class name mapping its' properties. - * - * @param stdClass $sourceObject Object to cast from. - * @param string|object $destinationClass Name of the class or class instance to cast to. - * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. - * - * @return object - * - * @throws DB2Exception - */ - private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) - { - if (! is_string($destinationClass)) { - if (! is_object($destinationClass)) { - throw new DB2Exception(sprintf( - 'Destination class has to be of type string or object, %s given.', - gettype($destinationClass) - )); - } - } else { - $destinationClass = new ReflectionClass($destinationClass); - $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); - } - - $sourceReflection = new ReflectionObject($sourceObject); - $destinationClassReflection = new ReflectionObject($destinationClass); - /** @var ReflectionProperty[] $destinationProperties */ - $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER); - - foreach ($sourceReflection->getProperties() as $sourceProperty) { - $sourceProperty->setAccessible(true); - - $name = $sourceProperty->getName(); - $value = $sourceProperty->getValue($sourceObject); - - // Try to find a case-matching property. - if ($destinationClassReflection->hasProperty($name)) { - $destinationProperty = $destinationClassReflection->getProperty($name); - - $destinationProperty->setAccessible(true); - $destinationProperty->setValue($destinationClass, $value); - - continue; - } - - $name = strtolower($name); - - // Try to find a property without matching case. - // Fallback for the driver returning either all uppercase or all lowercase column names. - if (isset($destinationProperties[$name])) { - $destinationProperty = $destinationProperties[$name]; - - $destinationProperty->setAccessible(true); - $destinationProperty->setValue($destinationClass, $value); - - continue; - } - - $destinationClass->$name = $value; - } - - return $destinationClass; - } - - /** - * @return resource - * - * @throws DB2Exception - */ - private function createTemporaryFile() - { - $handle = @tmpfile(); - - if ($handle === false) { - throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']); - } - - return $handle; - } - - /** - * @param resource $source - * @param resource $target - * - * @throws DB2Exception - */ - private function copyStreamToStream($source, $target): void - { - if (@stream_copy_to_stream($source, $target) === false) { - throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']); - } - } - - /** - * @param resource $target - * - * @throws DB2Exception - */ - private function writeStringToStream(string $string, $target): void - { - if (@fwrite($target, $string) === false) { - throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']); - } - } } diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/Driver.php b/lib/Doctrine/DBAL/Driver/IBMDB2/Driver.php new file mode 100644 index 00000000000..f4d116c3f23 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/Driver.php @@ -0,0 +1,38 @@ +toString(); + + return new Connection( + $params, + (string) $username, + (string) $password, + $driverOptions + ); + } + + /** + * {@inheritdoc} + * + * @deprecated + */ + public function getName() + { + return 'ibm_db2'; + } +} diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/Exception.php b/lib/Doctrine/DBAL/Driver/IBMDB2/Exception.php new file mode 100644 index 00000000000..b08984dc3ef --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/Exception.php @@ -0,0 +1,12 @@ +stmt = $stmt; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + assert(is_int($param)); + + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) + { + assert(is_int($column)); + + switch ($type) { + case ParameterType::INTEGER: + $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG); + break; + + case ParameterType::LARGE_OBJECT: + if (isset($this->lobs[$column])) { + [, $handle] = $this->lobs[$column]; + fclose($handle); + } + + $handle = $this->createTemporaryFile(); + $path = stream_get_meta_data($handle)['uri']; + + $this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY); + + $this->lobs[$column] = [&$variable, $handle]; + break; + + default: + $this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR); + break; + } + + return true; + } + + /** + * @param int $position Parameter position + * @param mixed $variable + * + * @throws Exception + */ + private function bind($position, &$variable, int $parameterType, int $dataType): void + { + $this->bindParam[$position] =& $variable; + + if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) { + throw StatementError::new($this->stmt); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use free() instead. + */ + public function closeCursor() + { + $this->bindParam = []; + + if (! db2_free_result($this->stmt)) { + return false; + } + + $this->result = false; + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return db2_num_fields($this->stmt) ?: 0; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + return db2_stmt_error(); + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + return [ + db2_stmt_errormsg(), + db2_stmt_error(), + ]; + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params === null) { + ksort($this->bindParam); + + $params = []; + + foreach ($this->bindParam as $column => $value) { + $params[] = $value; + } + } + + foreach ($this->lobs as [$source, $target]) { + if (is_resource($source)) { + $this->copyStreamToStream($source, $target); + + continue; + } + + $this->writeStringToStream($source, $target); + } + + $retval = db2_execute($this->stmt, $params); + + foreach ($this->lobs as [, $handle]) { + fclose($handle); + } + + $this->lobs = []; + + if ($retval === false) { + throw StatementError::new($this->stmt); + } + + $this->result = true; + + return $retval; + } + + /** + * {@inheritdoc} + * + * @deprecated Use one of the fetch- or iterate-related methods. + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; + $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; + + return true; + } + + /** + * {@inheritdoc} + * + * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. + */ + public function getIterator() + { + return new StatementIterator($this); + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. + */ + public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + switch ($fetchMode) { + case FetchMode::COLUMN: + return $this->fetchColumn(); + + case FetchMode::MIXED: + return db2_fetch_both($this->stmt); + + case FetchMode::ASSOCIATIVE: + return db2_fetch_assoc($this->stmt); + + case FetchMode::CUSTOM_OBJECT: + $className = $this->defaultFetchClass; + $ctorArgs = $this->defaultFetchClassCtorArgs; + + if (func_num_args() >= 2) { + $args = func_get_args(); + $className = $args[1]; + $ctorArgs = $args[2] ?? []; + } + + $result = db2_fetch_object($this->stmt); + + if ($result instanceof stdClass) { + $result = $this->castObject($result, $className, $ctorArgs); + } + + return $result; + + case FetchMode::NUMERIC: + return db2_fetch_array($this->stmt); + + case FetchMode::STANDARD_OBJECT: + return db2_fetch_object($this->stmt); + + default: + throw new Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.'); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + $rows = []; + + switch ($fetchMode) { + case FetchMode::CUSTOM_OBJECT: + while (($row = $this->fetch(...func_get_args())) !== false) { + $rows[] = $row; + } + + break; + + case FetchMode::COLUMN: + while (($row = $this->fetchColumn()) !== false) { + $rows[] = $row; + } + + break; + + default: + while (($row = $this->fetch($fetchMode)) !== false) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchOne() instead. + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(FetchMode::NUMERIC); + + if ($row === false) { + return false; + } + + return $row[$columnIndex] ?? null; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + if (! $this->result) { + return false; + } + + return db2_fetch_array($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function fetchAssociative() + { + // do not try fetching from the statement if it's not expected to contain the result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + return db2_fetch_assoc($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritdoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return @db2_num_rows($this->stmt) ? : 0; + } + + public function free(): void + { + $this->bindParam = []; + + db2_free_result($this->stmt); + + $this->result = false; + } + + /** + * Casts a stdClass object to the given class name mapping its' properties. + * + * @param stdClass $sourceObject Object to cast from. + * @param string|object $destinationClass Name of the class or class instance to cast to. + * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. + * + * @return object + * + * @throws Exception + */ + private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) + { + if (! is_string($destinationClass)) { + if (! is_object($destinationClass)) { + throw new Exception(sprintf( + 'Destination class has to be of type string or object, %s given.', + gettype($destinationClass) + )); + } + } else { + $destinationClass = new ReflectionClass($destinationClass); + $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); + } + + $sourceReflection = new ReflectionObject($sourceObject); + $destinationClassReflection = new ReflectionObject($destinationClass); + /** @var ReflectionProperty[] $destinationProperties */ + $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER); + + foreach ($sourceReflection->getProperties() as $sourceProperty) { + $sourceProperty->setAccessible(true); + + $name = $sourceProperty->getName(); + $value = $sourceProperty->getValue($sourceObject); + + // Try to find a case-matching property. + if ($destinationClassReflection->hasProperty($name)) { + $destinationProperty = $destinationClassReflection->getProperty($name); + + $destinationProperty->setAccessible(true); + $destinationProperty->setValue($destinationClass, $value); + + continue; + } + + $name = strtolower($name); + + // Try to find a property without matching case. + // Fallback for the driver returning either all uppercase or all lowercase column names. + if (isset($destinationProperties[$name])) { + $destinationProperty = $destinationProperties[$name]; + + $destinationProperty->setAccessible(true); + $destinationProperty->setValue($destinationClass, $value); + + continue; + } + + $destinationClass->$name = $value; + } + + return $destinationClass; + } + + /** + * @return resource + * + * @throws Exception + */ + private function createTemporaryFile() + { + $handle = @tmpfile(); + + if ($handle === false) { + throw new Exception('Could not create temporary file: ' . error_get_last()['message']); + } + + return $handle; + } + + /** + * @param resource $source + * @param resource $target + * + * @throws Exception + */ + private function copyStreamToStream($source, $target): void + { + if (@stream_copy_to_stream($source, $target) === false) { + throw new Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']); + } + } + + /** + * @param resource $target + * + * @throws Exception + */ + private function writeStringToStream(string $string, $target): void + { + if (@fwrite($target, $string) === false) { + throw new Exception('Could not write string to temporary file: ' . error_get_last()['message']); + } + } +} diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Connection.php b/lib/Doctrine/DBAL/Driver/Mysqli/Connection.php new file mode 100644 index 00000000000..5292d33c177 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/Mysqli/Connection.php @@ -0,0 +1,314 @@ +conn = mysqli_init(); + + $this->setSecureConnection($params); + $this->setDriverOptions($driverOptions); + + set_error_handler(static function () { + }); + try { + if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) { + throw new Exception($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno); + } + } finally { + restore_error_handler(); + } + + if (! isset($params['charset'])) { + return; + } + + $this->conn->set_charset($params['charset']); + } + + /** + * Retrieves mysqli native resource handle. + * + * Could be used if part of your application is not using DBAL. + * + * @return mysqli + */ + public function getWrappedResourceHandle() + { + return $this->conn; + } + + /** + * {@inheritdoc} + * + * The server version detection includes a special case for MariaDB + * to support '5.5.5-' prefixed versions introduced in Maria 10+ + * + * @link https://jira.mariadb.org/browse/MDEV-4088 + */ + public function getServerVersion() + { + $serverInfos = $this->conn->get_server_info(); + if (stripos($serverInfos, 'mariadb') !== false) { + return $serverInfos; + } + + $majorVersion = floor($this->conn->server_version / 10000); + $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100); + $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100); + + return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new Statement($this->conn, $prepareString); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = ParameterType::STRING) + { + return "'" . $this->conn->escape_string($input) . "'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + if ($this->conn->query($statement) === false) { + throw new Exception($this->conn->error, $this->conn->sqlstate, $this->conn->errno); + } + + return $this->conn->affected_rows; + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return $this->conn->insert_id; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->conn->query('START TRANSACTION'); + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->conn->commit(); + } + + /** + * {@inheritdoc}non-PHPdoc) + */ + public function rollBack() + { + return $this->conn->rollback(); + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + * + * @return int + */ + public function errorCode() + { + return $this->conn->errno; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + * + * @return string + */ + public function errorInfo() + { + return $this->conn->error; + } + + /** + * Apply the driver options to the connection. + * + * @param mixed[] $driverOptions + * + * @throws Exception When one of of the options is not supported. + * @throws Exception When applying doesn't work - e.g. due to incorrect value. + */ + private function setDriverOptions(array $driverOptions = []): void + { + $supportedDriverOptions = [ + MYSQLI_OPT_CONNECT_TIMEOUT, + MYSQLI_OPT_LOCAL_INFILE, + MYSQLI_INIT_COMMAND, + MYSQLI_READ_DEFAULT_FILE, + MYSQLI_READ_DEFAULT_GROUP, + ]; + + if (defined('MYSQLI_SERVER_PUBLIC_KEY')) { + $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY; + } + + $exceptionMsg = "%s option '%s' with value '%s'"; + + foreach ($driverOptions as $option => $value) { + if ($option === static::OPTION_FLAGS) { + continue; + } + + if (! in_array($option, $supportedDriverOptions, true)) { + throw new Exception( + sprintf($exceptionMsg, 'Unsupported', $option, $value) + ); + } + + if (@mysqli_options($this->conn, $option, $value)) { + continue; + } + + $msg = sprintf($exceptionMsg, 'Failed to set', $option, $value); + $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn)); + + throw new Exception( + $msg, + $this->conn->sqlstate, + $this->conn->errno + ); + } + } + + /** + * Pings the server and re-connects when `mysqli.reconnect = 1` + * + * @return bool + */ + public function ping() + { + return $this->conn->ping(); + } + + /** + * Establish a secure connection + * + * @param mixed[] $params + * + * @throws Exception + */ + private function setSecureConnection(array $params): void + { + if ( + ! isset($params['ssl_key']) && + ! isset($params['ssl_cert']) && + ! isset($params['ssl_ca']) && + ! isset($params['ssl_capath']) && + ! isset($params['ssl_cipher']) + ) { + return; + } + + $this->conn->ssl_set( + $params['ssl_key'] ?? null, + $params['ssl_cert'] ?? null, + $params['ssl_ca'] ?? null, + $params['ssl_capath'] ?? null, + $params['ssl_cipher'] ?? null + ); + } +} diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php b/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php index 1166e8d4c2e..59382ae8a25 100644 --- a/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php +++ b/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php @@ -13,8 +13,8 @@ class Driver extends AbstractMySQLDriver public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { - return new MysqliConnection($params, (string) $username, (string) $password, $driverOptions); - } catch (MysqliException $e) { + return new Connection($params, (string) $username, (string) $password, $driverOptions); + } catch (Exception $e) { throw DBALException::driverException($this, $e); } } diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Exception.php b/lib/Doctrine/DBAL/Driver/Mysqli/Exception.php new file mode 100644 index 00000000000..7433b467114 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/Mysqli/Exception.php @@ -0,0 +1,14 @@ +conn = mysqli_init(); - - $this->setSecureConnection($params); - $this->setDriverOptions($driverOptions); - - set_error_handler(static function () { - }); - try { - if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) { - throw new MysqliException($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno); - } - } finally { - restore_error_handler(); - } - - if (! isset($params['charset'])) { - return; - } - - $this->conn->set_charset($params['charset']); - } - - /** - * Retrieves mysqli native resource handle. - * - * Could be used if part of your application is not using DBAL. - * - * @return mysqli - */ - public function getWrappedResourceHandle() - { - return $this->conn; - } - - /** - * {@inheritdoc} - * - * The server version detection includes a special case for MariaDB - * to support '5.5.5-' prefixed versions introduced in Maria 10+ - * - * @link https://jira.mariadb.org/browse/MDEV-4088 - */ - public function getServerVersion() - { - $serverInfos = $this->conn->get_server_info(); - if (stripos($serverInfos, 'mariadb') !== false) { - return $serverInfos; - } - - $majorVersion = floor($this->conn->server_version / 10000); - $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100); - $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100); - - return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; - } - - /** - * {@inheritdoc} - */ - public function requiresQueryForServerVersion() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function prepare($prepareString) - { - return new MysqliStatement($this->conn, $prepareString); - } - - /** - * {@inheritdoc} - */ - public function query() - { - $args = func_get_args(); - $sql = $args[0]; - $stmt = $this->prepare($sql); - $stmt->execute(); - - return $stmt; - } - - /** - * {@inheritdoc} - */ - public function quote($input, $type = ParameterType::STRING) - { - return "'" . $this->conn->escape_string($input) . "'"; - } - - /** - * {@inheritdoc} - */ - public function exec($statement) - { - if ($this->conn->query($statement) === false) { - throw new MysqliException($this->conn->error, $this->conn->sqlstate, $this->conn->errno); - } - - return $this->conn->affected_rows; - } - - /** - * {@inheritdoc} - */ - public function lastInsertId($name = null) - { - return $this->conn->insert_id; - } - - /** - * {@inheritdoc} - */ - public function beginTransaction() - { - $this->conn->query('START TRANSACTION'); - - return true; - } - - /** - * {@inheritdoc} - */ - public function commit() - { - return $this->conn->commit(); - } - - /** - * {@inheritdoc}non-PHPdoc) - */ - public function rollBack() - { - return $this->conn->rollback(); - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - * - * @return int - */ - public function errorCode() - { - return $this->conn->errno; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - * - * @return string - */ - public function errorInfo() - { - return $this->conn->error; - } - - /** - * Apply the driver options to the connection. - * - * @param mixed[] $driverOptions - * - * @throws MysqliException When one of of the options is not supported. - * @throws MysqliException When applying doesn't work - e.g. due to incorrect value. - */ - private function setDriverOptions(array $driverOptions = []): void - { - $supportedDriverOptions = [ - MYSQLI_OPT_CONNECT_TIMEOUT, - MYSQLI_OPT_LOCAL_INFILE, - MYSQLI_INIT_COMMAND, - MYSQLI_READ_DEFAULT_FILE, - MYSQLI_READ_DEFAULT_GROUP, - ]; - - if (defined('MYSQLI_SERVER_PUBLIC_KEY')) { - $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY; - } - - $exceptionMsg = "%s option '%s' with value '%s'"; - - foreach ($driverOptions as $option => $value) { - if ($option === static::OPTION_FLAGS) { - continue; - } - - if (! in_array($option, $supportedDriverOptions, true)) { - throw new MysqliException( - sprintf($exceptionMsg, 'Unsupported', $option, $value) - ); - } - - if (@mysqli_options($this->conn, $option, $value)) { - continue; - } - - $msg = sprintf($exceptionMsg, 'Failed to set', $option, $value); - $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn)); - - throw new MysqliException( - $msg, - $this->conn->sqlstate, - $this->conn->errno - ); - } - } - - /** - * Pings the server and re-connects when `mysqli.reconnect = 1` - * - * @return bool - */ - public function ping() - { - return $this->conn->ping(); - } - - /** - * Establish a secure connection - * - * @param mixed[] $params - * - * @throws MysqliException - */ - private function setSecureConnection(array $params): void - { - if ( - ! isset($params['ssl_key']) && - ! isset($params['ssl_cert']) && - ! isset($params['ssl_ca']) && - ! isset($params['ssl_capath']) && - ! isset($params['ssl_cipher']) - ) { - return; - } - - $this->conn->ssl_set( - $params['ssl_key'] ?? null, - $params['ssl_cert'] ?? null, - $params['ssl_ca'] ?? null, - $params['ssl_capath'] ?? null, - $params['ssl_cipher'] ?? null - ); - } } diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php index 8e8e697c4a3..338c95009e2 100644 --- a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php +++ b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php @@ -2,13 +2,11 @@ namespace Doctrine\DBAL\Driver\Mysqli; -use Doctrine\DBAL\Driver\AbstractDriverException; - /** - * Exception thrown in case the mysqli driver errors. + * @deprecated Use {@link Exception} instead * * @psalm-immutable */ -class MysqliException extends AbstractDriverException +class MysqliException extends Exception { } diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php index c3f893fc127..e58f0c33338 100644 --- a/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php +++ b/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php @@ -2,557 +2,9 @@ namespace Doctrine\DBAL\Driver\Mysqli; -use Doctrine\DBAL\Driver\FetchUtils; -use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\Statement; -use Doctrine\DBAL\Driver\StatementIterator; -use Doctrine\DBAL\Exception\InvalidArgumentException; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\ParameterType; -use IteratorAggregate; -use mysqli; -use mysqli_stmt; -use PDO; - -use function array_combine; -use function array_fill; -use function assert; -use function count; -use function feof; -use function fread; -use function get_resource_type; -use function is_array; -use function is_int; -use function is_resource; -use function sprintf; -use function str_repeat; - -class MysqliStatement implements IteratorAggregate, Statement, Result +/** + * @deprecated Use {@link Statement} instead + */ +class MysqliStatement extends Statement { - /** @var string[] */ - protected static $_paramTypeMap = [ - ParameterType::STRING => 's', - ParameterType::BINARY => 's', - ParameterType::BOOLEAN => 'i', - ParameterType::NULL => 's', - ParameterType::INTEGER => 'i', - ParameterType::LARGE_OBJECT => 'b', - ]; - - /** @var mysqli */ - protected $_conn; - - /** @var mysqli_stmt */ - protected $_stmt; - - /** @var string[]|false|null */ - protected $_columnNames; - - /** @var mixed[] */ - protected $_rowBindedValues = []; - - /** @var mixed[] */ - protected $_bindedValues; - - /** @var string */ - protected $types; - - /** - * Contains ref values for bindValue(). - * - * @var mixed[] - */ - protected $_values = []; - - /** @var int */ - protected $_defaultFetchMode = FetchMode::MIXED; - - /** - * Indicates whether the statement is in the state when fetching results is possible - * - * @var bool - */ - private $result = false; - - /** - * @param string $prepareString - * - * @throws MysqliException - */ - public function __construct(mysqli $conn, $prepareString) - { - $this->_conn = $conn; - - $stmt = $conn->prepare($prepareString); - - if ($stmt === false) { - throw new MysqliException($this->_conn->error, $this->_conn->sqlstate, $this->_conn->errno); - } - - $this->_stmt = $stmt; - - $paramCount = $this->_stmt->param_count; - if (0 >= $paramCount) { - return; - } - - $this->types = str_repeat('s', $paramCount); - $this->_bindedValues = array_fill(1, $paramCount, null); - } - - /** - * {@inheritdoc} - */ - public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) - { - assert(is_int($column)); - - if (! isset(self::$_paramTypeMap[$type])) { - throw new MysqliException(sprintf("Unknown type: '%s'", $type)); - } - - $this->_bindedValues[$column] =& $variable; - $this->types[$column - 1] = self::$_paramTypeMap[$type]; - - return true; - } - - /** - * {@inheritdoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING) - { - assert(is_int($param)); - - if (! isset(self::$_paramTypeMap[$type])) { - throw new MysqliException(sprintf("Unknown type: '%s'", $type)); - } - - $this->_values[$param] = $value; - $this->_bindedValues[$param] =& $this->_values[$param]; - $this->types[$param - 1] = self::$_paramTypeMap[$type]; - - return true; - } - - /** - * {@inheritdoc} - */ - public function execute($params = null) - { - if ($this->_bindedValues !== null) { - if ($params !== null) { - if (! $this->bindUntypedValues($params)) { - throw new MysqliException($this->_stmt->error, $this->_stmt->errno); - } - } else { - $this->bindTypedParameters(); - } - } - - if (! $this->_stmt->execute()) { - throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); - } - - if ($this->_columnNames === null) { - $meta = $this->_stmt->result_metadata(); - if ($meta !== false) { - $fields = $meta->fetch_fields(); - assert(is_array($fields)); - - $columnNames = []; - foreach ($fields as $col) { - $columnNames[] = $col->name; - } - - $meta->free(); - - $this->_columnNames = $columnNames; - } else { - $this->_columnNames = false; - } - } - - if ($this->_columnNames !== false) { - // Store result of every execution which has it. Otherwise it will be impossible - // to execute a new statement in case if the previous one has non-fetched rows - // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html - $this->_stmt->store_result(); - - // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, - // it will have to allocate as much memory as it may be needed for the given column type - // (e.g. for a LONGBLOB field it's 4 gigabytes) - // @link https://bugs.php.net/bug.php?id=51386#1270673122 - // - // Make sure that the values are bound after each execution. Otherwise, if closeCursor() has been - // previously called on the statement, the values are unbound making the statement unusable. - // - // It's also important that row values are bound after _each_ call to store_result(). Otherwise, - // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated - // to the length of the ones fetched during the previous execution. - $this->_rowBindedValues = array_fill(0, count($this->_columnNames), null); - - $refs = []; - foreach ($this->_rowBindedValues as $key => &$value) { - $refs[$key] =& $value; - } - - if (! $this->_stmt->bind_result(...$refs)) { - throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); - } - } - - $this->result = true; - - return true; - } - - /** - * Binds parameters with known types previously bound to the statement - */ - private function bindTypedParameters(): void - { - $streams = $values = []; - $types = $this->types; - - foreach ($this->_bindedValues as $parameter => $value) { - assert(is_int($parameter)); - - if (! isset($types[$parameter - 1])) { - $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING]; - } - - if ($types[$parameter - 1] === static::$_paramTypeMap[ParameterType::LARGE_OBJECT]) { - if (is_resource($value)) { - if (get_resource_type($value) !== 'stream') { - throw new InvalidArgumentException('Resources passed with the LARGE_OBJECT parameter type must be stream resources.'); - } - - $streams[$parameter] = $value; - $values[$parameter] = null; - continue; - } - - $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING]; - } - - $values[$parameter] = $value; - } - - if (! $this->_stmt->bind_param($types, ...$values)) { - throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); - } - - $this->sendLongData($streams); - } - - /** - * Handle $this->_longData after regular query parameters have been bound - * - * @param array $streams - * - * @throws MysqliException - */ - private function sendLongData(array $streams): void - { - foreach ($streams as $paramNr => $stream) { - while (! feof($stream)) { - $chunk = fread($stream, 8192); - - if ($chunk === false) { - throw new MysqliException("Failed reading the stream resource for parameter offset ${paramNr}."); - } - - if (! $this->_stmt->send_long_data($paramNr - 1, $chunk)) { - throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); - } - } - } - } - - /** - * Binds a array of values to bound parameters. - * - * @param mixed[] $values - * - * @return bool - */ - private function bindUntypedValues(array $values) - { - $params = []; - $types = str_repeat('s', count($values)); - - foreach ($values as &$v) { - $params[] =& $v; - } - - return $this->_stmt->bind_param($types, ...$params); - } - - /** - * @return mixed[]|false|null - */ - private function _fetch() - { - $ret = $this->_stmt->fetch(); - - if ($ret === true) { - $values = []; - foreach ($this->_rowBindedValues as $v) { - $values[] = $v; - } - - return $values; - } - - return $ret; - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. - */ - public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - // do not try fetching from the statement if it's not expected to contain result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - $fetchMode = $fetchMode ?: $this->_defaultFetchMode; - - if ($fetchMode === FetchMode::COLUMN) { - return $this->fetchColumn(); - } - - $values = $this->_fetch(); - - if ($values === null) { - return false; - } - - if ($values === false) { - throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); - } - - if ($fetchMode === FetchMode::NUMERIC) { - return $values; - } - - assert(is_array($this->_columnNames)); - $assoc = array_combine($this->_columnNames, $values); - assert(is_array($assoc)); - - switch ($fetchMode) { - case FetchMode::ASSOCIATIVE: - return $assoc; - - case FetchMode::MIXED: - return $assoc + $values; - - case FetchMode::STANDARD_OBJECT: - return (object) $assoc; - - default: - throw new MysqliException(sprintf("Unknown fetch type '%s'", $fetchMode)); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - $fetchMode = $fetchMode ?: $this->_defaultFetchMode; - - $rows = []; - - if ($fetchMode === FetchMode::COLUMN) { - while (($row = $this->fetchColumn()) !== false) { - $rows[] = $row; - } - } else { - while (($row = $this->fetch($fetchMode)) !== false) { - $rows[] = $row; - } - } - - return $rows; - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchOne() instead. - */ - public function fetchColumn($columnIndex = 0) - { - $row = $this->fetch(FetchMode::NUMERIC); - - if ($row === false) { - return false; - } - - return $row[$columnIndex] ?? null; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function fetchNumeric() - { - // do not try fetching from the statement if it's not expected to contain the result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - $values = $this->_fetch(); - - if ($values === null) { - return false; - } - - if ($values === false) { - throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); - } - - return $values; - } - - /** - * {@inheritDoc} - */ - public function fetchAssociative() - { - $values = $this->fetchNumeric(); - - if ($values === false) { - return false; - } - - assert(is_array($this->_columnNames)); - $row = array_combine($this->_columnNames, $values); - assert(is_array($row)); - - return $row; - } - - /** - * {@inheritdoc} - */ - public function fetchOne() - { - return FetchUtils::fetchOne($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllNumeric(): array - { - return FetchUtils::fetchAllNumeric($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllAssociative(): array - { - return FetchUtils::fetchAllAssociative($this); - } - - /** - * {@inheritdoc} - */ - public function fetchFirstColumn(): array - { - return FetchUtils::fetchFirstColumn($this); - } - - /** - * {@inheritdoc} - */ - public function errorCode() - { - return $this->_stmt->errno; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - * - * @return string - */ - public function errorInfo() - { - return $this->_stmt->error; - } - - /** - * {@inheritdoc} - * - * @deprecated Use free() instead. - */ - public function closeCursor() - { - $this->free(); - - return true; - } - - /** - * {@inheritdoc} - */ - public function rowCount() - { - if ($this->_columnNames === false) { - return $this->_stmt->affected_rows; - } - - return $this->_stmt->num_rows; - } - - /** - * {@inheritdoc} - */ - public function columnCount() - { - return $this->_stmt->field_count; - } - - public function free(): void - { - $this->_stmt->free_result(); - $this->result = false; - } - - /** - * {@inheritdoc} - * - * @deprecated Use one of the fetch- or iterate-related methods. - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) - { - $this->_defaultFetchMode = $fetchMode; - - return true; - } - - /** - * {@inheritdoc} - * - * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. - */ - public function getIterator() - { - return new StatementIterator($this); - } } diff --git a/lib/Doctrine/DBAL/Driver/Mysqli/Statement.php b/lib/Doctrine/DBAL/Driver/Mysqli/Statement.php new file mode 100644 index 00000000000..983de17acbf --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/Mysqli/Statement.php @@ -0,0 +1,558 @@ + 's', + ParameterType::BINARY => 's', + ParameterType::BOOLEAN => 'i', + ParameterType::NULL => 's', + ParameterType::INTEGER => 'i', + ParameterType::LARGE_OBJECT => 'b', + ]; + + /** @var mysqli */ + protected $_conn; + + /** @var mysqli_stmt */ + protected $_stmt; + + /** @var string[]|false|null */ + protected $_columnNames; + + /** @var mixed[] */ + protected $_rowBindedValues = []; + + /** @var mixed[] */ + protected $_bindedValues; + + /** @var string */ + protected $types; + + /** + * Contains ref values for bindValue(). + * + * @var mixed[] + */ + protected $_values = []; + + /** @var int */ + protected $_defaultFetchMode = FetchMode::MIXED; + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * @param string $prepareString + * + * @throws Exception + */ + public function __construct(mysqli $conn, $prepareString) + { + $this->_conn = $conn; + + $stmt = $conn->prepare($prepareString); + + if ($stmt === false) { + throw new Exception($this->_conn->error, $this->_conn->sqlstate, $this->_conn->errno); + } + + $this->_stmt = $stmt; + + $paramCount = $this->_stmt->param_count; + if (0 >= $paramCount) { + return; + } + + $this->types = str_repeat('s', $paramCount); + $this->_bindedValues = array_fill(1, $paramCount, null); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) + { + assert(is_int($column)); + + if (! isset(self::$_paramTypeMap[$type])) { + throw new Exception(sprintf("Unknown type: '%s'", $type)); + } + + $this->_bindedValues[$column] =& $variable; + $this->types[$column - 1] = self::$_paramTypeMap[$type]; + + return true; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + assert(is_int($param)); + + if (! isset(self::$_paramTypeMap[$type])) { + throw new Exception(sprintf("Unknown type: '%s'", $type)); + } + + $this->_values[$param] = $value; + $this->_bindedValues[$param] =& $this->_values[$param]; + $this->types[$param - 1] = self::$_paramTypeMap[$type]; + + return true; + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($this->_bindedValues !== null) { + if ($params !== null) { + if (! $this->bindUntypedValues($params)) { + throw new Exception($this->_stmt->error, $this->_stmt->errno); + } + } else { + $this->bindTypedParameters(); + } + } + + if (! $this->_stmt->execute()) { + throw new Exception($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + + if ($this->_columnNames === null) { + $meta = $this->_stmt->result_metadata(); + if ($meta !== false) { + $fields = $meta->fetch_fields(); + assert(is_array($fields)); + + $columnNames = []; + foreach ($fields as $col) { + $columnNames[] = $col->name; + } + + $meta->free(); + + $this->_columnNames = $columnNames; + } else { + $this->_columnNames = false; + } + } + + if ($this->_columnNames !== false) { + // Store result of every execution which has it. Otherwise it will be impossible + // to execute a new statement in case if the previous one has non-fetched rows + // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html + $this->_stmt->store_result(); + + // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, + // it will have to allocate as much memory as it may be needed for the given column type + // (e.g. for a LONGBLOB field it's 4 gigabytes) + // @link https://bugs.php.net/bug.php?id=51386#1270673122 + // + // Make sure that the values are bound after each execution. Otherwise, if closeCursor() has been + // previously called on the statement, the values are unbound making the statement unusable. + // + // It's also important that row values are bound after _each_ call to store_result(). Otherwise, + // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated + // to the length of the ones fetched during the previous execution. + $this->_rowBindedValues = array_fill(0, count($this->_columnNames), null); + + $refs = []; + foreach ($this->_rowBindedValues as $key => &$value) { + $refs[$key] =& $value; + } + + if (! $this->_stmt->bind_result(...$refs)) { + throw new Exception($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + } + + $this->result = true; + + return true; + } + + /** + * Binds parameters with known types previously bound to the statement + */ + private function bindTypedParameters(): void + { + $streams = $values = []; + $types = $this->types; + + foreach ($this->_bindedValues as $parameter => $value) { + assert(is_int($parameter)); + + if (! isset($types[$parameter - 1])) { + $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING]; + } + + if ($types[$parameter - 1] === static::$_paramTypeMap[ParameterType::LARGE_OBJECT]) { + if (is_resource($value)) { + if (get_resource_type($value) !== 'stream') { + throw new InvalidArgumentException('Resources passed with the LARGE_OBJECT parameter type must be stream resources.'); + } + + $streams[$parameter] = $value; + $values[$parameter] = null; + continue; + } + + $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING]; + } + + $values[$parameter] = $value; + } + + if (! $this->_stmt->bind_param($types, ...$values)) { + throw new Exception($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + + $this->sendLongData($streams); + } + + /** + * Handle $this->_longData after regular query parameters have been bound + * + * @param array $streams + * + * @throws Exception + */ + private function sendLongData(array $streams): void + { + foreach ($streams as $paramNr => $stream) { + while (! feof($stream)) { + $chunk = fread($stream, 8192); + + if ($chunk === false) { + throw new Exception("Failed reading the stream resource for parameter offset ${paramNr}."); + } + + if (! $this->_stmt->send_long_data($paramNr - 1, $chunk)) { + throw new Exception($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + } + } + } + + /** + * Binds a array of values to bound parameters. + * + * @param mixed[] $values + * + * @return bool + */ + private function bindUntypedValues(array $values) + { + $params = []; + $types = str_repeat('s', count($values)); + + foreach ($values as &$v) { + $params[] =& $v; + } + + return $this->_stmt->bind_param($types, ...$params); + } + + /** + * @return mixed[]|false|null + */ + private function _fetch() + { + $ret = $this->_stmt->fetch(); + + if ($ret === true) { + $values = []; + foreach ($this->_rowBindedValues as $v) { + $values[] = $v; + } + + return $values; + } + + return $ret; + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. + */ + public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + if ($fetchMode === FetchMode::COLUMN) { + return $this->fetchColumn(); + } + + $values = $this->_fetch(); + + if ($values === null) { + return false; + } + + if ($values === false) { + throw new Exception($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + + if ($fetchMode === FetchMode::NUMERIC) { + return $values; + } + + assert(is_array($this->_columnNames)); + $assoc = array_combine($this->_columnNames, $values); + assert(is_array($assoc)); + + switch ($fetchMode) { + case FetchMode::ASSOCIATIVE: + return $assoc; + + case FetchMode::MIXED: + return $assoc + $values; + + case FetchMode::STANDARD_OBJECT: + return (object) $assoc; + + default: + throw new Exception(sprintf("Unknown fetch type '%s'", $fetchMode)); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + $rows = []; + + if ($fetchMode === FetchMode::COLUMN) { + while (($row = $this->fetchColumn()) !== false) { + $rows[] = $row; + } + } else { + while (($row = $this->fetch($fetchMode)) !== false) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchOne() instead. + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(FetchMode::NUMERIC); + + if ($row === false) { + return false; + } + + return $row[$columnIndex] ?? null; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function fetchNumeric() + { + // do not try fetching from the statement if it's not expected to contain the result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + $values = $this->_fetch(); + + if ($values === null) { + return false; + } + + if ($values === false) { + throw new Exception($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + + return $values; + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + $values = $this->fetchNumeric(); + + if ($values === false) { + return false; + } + + assert(is_array($this->_columnNames)); + $row = array_combine($this->_columnNames, $values); + assert(is_array($row)); + + return $row; + } + + /** + * {@inheritdoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritdoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->_stmt->errno; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + * + * @return string + */ + public function errorInfo() + { + return $this->_stmt->error; + } + + /** + * {@inheritdoc} + * + * @deprecated Use free() instead. + */ + public function closeCursor() + { + $this->free(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + if ($this->_columnNames === false) { + return $this->_stmt->affected_rows; + } + + return $this->_stmt->num_rows; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->_stmt->field_count; + } + + public function free(): void + { + $this->_stmt->free_result(); + $this->result = false; + } + + /** + * {@inheritdoc} + * + * @deprecated Use one of the fetch- or iterate-related methods. + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + * + * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. + */ + public function getIterator() + { + return new StatementIterator($this); + } +} diff --git a/lib/Doctrine/DBAL/Driver/OCI8/Connection.php b/lib/Doctrine/DBAL/Driver/OCI8/Connection.php new file mode 100644 index 00000000000..123a511a7de --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/OCI8/Connection.php @@ -0,0 +1,252 @@ +dbh = $dbh; + } + + /** + * {@inheritdoc} + * + * @throws UnexpectedValueException If the version string returned by the database server + * does not contain a parsable version number. + */ + public function getServerVersion() + { + $version = oci_server_version($this->dbh); + + if ($version === false) { + throw Exception::fromErrorInfo(oci_error($this->dbh)); + } + + if (! preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches)) { + throw new UnexpectedValueException( + sprintf( + 'Unexpected database version string "%s". Cannot parse an appropriate version number from it. ' . + 'Please report this database version string to the Doctrine team.', + $version + ) + ); + } + + return $matches[1]; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new Statement($this->dbh, $prepareString, $this); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + //$fetchMode = $args[1]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + if (is_int($value) || is_float($value)) { + return $value; + } + + $value = str_replace("'", "''", $value); + + return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return false; + } + + $sql = 'SELECT ' . $name . '.CURRVAL FROM DUAL'; + $stmt = $this->query($sql); + $result = $stmt->fetchColumn(); + + if ($result === false) { + throw new Exception('lastInsertId failed: Query was executed but no result was returned.'); + } + + return (int) $result; + } + + /** + * Returns the current execution mode. + * + * @return int + */ + public function getExecuteMode() + { + return $this->executeMode; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->executeMode = OCI_NO_AUTO_COMMIT; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (! oci_commit($this->dbh)) { + throw Exception::fromErrorInfo($this->errorInfo()); + } + + $this->executeMode = OCI_COMMIT_ON_SUCCESS; + + return true; + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + if (! oci_rollback($this->dbh)) { + throw Exception::fromErrorInfo($this->errorInfo()); + } + + $this->executeMode = OCI_COMMIT_ON_SUCCESS; + + return true; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + $error = oci_error($this->dbh); + + if ($error !== false) { + return $error['code']; + } + + return null; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + $error = oci_error($this->dbh); + + if ($error === false) { + return []; + } + + return $error; + } +} diff --git a/lib/Doctrine/DBAL/Driver/OCI8/Driver.php b/lib/Doctrine/DBAL/Driver/OCI8/Driver.php index b749154845e..bd2eb2f6074 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/Driver.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/Driver.php @@ -18,7 +18,7 @@ class Driver extends AbstractOracleDriver public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { - return new OCI8Connection( + return new Connection( (string) $username, (string) $password, $this->_constructDsn($params), @@ -26,7 +26,7 @@ public function connect(array $params, $username = null, $password = null, array $params['sessionMode'] ?? OCI_NO_AUTO_COMMIT, $params['persistent'] ?? false ); - } catch (OCI8Exception $e) { + } catch (Exception $e) { throw DBALException::driverException($this, $e); } } diff --git a/lib/Doctrine/DBAL/Driver/OCI8/Exception.php b/lib/Doctrine/DBAL/Driver/OCI8/Exception.php new file mode 100644 index 00000000000..36bfb30bdb8 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/OCI8/Exception.php @@ -0,0 +1,25 @@ +dbh = $dbh; - } - - /** - * {@inheritdoc} - * - * @throws UnexpectedValueException If the version string returned by the database server - * does not contain a parsable version number. - */ - public function getServerVersion() - { - $version = oci_server_version($this->dbh); - - if ($version === false) { - throw OCI8Exception::fromErrorInfo(oci_error($this->dbh)); - } - - if (! preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches)) { - throw new UnexpectedValueException( - sprintf( - 'Unexpected database version string "%s". Cannot parse an appropriate version number from it. ' . - 'Please report this database version string to the Doctrine team.', - $version - ) - ); - } - - return $matches[1]; - } - - /** - * {@inheritdoc} - */ - public function requiresQueryForServerVersion() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function prepare($prepareString) - { - return new OCI8Statement($this->dbh, $prepareString, $this); - } - - /** - * {@inheritdoc} - */ - public function query() - { - $args = func_get_args(); - $sql = $args[0]; - //$fetchMode = $args[1]; - $stmt = $this->prepare($sql); - $stmt->execute(); - - return $stmt; - } - - /** - * {@inheritdoc} - */ - public function quote($value, $type = ParameterType::STRING) - { - if (is_int($value) || is_float($value)) { - return $value; - } - - $value = str_replace("'", "''", $value); - - return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; - } - - /** - * {@inheritdoc} - */ - public function exec($statement) - { - $stmt = $this->prepare($statement); - $stmt->execute(); - - return $stmt->rowCount(); - } - - /** - * {@inheritdoc} - * - * @return int|false - */ - public function lastInsertId($name = null) - { - if ($name === null) { - return false; - } - - $sql = 'SELECT ' . $name . '.CURRVAL FROM DUAL'; - $stmt = $this->query($sql); - $result = $stmt->fetchColumn(); - - if ($result === false) { - throw new OCI8Exception('lastInsertId failed: Query was executed but no result was returned.'); - } - - return (int) $result; - } - - /** - * Returns the current execution mode. - * - * @return int - */ - public function getExecuteMode() - { - return $this->executeMode; - } - - /** - * {@inheritdoc} - */ - public function beginTransaction() - { - $this->executeMode = OCI_NO_AUTO_COMMIT; - - return true; - } - - /** - * {@inheritdoc} - */ - public function commit() - { - if (! oci_commit($this->dbh)) { - throw OCI8Exception::fromErrorInfo($this->errorInfo()); - } - - $this->executeMode = OCI_COMMIT_ON_SUCCESS; - - return true; - } - - /** - * {@inheritdoc} - */ - public function rollBack() - { - if (! oci_rollback($this->dbh)) { - throw OCI8Exception::fromErrorInfo($this->errorInfo()); - } - - $this->executeMode = OCI_COMMIT_ON_SUCCESS; - - return true; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorCode() - { - $error = oci_error($this->dbh); - - if ($error !== false) { - return $error['code']; - } - - return null; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorInfo() - { - $error = oci_error($this->dbh); - - if ($error === false) { - return []; - } - - return $error; - } } diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php index 83b078097a4..6b63ad8554b 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php @@ -2,24 +2,11 @@ namespace Doctrine\DBAL\Driver\OCI8; -use Doctrine\DBAL\Driver\AbstractDriverException; - /** + * @deprecated Use {@link Exception} instead + * * @psalm-immutable */ -class OCI8Exception extends AbstractDriverException +class OCI8Exception extends Exception { - /** - * @param mixed[]|false $error - * - * @return OCI8Exception - */ - public static function fromErrorInfo($error) - { - if ($error === false) { - return new self('Database error occurred but no error information was retrieved from the driver.'); - } - - return new self($error['message'], null, $error['code']); - } } diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php index 4f04a46a5cf..30dc69e1488 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php @@ -2,648 +2,9 @@ namespace Doctrine\DBAL\Driver\OCI8; -use Doctrine\DBAL\Driver\FetchUtils; -use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\Statement; -use Doctrine\DBAL\Driver\StatementIterator; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\ParameterType; -use InvalidArgumentException; -use IteratorAggregate; -use PDO; - -use function array_key_exists; -use function assert; -use function count; -use function implode; -use function is_int; -use function is_resource; -use function oci_bind_by_name; -use function oci_cancel; -use function oci_error; -use function oci_execute; -use function oci_fetch_all; -use function oci_fetch_array; -use function oci_fetch_object; -use function oci_new_descriptor; -use function oci_num_fields; -use function oci_num_rows; -use function oci_parse; -use function preg_match; -use function preg_quote; -use function sprintf; -use function substr; - -use const OCI_ASSOC; -use const OCI_B_BIN; -use const OCI_B_BLOB; -use const OCI_BOTH; -use const OCI_D_LOB; -use const OCI_FETCHSTATEMENT_BY_COLUMN; -use const OCI_FETCHSTATEMENT_BY_ROW; -use const OCI_NUM; -use const OCI_RETURN_LOBS; -use const OCI_RETURN_NULLS; -use const OCI_TEMP_BLOB; -use const PREG_OFFSET_CAPTURE; -use const SQLT_CHR; - /** - * The OCI8 implementation of the Statement interface. + * @deprecated Use {@link Statement} instead */ -class OCI8Statement implements IteratorAggregate, Statement, Result +class OCI8Statement extends Statement { - /** @var resource */ - protected $_dbh; - - /** @var resource */ - protected $_sth; - - /** @var OCI8Connection */ - protected $_conn; - - /** - * @deprecated - * - * @var string - */ - protected static $_PARAM = ':param'; - - /** @var int[] */ - protected static $fetchModeMap = [ - FetchMode::MIXED => OCI_BOTH, - FetchMode::ASSOCIATIVE => OCI_ASSOC, - FetchMode::NUMERIC => OCI_NUM, - FetchMode::COLUMN => OCI_NUM, - ]; - - /** @var int */ - protected $_defaultFetchMode = FetchMode::MIXED; - - /** @var string[] */ - protected $_paramMap = []; - - /** - * Holds references to bound parameter values. - * - * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected. - * - * @var mixed[] - */ - private $boundValues = []; - - /** - * Indicates whether the statement is in the state when fetching results is possible - * - * @var bool - */ - private $result = false; - - /** - * Creates a new OCI8Statement that uses the given connection handle and SQL statement. - * - * @param resource $dbh The connection handle. - * @param string $query The SQL query. - */ - public function __construct($dbh, $query, OCI8Connection $conn) - { - [$query, $paramMap] = self::convertPositionalToNamedPlaceholders($query); - - $stmt = oci_parse($dbh, $query); - assert(is_resource($stmt)); - - $this->_sth = $stmt; - $this->_dbh = $dbh; - $this->_paramMap = $paramMap; - $this->_conn = $conn; - } - - /** - * Converts positional (?) into named placeholders (:param). - * - * Oracle does not support positional parameters, hence this method converts all - * positional parameters into artificially named parameters. Note that this conversion - * is not perfect. All question marks (?) in the original statement are treated as - * placeholders and converted to a named parameter. - * - * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral. - * Question marks inside literal strings are therefore handled correctly by this method. - * This comes at a cost, the whole sql statement has to be looped over. - * - * @param string $statement The SQL statement to convert. - * - * @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array). - * - * @throws OCI8Exception - * - * @todo extract into utility class in Doctrine\DBAL\Util namespace - * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. - */ - public static function convertPositionalToNamedPlaceholders($statement) - { - $fragmentOffset = $tokenOffset = 0; - $fragments = $paramMap = []; - $currentLiteralDelimiter = null; - - do { - if (! $currentLiteralDelimiter) { - $result = self::findPlaceholderOrOpeningQuote( - $statement, - $tokenOffset, - $fragmentOffset, - $fragments, - $currentLiteralDelimiter, - $paramMap - ); - } else { - $result = self::findClosingQuote($statement, $tokenOffset, $currentLiteralDelimiter); - } - } while ($result); - - if ($currentLiteralDelimiter) { - throw new OCI8Exception(sprintf( - 'The statement contains non-terminated string literal starting at offset %d', - $tokenOffset - 1 - )); - } - - $fragments[] = substr($statement, $fragmentOffset); - $statement = implode('', $fragments); - - return [$statement, $paramMap]; - } - - /** - * Finds next placeholder or opening quote. - * - * @param string $statement The SQL statement to parse - * @param string $tokenOffset The offset to start searching from - * @param int $fragmentOffset The offset to build the next fragment from - * @param string[] $fragments Fragments of the original statement not containing placeholders - * @param string|null $currentLiteralDelimiter The delimiter of the current string literal - * or NULL if not currently in a literal - * @param array $paramMap Mapping of the original parameter positions to their named replacements - * - * @return bool Whether the token was found - */ - private static function findPlaceholderOrOpeningQuote( - $statement, - &$tokenOffset, - &$fragmentOffset, - &$fragments, - &$currentLiteralDelimiter, - &$paramMap - ) { - $token = self::findToken($statement, $tokenOffset, '/[?\'"]/'); - - if (! $token) { - return false; - } - - if ($token === '?') { - $position = count($paramMap) + 1; - $param = ':param' . $position; - $fragments[] = substr($statement, $fragmentOffset, $tokenOffset - $fragmentOffset); - $fragments[] = $param; - $paramMap[$position] = $param; - $tokenOffset += 1; - $fragmentOffset = $tokenOffset; - - return true; - } - - $currentLiteralDelimiter = $token; - ++$tokenOffset; - - return true; - } - - /** - * Finds closing quote - * - * @param string $statement The SQL statement to parse - * @param string $tokenOffset The offset to start searching from - * @param string $currentLiteralDelimiter The delimiter of the current string literal - * - * @return bool Whether the token was found - */ - private static function findClosingQuote( - $statement, - &$tokenOffset, - &$currentLiteralDelimiter - ) { - $token = self::findToken( - $statement, - $tokenOffset, - '/' . preg_quote($currentLiteralDelimiter, '/') . '/' - ); - - if (! $token) { - return false; - } - - $currentLiteralDelimiter = false; - ++$tokenOffset; - - return true; - } - - /** - * Finds the token described by regex starting from the given offset. Updates the offset with the position - * where the token was found. - * - * @param string $statement The SQL statement to parse - * @param int $offset The offset to start searching from - * @param string $regex The regex containing token pattern - * - * @return string|null Token or NULL if not found - */ - private static function findToken($statement, &$offset, $regex) - { - if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset)) { - $offset = $matches[0][1]; - - return $matches[0][0]; - } - - return null; - } - - /** - * {@inheritdoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING) - { - return $this->bindParam($param, $value, $type, null); - } - - /** - * {@inheritdoc} - */ - public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) - { - if (is_int($param)) { - if (! isset($this->_paramMap[$param])) { - throw new OCI8Exception(sprintf('Could not find variable mapping with index %d, in the SQL statement', $param)); - } - - $param = $this->_paramMap[$param]; - } - - if ($type === ParameterType::LARGE_OBJECT) { - $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB); - - $class = 'OCI-Lob'; - assert($lob instanceof $class); - - $lob->writeTemporary($variable, OCI_TEMP_BLOB); - - $variable =& $lob; - } - - $this->boundValues[$param] =& $variable; - - return oci_bind_by_name( - $this->_sth, - $param, - $variable, - $length ?? -1, - $this->convertParameterType($type) - ); - } - - /** - * Converts DBAL parameter type to oci8 parameter type - */ - private function convertParameterType(int $type): int - { - switch ($type) { - case ParameterType::BINARY: - return OCI_B_BIN; - - case ParameterType::LARGE_OBJECT: - return OCI_B_BLOB; - - default: - return SQLT_CHR; - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use free() instead. - */ - public function closeCursor() - { - $this->free(); - - return true; - } - - /** - * {@inheritdoc} - */ - public function columnCount() - { - return oci_num_fields($this->_sth) ?: 0; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorCode() - { - $error = oci_error($this->_sth); - if ($error !== false) { - $error = $error['code']; - } - - return $error; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorInfo() - { - $error = oci_error($this->_sth); - - if ($error === false) { - return []; - } - - return $error; - } - - /** - * {@inheritdoc} - */ - public function execute($params = null) - { - if ($params) { - $hasZeroIndex = array_key_exists(0, $params); - - foreach ($params as $key => $val) { - if ($hasZeroIndex && is_int($key)) { - $this->bindValue($key + 1, $val); - } else { - $this->bindValue($key, $val); - } - } - } - - $ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode()); - if (! $ret) { - throw OCI8Exception::fromErrorInfo($this->errorInfo()); - } - - $this->result = true; - - return $ret; - } - - /** - * {@inheritdoc} - * - * @deprecated Use one of the fetch- or iterate-related methods. - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) - { - $this->_defaultFetchMode = $fetchMode; - - return true; - } - - /** - * {@inheritdoc} - * - * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. - */ - public function getIterator() - { - return new StatementIterator($this); - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. - */ - public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - // do not try fetching from the statement if it's not expected to contain result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - $fetchMode = $fetchMode ?: $this->_defaultFetchMode; - - if ($fetchMode === FetchMode::COLUMN) { - return $this->fetchColumn(); - } - - if ($fetchMode === FetchMode::STANDARD_OBJECT) { - return oci_fetch_object($this->_sth); - } - - if (! isset(self::$fetchModeMap[$fetchMode])) { - throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); - } - - return oci_fetch_array( - $this->_sth, - self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS - ); - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - $fetchMode = $fetchMode ?: $this->_defaultFetchMode; - - $result = []; - - if ($fetchMode === FetchMode::STANDARD_OBJECT) { - while ($row = $this->fetch($fetchMode)) { - $result[] = $row; - } - - return $result; - } - - if (! isset(self::$fetchModeMap[$fetchMode])) { - throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); - } - - if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) { - while ($row = $this->fetch($fetchMode)) { - $result[] = $row; - } - } else { - $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW; - - if ($fetchMode === FetchMode::COLUMN) { - $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN; - } - - // do not try fetching from the statement if it's not expected to contain result - // in order to prevent exceptional situation - if (! $this->result) { - return []; - } - - oci_fetch_all( - $this->_sth, - $result, - 0, - -1, - self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS - ); - - if ($fetchMode === FetchMode::COLUMN) { - $result = $result[0]; - } - } - - return $result; - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchOne() instead. - */ - public function fetchColumn($columnIndex = 0) - { - // do not try fetching from the statement if it's not expected to contain result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS); - - if ($row === false) { - return false; - } - - return $row[$columnIndex] ?? null; - } - - /** - * {@inheritdoc} - */ - public function rowCount() - { - return oci_num_rows($this->_sth) ?: 0; - } - - /** - * {@inheritdoc} - */ - public function fetchNumeric() - { - return $this->doFetch(OCI_NUM); - } - - /** - * {@inheritdoc} - */ - public function fetchAssociative() - { - return $this->doFetch(OCI_ASSOC); - } - - /** - * {@inheritdoc} - */ - public function fetchOne() - { - return FetchUtils::fetchOne($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllNumeric(): array - { - return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW); - } - - /** - * {@inheritdoc} - */ - public function fetchAllAssociative(): array - { - return $this->doFetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW); - } - - /** - * {@inheritdoc} - */ - public function fetchFirstColumn(): array - { - return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0]; - } - - public function free(): void - { - // not having the result means there's nothing to close - if (! $this->result) { - return; - } - - oci_cancel($this->_sth); - - $this->result = false; - } - - /** - * @return mixed|false - */ - private function doFetch(int $mode) - { - // do not try fetching from the statement if it's not expected to contain the result - // in order to prevent exceptional situation - if (! $this->result) { - return false; - } - - return oci_fetch_array( - $this->_sth, - $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS - ); - } - - /** - * @return array - */ - private function doFetchAll(int $mode, int $fetchStructure): array - { - // do not try fetching from the statement if it's not expected to contain the result - // in order to prevent exceptional situation - if (! $this->result) { - return []; - } - - oci_fetch_all( - $this->_sth, - $result, - 0, - -1, - $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS - ); - - return $result; - } } diff --git a/lib/Doctrine/DBAL/Driver/OCI8/Statement.php b/lib/Doctrine/DBAL/Driver/OCI8/Statement.php new file mode 100644 index 00000000000..95cf12f6cd7 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/OCI8/Statement.php @@ -0,0 +1,649 @@ + OCI_BOTH, + FetchMode::ASSOCIATIVE => OCI_ASSOC, + FetchMode::NUMERIC => OCI_NUM, + FetchMode::COLUMN => OCI_NUM, + ]; + + /** @var int */ + protected $_defaultFetchMode = FetchMode::MIXED; + + /** @var string[] */ + protected $_paramMap = []; + + /** + * Holds references to bound parameter values. + * + * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected. + * + * @var mixed[] + */ + private $boundValues = []; + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * Creates a new OCI8Statement that uses the given connection handle and SQL statement. + * + * @param resource $dbh The connection handle. + * @param string $query The SQL query. + */ + public function __construct($dbh, $query, Connection $conn) + { + [$query, $paramMap] = self::convertPositionalToNamedPlaceholders($query); + + $stmt = oci_parse($dbh, $query); + assert(is_resource($stmt)); + + $this->_sth = $stmt; + $this->_dbh = $dbh; + $this->_paramMap = $paramMap; + $this->_conn = $conn; + } + + /** + * Converts positional (?) into named placeholders (:param). + * + * Oracle does not support positional parameters, hence this method converts all + * positional parameters into artificially named parameters. Note that this conversion + * is not perfect. All question marks (?) in the original statement are treated as + * placeholders and converted to a named parameter. + * + * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral. + * Question marks inside literal strings are therefore handled correctly by this method. + * This comes at a cost, the whole sql statement has to be looped over. + * + * @param string $statement The SQL statement to convert. + * + * @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array). + * + * @throws Exception + * + * @todo extract into utility class in Doctrine\DBAL\Util namespace + * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. + */ + public static function convertPositionalToNamedPlaceholders($statement) + { + $fragmentOffset = $tokenOffset = 0; + $fragments = $paramMap = []; + $currentLiteralDelimiter = null; + + do { + if (! $currentLiteralDelimiter) { + $result = self::findPlaceholderOrOpeningQuote( + $statement, + $tokenOffset, + $fragmentOffset, + $fragments, + $currentLiteralDelimiter, + $paramMap + ); + } else { + $result = self::findClosingQuote($statement, $tokenOffset, $currentLiteralDelimiter); + } + } while ($result); + + if ($currentLiteralDelimiter) { + throw new Exception(sprintf( + 'The statement contains non-terminated string literal starting at offset %d', + $tokenOffset - 1 + )); + } + + $fragments[] = substr($statement, $fragmentOffset); + $statement = implode('', $fragments); + + return [$statement, $paramMap]; + } + + /** + * Finds next placeholder or opening quote. + * + * @param string $statement The SQL statement to parse + * @param string $tokenOffset The offset to start searching from + * @param int $fragmentOffset The offset to build the next fragment from + * @param string[] $fragments Fragments of the original statement not containing placeholders + * @param string|null $currentLiteralDelimiter The delimiter of the current string literal + * or NULL if not currently in a literal + * @param array $paramMap Mapping of the original parameter positions to their named replacements + * + * @return bool Whether the token was found + */ + private static function findPlaceholderOrOpeningQuote( + $statement, + &$tokenOffset, + &$fragmentOffset, + &$fragments, + &$currentLiteralDelimiter, + &$paramMap + ) { + $token = self::findToken($statement, $tokenOffset, '/[?\'"]/'); + + if (! $token) { + return false; + } + + if ($token === '?') { + $position = count($paramMap) + 1; + $param = ':param' . $position; + $fragments[] = substr($statement, $fragmentOffset, $tokenOffset - $fragmentOffset); + $fragments[] = $param; + $paramMap[$position] = $param; + $tokenOffset += 1; + $fragmentOffset = $tokenOffset; + + return true; + } + + $currentLiteralDelimiter = $token; + ++$tokenOffset; + + return true; + } + + /** + * Finds closing quote + * + * @param string $statement The SQL statement to parse + * @param string $tokenOffset The offset to start searching from + * @param string $currentLiteralDelimiter The delimiter of the current string literal + * + * @return bool Whether the token was found + */ + private static function findClosingQuote( + $statement, + &$tokenOffset, + &$currentLiteralDelimiter + ) { + $token = self::findToken( + $statement, + $tokenOffset, + '/' . preg_quote($currentLiteralDelimiter, '/') . '/' + ); + + if (! $token) { + return false; + } + + $currentLiteralDelimiter = false; + ++$tokenOffset; + + return true; + } + + /** + * Finds the token described by regex starting from the given offset. Updates the offset with the position + * where the token was found. + * + * @param string $statement The SQL statement to parse + * @param int $offset The offset to start searching from + * @param string $regex The regex containing token pattern + * + * @return string|null Token or NULL if not found + */ + private static function findToken($statement, &$offset, $regex) + { + if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset)) { + $offset = $matches[0][1]; + + return $matches[0][0]; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + return $this->bindParam($param, $value, $type, null); + } + + /** + * {@inheritdoc} + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) + { + if (is_int($param)) { + if (! isset($this->_paramMap[$param])) { + throw new Exception(sprintf('Could not find variable mapping with index %d, in the SQL statement', $param)); + } + + $param = $this->_paramMap[$param]; + } + + if ($type === ParameterType::LARGE_OBJECT) { + $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB); + + $class = 'OCI-Lob'; + assert($lob instanceof $class); + + $lob->writeTemporary($variable, OCI_TEMP_BLOB); + + $variable =& $lob; + } + + $this->boundValues[$param] =& $variable; + + return oci_bind_by_name( + $this->_sth, + $param, + $variable, + $length ?? -1, + $this->convertParameterType($type) + ); + } + + /** + * Converts DBAL parameter type to oci8 parameter type + */ + private function convertParameterType(int $type): int + { + switch ($type) { + case ParameterType::BINARY: + return OCI_B_BIN; + + case ParameterType::LARGE_OBJECT: + return OCI_B_BLOB; + + default: + return SQLT_CHR; + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use free() instead. + */ + public function closeCursor() + { + $this->free(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return oci_num_fields($this->_sth) ?: 0; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + $error = oci_error($this->_sth); + if ($error !== false) { + $error = $error['code']; + } + + return $error; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + $error = oci_error($this->_sth); + + if ($error === false) { + return []; + } + + return $error; + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params) { + $hasZeroIndex = array_key_exists(0, $params); + + foreach ($params as $key => $val) { + if ($hasZeroIndex && is_int($key)) { + $this->bindValue($key + 1, $val); + } else { + $this->bindValue($key, $val); + } + } + } + + $ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode()); + if (! $ret) { + throw Exception::fromErrorInfo($this->errorInfo()); + } + + $this->result = true; + + return $ret; + } + + /** + * {@inheritdoc} + * + * @deprecated Use one of the fetch- or iterate-related methods. + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + * + * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. + */ + public function getIterator() + { + return new StatementIterator($this); + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. + */ + public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + if ($fetchMode === FetchMode::COLUMN) { + return $this->fetchColumn(); + } + + if ($fetchMode === FetchMode::STANDARD_OBJECT) { + return oci_fetch_object($this->_sth); + } + + if (! isset(self::$fetchModeMap[$fetchMode])) { + throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); + } + + return oci_fetch_array( + $this->_sth, + self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS + ); + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + $result = []; + + if ($fetchMode === FetchMode::STANDARD_OBJECT) { + while ($row = $this->fetch($fetchMode)) { + $result[] = $row; + } + + return $result; + } + + if (! isset(self::$fetchModeMap[$fetchMode])) { + throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); + } + + if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) { + while ($row = $this->fetch($fetchMode)) { + $result[] = $row; + } + } else { + $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW; + + if ($fetchMode === FetchMode::COLUMN) { + $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN; + } + + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (! $this->result) { + return []; + } + + oci_fetch_all( + $this->_sth, + $result, + 0, + -1, + self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS + ); + + if ($fetchMode === FetchMode::COLUMN) { + $result = $result[0]; + } + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchOne() instead. + */ + public function fetchColumn($columnIndex = 0) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS); + + if ($row === false) { + return false; + } + + return $row[$columnIndex] ?? null; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return oci_num_rows($this->_sth) ?: 0; + } + + /** + * {@inheritdoc} + */ + public function fetchNumeric() + { + return $this->doFetch(OCI_NUM); + } + + /** + * {@inheritdoc} + */ + public function fetchAssociative() + { + return $this->doFetch(OCI_ASSOC); + } + + /** + * {@inheritdoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllNumeric(): array + { + return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW); + } + + /** + * {@inheritdoc} + */ + public function fetchAllAssociative(): array + { + return $this->doFetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW); + } + + /** + * {@inheritdoc} + */ + public function fetchFirstColumn(): array + { + return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0]; + } + + public function free(): void + { + // not having the result means there's nothing to close + if (! $this->result) { + return; + } + + oci_cancel($this->_sth); + + $this->result = false; + } + + /** + * @return mixed|false + */ + private function doFetch(int $mode) + { + // do not try fetching from the statement if it's not expected to contain the result + // in order to prevent exceptional situation + if (! $this->result) { + return false; + } + + return oci_fetch_array( + $this->_sth, + $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS + ); + } + + /** + * @return array + */ + private function doFetchAll(int $mode, int $fetchStructure): array + { + // do not try fetching from the statement if it's not expected to contain the result + // in order to prevent exceptional situation + if (! $this->result) { + return []; + } + + oci_fetch_all( + $this->_sth, + $result, + 0, + -1, + $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS + ); + + return $result; + } +} diff --git a/lib/Doctrine/DBAL/Driver/PDO/Connection.php b/lib/Doctrine/DBAL/Driver/PDO/Connection.php new file mode 100644 index 00000000000..47eed0c59ea --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/PDO/Connection.php @@ -0,0 +1,131 @@ +setAttribute(PDO::ATTR_STATEMENT_CLASS, [Statement::class, []]); + $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + try { + $result = parent::exec($statement); + assert($result !== false); + + return $result; + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + return PDO::getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * @param string $prepareString + * @param array $driverOptions + * + * @return PDOStatement + */ + public function prepare($prepareString, $driverOptions = []) + { + try { + $statement = parent::prepare($prepareString, $driverOptions); + assert($statement instanceof PDOStatement); + + return $statement; + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + * + * @return PDOStatement + */ + public function query() + { + $args = func_get_args(); + + try { + $stmt = parent::query(...$args); + assert($stmt instanceof PDOStatement); + + return $stmt; + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = ParameterType::STRING) + { + return parent::quote($input, $type); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + try { + if ($name === null) { + return parent::lastInsertId(); + } + + return parent::lastInsertId($name); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } +} diff --git a/lib/Doctrine/DBAL/Driver/PDO/Exception.php b/lib/Doctrine/DBAL/Driver/PDO/Exception.php new file mode 100644 index 00000000000..169e9bc94a0 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/PDO/Exception.php @@ -0,0 +1,57 @@ +getMessage(), 0, $exception); + + $this->code = $exception->getCode(); + $this->errorInfo = $exception->errorInfo; + $this->errorCode = $exception->errorInfo[1] ?? $exception->getCode(); + $this->sqlState = $exception->errorInfo[0] ?? $exception->getCode(); + } + + /** + * {@inheritdoc} + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * {@inheritdoc} + */ + public function getSQLState() + { + return $this->sqlState; + } +} diff --git a/lib/Doctrine/DBAL/Driver/PDO/Statement.php b/lib/Doctrine/DBAL/Driver/PDO/Statement.php new file mode 100644 index 00000000000..0db8e0ef993 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/PDO/Statement.php @@ -0,0 +1,303 @@ + PDO::PARAM_NULL, + ParameterType::INTEGER => PDO::PARAM_INT, + ParameterType::STRING => PDO::PARAM_STR, + ParameterType::BINARY => PDO::PARAM_LOB, + ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, + ParameterType::BOOLEAN => PDO::PARAM_BOOL, + ]; + + private const FETCH_MODE_MAP = [ + FetchMode::ASSOCIATIVE => PDO::FETCH_ASSOC, + FetchMode::NUMERIC => PDO::FETCH_NUM, + FetchMode::MIXED => PDO::FETCH_BOTH, + FetchMode::STANDARD_OBJECT => PDO::FETCH_OBJ, + FetchMode::COLUMN => PDO::FETCH_COLUMN, + FetchMode::CUSTOM_OBJECT => PDO::FETCH_CLASS, + ]; + + /** + * Protected constructor. + */ + protected function __construct() + { + } + + /** + * {@inheritdoc} + * + * @deprecated Use one of the fetch- or iterate-related methods. + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $fetchMode = $this->convertFetchMode($fetchMode); + + // This thin wrapper is necessary to shield against the weird signature + // of PDOStatement::setFetchMode(): even if the second and third + // parameters are optional, PHP will not let us remove it from this + // declaration. + try { + if ($arg2 === null && $arg3 === null) { + return parent::setFetchMode($fetchMode); + } + + if ($arg3 === null) { + return parent::setFetchMode($fetchMode, $arg2); + } + + return parent::setFetchMode($fetchMode, $arg2, $arg3); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + $type = $this->convertParamType($type); + + try { + return parent::bindValue($param, $value, $type); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * @param mixed $column + * @param mixed $variable + * @param int $type + * @param int|null $length + * @param mixed $driverOptions + * + * @return bool + */ + public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) + { + $type = $this->convertParamType($type); + + try { + return parent::bindParam($column, $variable, $type, ...array_slice(func_get_args(), 3)); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use free() instead. + */ + public function closeCursor() + { + try { + return parent::closeCursor(); + } catch (PDOException $exception) { + // Exceptions not allowed by the interface. + // In case driver implementations do not adhere to the interface, silence exceptions here. + return true; + } + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + try { + return parent::execute($params); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. + */ + public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + { + $args = func_get_args(); + + if (isset($args[0])) { + $args[0] = $this->convertFetchMode($args[0]); + } + + try { + return parent::fetch(...$args); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + $args = func_get_args(); + + if (isset($args[0])) { + $args[0] = $this->convertFetchMode($args[0]); + } + + if ($fetchMode === null && $fetchArgument === null && $ctorArgs === null) { + $args = []; + } elseif ($fetchArgument === null && $ctorArgs === null) { + $args = [$fetchMode]; + } elseif ($ctorArgs === null) { + $args = [$fetchMode, $fetchArgument]; + } else { + $args = [$fetchMode, $fetchArgument, $ctorArgs]; + } + + try { + $data = parent::fetchAll(...$args); + assert(is_array($data)); + + return $data; + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchOne() instead. + */ + public function fetchColumn($columnIndex = 0) + { + try { + return parent::fetchColumn($columnIndex); + } catch (PDOException $exception) { + throw new Exception($exception); + } + } + + /** + * {@inheritdoc} + */ + public function fetchNumeric() + { + return $this->fetch(PDO::FETCH_NUM); + } + + /** + * {@inheritdoc} + */ + public function fetchAssociative() + { + return $this->fetch(PDO::FETCH_ASSOC); + } + + /** + * {@inheritdoc} + */ + public function fetchOne() + { + return $this->fetch(PDO::FETCH_COLUMN); + } + + /** + * {@inheritdoc} + */ + public function fetchAllNumeric(): array + { + return $this->fetchAll(PDO::FETCH_NUM); + } + + /** + * {@inheritdoc} + */ + public function fetchAllAssociative(): array + { + return $this->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * {@inheritdoc} + */ + public function fetchFirstColumn(): array + { + return $this->fetchAll(PDO::FETCH_COLUMN); + } + + public function free(): void + { + parent::closeCursor(); + } + + /** + * Converts DBAL parameter type to PDO parameter type + * + * @param int $type Parameter type + */ + private function convertParamType(int $type): int + { + if (! isset(self::PARAM_TYPE_MAP[$type])) { + // TODO: next major: throw an exception + @trigger_error(sprintf( + 'Using a PDO parameter type (%d given) is deprecated and will cause an error in Doctrine DBAL 3.0', + $type + ), E_USER_DEPRECATED); + + return $type; + } + + return self::PARAM_TYPE_MAP[$type]; + } + + /** + * Converts DBAL fetch mode to PDO fetch mode + * + * @param int $fetchMode Fetch mode + */ + private function convertFetchMode(int $fetchMode): int + { + if (! isset(self::FETCH_MODE_MAP[$fetchMode])) { + // TODO: next major: throw an exception + @trigger_error(sprintf( + 'Using a PDO fetch mode or their combination (%d given)' . + ' is deprecated and will cause an error in Doctrine DBAL 3.0', + $fetchMode + ), E_USER_DEPRECATED); + + return $fetchMode; + } + + return self::FETCH_MODE_MAP[$fetchMode]; + } +} diff --git a/lib/Doctrine/DBAL/Driver/PDOConnection.php b/lib/Doctrine/DBAL/Driver/PDOConnection.php index 7c38b4398e5..b47fb9ce8b4 100644 --- a/lib/Doctrine/DBAL/Driver/PDOConnection.php +++ b/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -2,126 +2,11 @@ namespace Doctrine\DBAL\Driver; -use Doctrine\DBAL\ParameterType; -use PDO; - -use function assert; -use function func_get_args; +use Doctrine\DBAL\Driver\PDO\Connection; /** - * PDO implementation of the Connection interface. - * Used by all PDO-based drivers. + * @deprecated Use {@link Connection} instead */ -class PDOConnection extends PDO implements Connection, ServerInfoAwareConnection +class PDOConnection extends Connection { - /** - * @param string $dsn - * @param string|null $user - * @param string|null $password - * @param mixed[]|null $options - * - * @throws PDOException In case of an error. - */ - public function __construct($dsn, $user = null, $password = null, ?array $options = null) - { - try { - parent::__construct($dsn, (string) $user, (string) $password, (array) $options); - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDOStatement::class, []]); - $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function exec($statement) - { - try { - $result = parent::exec($statement); - assert($result !== false); - - return $result; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function getServerVersion() - { - return PDO::getAttribute(PDO::ATTR_SERVER_VERSION); - } - - /** - * @param string $prepareString - * @param array $driverOptions - * - * @return \PDOStatement - */ - public function prepare($prepareString, $driverOptions = []) - { - try { - $statement = parent::prepare($prepareString, $driverOptions); - assert($statement instanceof \PDOStatement); - - return $statement; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - * - * @return \PDOStatement - */ - public function query() - { - $args = func_get_args(); - - try { - $stmt = parent::query(...$args); - assert($stmt instanceof \PDOStatement); - - return $stmt; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function quote($input, $type = ParameterType::STRING) - { - return parent::quote($input, $type); - } - - /** - * {@inheritdoc} - */ - public function lastInsertId($name = null) - { - try { - if ($name === null) { - return parent::lastInsertId(); - } - - return parent::lastInsertId($name); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function requiresQueryForServerVersion() - { - return false; - } } diff --git a/lib/Doctrine/DBAL/Driver/PDOException.php b/lib/Doctrine/DBAL/Driver/PDOException.php index c2571032b80..f6e70b77a4c 100644 --- a/lib/Doctrine/DBAL/Driver/PDOException.php +++ b/lib/Doctrine/DBAL/Driver/PDOException.php @@ -2,53 +2,13 @@ namespace Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\PDO\Exception; + /** - * Tiny wrapper for PDOException instances to implement the {@link DriverException} interface. + * @deprecated Use {@link Exception} instead * * @psalm-immutable */ -class PDOException extends \PDOException implements DriverException +class PDOException extends Exception { - /** - * The driver specific error code. - * - * @var int|string|null - */ - private $errorCode; - - /** - * The SQLSTATE of the driver. - * - * @var string|null - */ - private $sqlState; - - /** - * @param \PDOException $exception The PDO exception to wrap. - */ - public function __construct(\PDOException $exception) - { - parent::__construct($exception->getMessage(), 0, $exception); - - $this->code = $exception->getCode(); - $this->errorInfo = $exception->errorInfo; - $this->errorCode = $exception->errorInfo[1] ?? $exception->getCode(); - $this->sqlState = $exception->errorInfo[0] ?? $exception->getCode(); - } - - /** - * {@inheritdoc} - */ - public function getErrorCode() - { - return $this->errorCode; - } - - /** - * {@inheritdoc} - */ - public function getSQLState() - { - return $this->sqlState; - } } diff --git a/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php index 20332adbd16..0543d8786e7 100644 --- a/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php @@ -3,7 +3,7 @@ namespace Doctrine\DBAL\Driver\PDOIbm; use Doctrine\DBAL\Driver\AbstractDB2Driver; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; /** * Driver for the PDO IBM extension. @@ -17,7 +17,7 @@ class Driver extends AbstractDB2Driver */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { - return new PDOConnection( + return new Connection( $this->_constructPdoDsn($params), $username, $password, diff --git a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php index 3058febd05a..e6df0bbad21 100644 --- a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php @@ -4,7 +4,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\AbstractMySQLDriver; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use PDOException; /** @@ -18,7 +18,7 @@ class Driver extends AbstractMySQLDriver public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { - $conn = new PDOConnection( + $conn = new Connection( $this->constructPdoDsn($params), $username, $password, diff --git a/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php b/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php index f1239eafbd4..88aa01a5434 100644 --- a/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php @@ -4,7 +4,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\AbstractOracleDriver; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use PDOException; /** @@ -23,7 +23,7 @@ class Driver extends AbstractOracleDriver public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { - return new PDOConnection( + return new Connection( $this->constructPdoDsn($params), $username, $password, diff --git a/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php b/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php index 43f06cb7b7f..e3f15ab610f 100644 --- a/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php @@ -4,7 +4,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use PDO; use PDOException; @@ -21,7 +21,7 @@ class Driver extends AbstractPostgreSQLDriver public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { - $pdo = new PDOConnection( + $pdo = new Connection( $this->_constructPdoDsn($params), $username, $password, diff --git a/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php b/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php index 4566f649578..304043b9cdd 100644 --- a/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php @@ -4,7 +4,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Platforms\SqlitePlatform; use PDOException; @@ -36,7 +36,7 @@ public function connect(array $params, $username = null, $password = null, array } try { - $pdo = new PDOConnection( + $pdo = new Connection( $this->_constructPdoDsn($params), $username, $password, diff --git a/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php b/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php index bc5ae131778..1822a2c90de 100644 --- a/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php +++ b/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php @@ -2,7 +2,7 @@ namespace Doctrine\DBAL\Driver\PDOSqlsrv; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection as BaseConnection; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\ParameterType; use PDO; @@ -13,7 +13,7 @@ /** * Sqlsrv Connection implementation. */ -class Connection extends PDOConnection +class Connection extends BaseConnection { /** * {@inheritdoc} diff --git a/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php b/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php index 880f1263da0..bd0fd6a665c 100644 --- a/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php @@ -3,7 +3,7 @@ namespace Doctrine\DBAL\Driver\PDOSqlsrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; -use Doctrine\DBAL\Driver\AbstractSQLServerDriver\PortWithoutHost; +use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use function is_int; use function sprintf; diff --git a/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php b/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php index 8d6521ff56c..7091eb375a3 100644 --- a/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php +++ b/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php @@ -2,14 +2,14 @@ namespace Doctrine\DBAL\Driver\PDOSqlsrv; -use Doctrine\DBAL\Driver\PDOStatement; +use Doctrine\DBAL\Driver\PDO\Statement as BaseStatement; use Doctrine\DBAL\ParameterType; use PDO; /** * PDO SQL Server Statement */ -class Statement extends PDOStatement +class Statement extends BaseStatement { /** * {@inheritdoc} diff --git a/lib/Doctrine/DBAL/Driver/PDOStatement.php b/lib/Doctrine/DBAL/Driver/PDOStatement.php index 911f5d49665..a9e28dbf318 100644 --- a/lib/Doctrine/DBAL/Driver/PDOStatement.php +++ b/lib/Doctrine/DBAL/Driver/PDOStatement.php @@ -2,298 +2,11 @@ namespace Doctrine\DBAL\Driver; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\ParameterType; -use PDO; - -use function array_slice; -use function assert; -use function func_get_args; -use function is_array; -use function sprintf; -use function trigger_error; - -use const E_USER_DEPRECATED; +use Doctrine\DBAL\Driver\PDO\Statement; /** - * The PDO implementation of the Statement interface. - * Used by all PDO-based drivers. + * @deprecated Use {@link Statement} instead */ -class PDOStatement extends \PDOStatement implements Statement, Result +class PDOStatement extends Statement { - private const PARAM_TYPE_MAP = [ - ParameterType::NULL => PDO::PARAM_NULL, - ParameterType::INTEGER => PDO::PARAM_INT, - ParameterType::STRING => PDO::PARAM_STR, - ParameterType::BINARY => PDO::PARAM_LOB, - ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, - ParameterType::BOOLEAN => PDO::PARAM_BOOL, - ]; - - private const FETCH_MODE_MAP = [ - FetchMode::ASSOCIATIVE => PDO::FETCH_ASSOC, - FetchMode::NUMERIC => PDO::FETCH_NUM, - FetchMode::MIXED => PDO::FETCH_BOTH, - FetchMode::STANDARD_OBJECT => PDO::FETCH_OBJ, - FetchMode::COLUMN => PDO::FETCH_COLUMN, - FetchMode::CUSTOM_OBJECT => PDO::FETCH_CLASS, - ]; - - /** - * Protected constructor. - */ - protected function __construct() - { - } - - /** - * {@inheritdoc} - * - * @deprecated Use one of the fetch- or iterate-related methods. - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) - { - $fetchMode = $this->convertFetchMode($fetchMode); - - // This thin wrapper is necessary to shield against the weird signature - // of PDOStatement::setFetchMode(): even if the second and third - // parameters are optional, PHP will not let us remove it from this - // declaration. - try { - if ($arg2 === null && $arg3 === null) { - return parent::setFetchMode($fetchMode); - } - - if ($arg3 === null) { - return parent::setFetchMode($fetchMode, $arg2); - } - - return parent::setFetchMode($fetchMode, $arg2, $arg3); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING) - { - $type = $this->convertParamType($type); - - try { - return parent::bindValue($param, $value, $type); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * @param mixed $column - * @param mixed $variable - * @param int $type - * @param int|null $length - * @param mixed $driverOptions - * - * @return bool - */ - public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) - { - $type = $this->convertParamType($type); - - try { - return parent::bindParam($column, $variable, $type, ...array_slice(func_get_args(), 3)); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use free() instead. - */ - public function closeCursor() - { - try { - return parent::closeCursor(); - } catch (\PDOException $exception) { - // Exceptions not allowed by the interface. - // In case driver implementations do not adhere to the interface, silence exceptions here. - return true; - } - } - - /** - * {@inheritdoc} - */ - public function execute($params = null) - { - try { - return parent::execute($params); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. - */ - public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - $args = func_get_args(); - - if (isset($args[0])) { - $args[0] = $this->convertFetchMode($args[0]); - } - - try { - return parent::fetch(...$args); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - $args = func_get_args(); - - if (isset($args[0])) { - $args[0] = $this->convertFetchMode($args[0]); - } - - if ($fetchMode === null && $fetchArgument === null && $ctorArgs === null) { - $args = []; - } elseif ($fetchArgument === null && $ctorArgs === null) { - $args = [$fetchMode]; - } elseif ($ctorArgs === null) { - $args = [$fetchMode, $fetchArgument]; - } else { - $args = [$fetchMode, $fetchArgument, $ctorArgs]; - } - - try { - $data = parent::fetchAll(...$args); - assert(is_array($data)); - - return $data; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchOne() instead. - */ - public function fetchColumn($columnIndex = 0) - { - try { - return parent::fetchColumn($columnIndex); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function fetchNumeric() - { - return $this->fetch(PDO::FETCH_NUM); - } - - /** - * {@inheritdoc} - */ - public function fetchAssociative() - { - return $this->fetch(PDO::FETCH_ASSOC); - } - - /** - * {@inheritdoc} - */ - public function fetchOne() - { - return $this->fetch(PDO::FETCH_COLUMN); - } - - /** - * {@inheritdoc} - */ - public function fetchAllNumeric(): array - { - return $this->fetchAll(PDO::FETCH_NUM); - } - - /** - * {@inheritdoc} - */ - public function fetchAllAssociative(): array - { - return $this->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * {@inheritdoc} - */ - public function fetchFirstColumn(): array - { - return $this->fetchAll(PDO::FETCH_COLUMN); - } - - public function free(): void - { - parent::closeCursor(); - } - - /** - * Converts DBAL parameter type to PDO parameter type - * - * @param int $type Parameter type - */ - private function convertParamType(int $type): int - { - if (! isset(self::PARAM_TYPE_MAP[$type])) { - // TODO: next major: throw an exception - @trigger_error(sprintf( - 'Using a PDO parameter type (%d given) is deprecated and will cause an error in Doctrine DBAL 3.0', - $type - ), E_USER_DEPRECATED); - - return $type; - } - - return self::PARAM_TYPE_MAP[$type]; - } - - /** - * Converts DBAL fetch mode to PDO fetch mode - * - * @param int $fetchMode Fetch mode - */ - private function convertFetchMode(int $fetchMode): int - { - if (! isset(self::FETCH_MODE_MAP[$fetchMode])) { - // TODO: next major: throw an exception - @trigger_error(sprintf( - 'Using a PDO fetch mode or their combination (%d given)' . - ' is deprecated and will cause an error in Doctrine DBAL 3.0', - $fetchMode - ), E_USER_DEPRECATED); - - return $fetchMode; - } - - return self::FETCH_MODE_MAP[$fetchMode]; - } } diff --git a/lib/Doctrine/DBAL/Driver/Result.php b/lib/Doctrine/DBAL/Driver/Result.php index 65be0f03c49..a52f0563e4b 100644 --- a/lib/Doctrine/DBAL/Driver/Result.php +++ b/lib/Doctrine/DBAL/Driver/Result.php @@ -14,7 +14,7 @@ interface Result * * @return array|false * - * @throws DriverException + * @throws Exception */ public function fetchNumeric(); @@ -23,7 +23,7 @@ public function fetchNumeric(); * * @return array|false * - * @throws DriverException + * @throws Exception */ public function fetchAssociative(); @@ -32,7 +32,7 @@ public function fetchAssociative(); * * @return mixed|false * - * @throws DriverException + * @throws Exception */ public function fetchOne(); @@ -41,7 +41,7 @@ public function fetchOne(); * * @return array> * - * @throws DriverException + * @throws Exception */ public function fetchAllNumeric(): array; @@ -50,7 +50,7 @@ public function fetchAllNumeric(): array; * * @return array> * - * @throws DriverException + * @throws Exception */ public function fetchAllAssociative(): array; @@ -59,7 +59,7 @@ public function fetchAllAssociative(): array; * * @return array * - * @throws DriverException + * @throws Exception */ public function fetchFirstColumn(): array; diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php index 5446a3b142c..9eb4b452325 100644 --- a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php +++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php @@ -2,7 +2,7 @@ namespace Doctrine\DBAL\Driver\SQLAnywhere; -use Doctrine\DBAL\Driver\AbstractDriverException; +use Doctrine\DBAL\Driver\AbstractException; use InvalidArgumentException; use function sasql_error; @@ -16,7 +16,7 @@ * * @psalm-immutable */ -class SQLAnywhereException extends AbstractDriverException +class SQLAnywhereException extends AbstractException { /** * Helper method to turn SQL Anywhere error into exception. diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php index c9c3a6f2ef7..25e28e9a0e8 100644 --- a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php +++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php @@ -2,7 +2,7 @@ namespace Doctrine\DBAL\Driver\SQLAnywhere; -use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; @@ -350,7 +350,7 @@ public function fetchAssociative() /** * {@inheritdoc} * - * @throws DriverException + * @throws Exception */ public function fetchOne() { @@ -360,7 +360,7 @@ public function fetchOne() /** * @return array> * - * @throws DriverException + * @throws Exception */ public function fetchAllNumeric(): array { @@ -370,7 +370,7 @@ public function fetchAllNumeric(): array /** * @return array> * - * @throws DriverException + * @throws Exception */ public function fetchAllAssociative(): array { @@ -380,7 +380,7 @@ public function fetchAllAssociative(): array /** * @return array * - * @throws DriverException + * @throws Exception */ public function fetchFirstColumn(): array { diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/Connection.php b/lib/Doctrine/DBAL/Driver/SQLSrv/Connection.php new file mode 100644 index 00000000000..7e8ddd7cfb0 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLSrv/Connection.php @@ -0,0 +1,214 @@ +conn = $conn; + $this->lastInsertId = new LastInsertId(); + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + $serverInfo = sqlsrv_server_info($this->conn); + + return $serverInfo['SQLServerVersion']; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function prepare($sql) + { + return new Statement($this->conn, $sql, $this->lastInsertId); + } + + /** + * {@inheritDoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + if (is_int($value)) { + return $value; + } + + if (is_float($value)) { + return sprintf('%F', $value); + } + + return "'" . str_replace("'", "''", $value) . "'"; + } + + /** + * {@inheritDoc} + */ + public function exec($statement) + { + $stmt = sqlsrv_query($this->conn, $statement); + + if ($stmt === false) { + throw Exception::fromSqlSrvErrors(); + } + + $rowsAffected = sqlsrv_rows_affected($stmt); + + if ($rowsAffected === false) { + throw Exception::fromSqlSrvErrors(); + } + + return $rowsAffected; + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); + $stmt->execute([$name]); + } else { + $stmt = $this->query('SELECT @@IDENTITY'); + } + + if ($stmt instanceof Result) { + return $stmt->fetchOne(); + } + + return $stmt->fetchColumn(); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + if (! sqlsrv_begin_transaction($this->conn)) { + throw Exception::fromSqlSrvErrors(); + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function commit() + { + if (! sqlsrv_commit($this->conn)) { + throw Exception::fromSqlSrvErrors(); + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + if (! sqlsrv_rollback($this->conn)) { + throw Exception::fromSqlSrvErrors(); + } + + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + if ($errors) { + return $errors[0]['code']; + } + + return null; + } + + /** + * {@inheritDoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS); + } +} diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php b/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php index 72cbff5cb7d..a57128d9dd8 100644 --- a/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php +++ b/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php @@ -3,7 +3,7 @@ namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; -use Doctrine\DBAL\Driver\AbstractSQLServerDriver\PortWithoutHost; +use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; /** * Driver for ext/sqlsrv. @@ -47,7 +47,7 @@ public function connect(array $params, $username = null, $password = null, array $driverOptions['ReturnDatesAsStrings'] = 1; } - return new SQLSrvConnection($serverName, $driverOptions); + return new Connection($serverName, $driverOptions); } /** diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/Exception.php b/lib/Doctrine/DBAL/Driver/SQLSrv/Exception.php new file mode 100644 index 00000000000..f1b7e09419f --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLSrv/Exception.php @@ -0,0 +1,48 @@ +conn = $conn; - $this->lastInsertId = new LastInsertId(); - } - - /** - * {@inheritdoc} - */ - public function getServerVersion() - { - $serverInfo = sqlsrv_server_info($this->conn); - - return $serverInfo['SQLServerVersion']; - } - - /** - * {@inheritdoc} - */ - public function requiresQueryForServerVersion() - { - return false; - } - - /** - * {@inheritDoc} - */ - public function prepare($sql) - { - return new SQLSrvStatement($this->conn, $sql, $this->lastInsertId); - } - - /** - * {@inheritDoc} - */ - public function query() - { - $args = func_get_args(); - $sql = $args[0]; - $stmt = $this->prepare($sql); - $stmt->execute(); - - return $stmt; - } - - /** - * {@inheritDoc} - */ - public function quote($value, $type = ParameterType::STRING) - { - if (is_int($value)) { - return $value; - } - - if (is_float($value)) { - return sprintf('%F', $value); - } - - return "'" . str_replace("'", "''", $value) . "'"; - } - - /** - * {@inheritDoc} - */ - public function exec($statement) - { - $stmt = sqlsrv_query($this->conn, $statement); - - if ($stmt === false) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - $rowsAffected = sqlsrv_rows_affected($stmt); - - if ($rowsAffected === false) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - return $rowsAffected; - } - - /** - * {@inheritDoc} - */ - public function lastInsertId($name = null) - { - if ($name !== null) { - $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); - $stmt->execute([$name]); - } else { - $stmt = $this->query('SELECT @@IDENTITY'); - } - - if ($stmt instanceof Result) { - return $stmt->fetchOne(); - } - - return $stmt->fetchColumn(); - } - - /** - * {@inheritDoc} - */ - public function beginTransaction() - { - if (! sqlsrv_begin_transaction($this->conn)) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - return true; - } - - /** - * {@inheritDoc} - */ - public function commit() - { - if (! sqlsrv_commit($this->conn)) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - return true; - } - - /** - * {@inheritDoc} - */ - public function rollBack() - { - if (! sqlsrv_rollback($this->conn)) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - return true; - } - - /** - * {@inheritDoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorCode() - { - $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); - if ($errors) { - return $errors[0]['code']; - } - - return null; - } - - /** - * {@inheritDoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorInfo() - { - return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS); - } } diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php b/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php index af70c6852ff..c4d3de20a7a 100644 --- a/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php +++ b/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php @@ -2,47 +2,11 @@ namespace Doctrine\DBAL\Driver\SQLSrv; -use Doctrine\DBAL\Driver\AbstractDriverException; - -use function rtrim; -use function sqlsrv_errors; - -use const SQLSRV_ERR_ERRORS; - /** + * @deprecated Use {@link Exception} instead + * * @psalm-immutable */ -class SQLSrvException extends AbstractDriverException +class SQLSrvException extends Exception { - /** - * Helper method to turn sql server errors into exception. - * - * @return SQLSrvException - */ - public static function fromSqlSrvErrors() - { - $message = ''; - $sqlState = null; - $errorCode = null; - - foreach ((array) sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) { - $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n"; - - if ($sqlState === null) { - $sqlState = $error['SQLSTATE']; - } - - if ($errorCode !== null) { - continue; - } - - $errorCode = $error['code']; - } - - if (! $message) { - $message = 'SQL Server error occurred but no error message was retrieved from driver.'; - } - - return new self(rtrim($message), $sqlState, $errorCode); - } } diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php b/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php index 886faf55bcc..7b0b66b7ec2 100644 --- a/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php +++ b/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php @@ -2,520 +2,9 @@ namespace Doctrine\DBAL\Driver\SQLSrv; -use Doctrine\DBAL\Driver\FetchUtils; -use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\Statement; -use Doctrine\DBAL\Driver\StatementIterator; -use Doctrine\DBAL\FetchMode; -use Doctrine\DBAL\ParameterType; -use IteratorAggregate; -use PDO; - -use function array_key_exists; -use function count; -use function func_get_args; -use function in_array; -use function is_int; -use function is_numeric; -use function sqlsrv_errors; -use function sqlsrv_execute; -use function sqlsrv_fetch; -use function sqlsrv_fetch_array; -use function sqlsrv_fetch_object; -use function sqlsrv_get_field; -use function sqlsrv_next_result; -use function sqlsrv_num_fields; -use function SQLSRV_PHPTYPE_STREAM; -use function SQLSRV_PHPTYPE_STRING; -use function sqlsrv_prepare; -use function sqlsrv_rows_affected; -use function SQLSRV_SQLTYPE_VARBINARY; -use function stripos; - -use const SQLSRV_ENC_BINARY; -use const SQLSRV_ERR_ERRORS; -use const SQLSRV_FETCH_ASSOC; -use const SQLSRV_FETCH_BOTH; -use const SQLSRV_FETCH_NUMERIC; -use const SQLSRV_PARAM_IN; - /** - * SQL Server Statement. + * @deprecated Use {@link Statement} instead */ -class SQLSrvStatement implements IteratorAggregate, Statement, Result +class SQLSrvStatement extends Statement { - /** - * The SQLSRV Resource. - * - * @var resource - */ - private $conn; - - /** - * The SQL statement to execute. - * - * @var string - */ - private $sql; - - /** - * The SQLSRV statement resource. - * - * @var resource|null - */ - private $stmt; - - /** - * References to the variables bound as statement parameters. - * - * @var mixed - */ - private $variables = []; - - /** - * Bound parameter types. - * - * @var int[] - */ - private $types = []; - - /** - * Translations. - * - * @var int[] - */ - private static $fetchMap = [ - FetchMode::MIXED => SQLSRV_FETCH_BOTH, - FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC, - FetchMode::NUMERIC => SQLSRV_FETCH_NUMERIC, - ]; - - /** - * The name of the default class to instantiate when fetching class instances. - * - * @var string - */ - private $defaultFetchClass = '\stdClass'; - - /** - * The constructor arguments for the default class to instantiate when fetching class instances. - * - * @var mixed[] - */ - private $defaultFetchClassCtorArgs = []; - - /** - * The fetch style. - * - * @var int - */ - private $defaultFetchMode = FetchMode::MIXED; - - /** - * The last insert ID. - * - * @var LastInsertId|null - */ - private $lastInsertId; - - /** - * Indicates whether the statement is in the state when fetching results is possible - * - * @var bool - */ - private $result = false; - - /** - * Append to any INSERT query to retrieve the last insert id. - * - * @deprecated This constant has been deprecated and will be made private in 3.0 - */ - public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; - - /** - * @param resource $conn - * @param string $sql - */ - public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null) - { - $this->conn = $conn; - $this->sql = $sql; - - if (stripos($sql, 'INSERT INTO ') !== 0) { - return; - } - - $this->sql .= self::LAST_INSERT_ID_SQL; - $this->lastInsertId = $lastInsertId; - } - - /** - * {@inheritdoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING) - { - if (! is_numeric($param)) { - throw new SQLSrvException( - 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.' - ); - } - - $this->variables[$param] = $value; - $this->types[$param] = $type; - - return true; - } - - /** - * {@inheritdoc} - */ - public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) - { - if (! is_numeric($column)) { - throw new SQLSrvException('sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'); - } - - $this->variables[$column] =& $variable; - $this->types[$column] = $type; - - // unset the statement resource if it exists as the new one will need to be bound to the new variable - $this->stmt = null; - - return true; - } - - /** - * {@inheritdoc} - * - * @deprecated Use free() instead. - */ - public function closeCursor() - { - $this->free(); - - return true; - } - - /** - * {@inheritdoc} - */ - public function columnCount() - { - if ($this->stmt === null) { - return 0; - } - - return sqlsrv_num_fields($this->stmt) ?: 0; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorCode() - { - $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); - if ($errors) { - return $errors[0]['code']; - } - - return false; - } - - /** - * {@inheritdoc} - * - * @deprecated The error information is available via exceptions. - */ - public function errorInfo() - { - return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS); - } - - /** - * {@inheritdoc} - */ - public function execute($params = null) - { - if ($params) { - $hasZeroIndex = array_key_exists(0, $params); - - foreach ($params as $key => $val) { - if ($hasZeroIndex && is_int($key)) { - $this->bindValue($key + 1, $val); - } else { - $this->bindValue($key, $val); - } - } - } - - if (! $this->stmt) { - $this->stmt = $this->prepare(); - } - - if (! sqlsrv_execute($this->stmt)) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - if ($this->lastInsertId) { - sqlsrv_next_result($this->stmt); - sqlsrv_fetch($this->stmt); - $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0)); - } - - $this->result = true; - - return true; - } - - /** - * Prepares SQL Server statement resource - * - * @return resource - * - * @throws SQLSrvException - */ - private function prepare() - { - $params = []; - - foreach ($this->variables as $column => &$variable) { - switch ($this->types[$column]) { - case ParameterType::LARGE_OBJECT: - $params[$column - 1] = [ - &$variable, - SQLSRV_PARAM_IN, - SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), - SQLSRV_SQLTYPE_VARBINARY('max'), - ]; - break; - - case ParameterType::BINARY: - $params[$column - 1] = [ - &$variable, - SQLSRV_PARAM_IN, - SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), - ]; - break; - - default: - $params[$column - 1] =& $variable; - break; - } - } - - $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); - - if (! $stmt) { - throw SQLSrvException::fromSqlSrvErrors(); - } - - return $stmt; - } - - /** - * {@inheritdoc} - * - * @deprecated Use one of the fetch- or iterate-related methods. - */ - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) - { - $this->defaultFetchMode = $fetchMode; - $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; - $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; - - return true; - } - - /** - * {@inheritdoc} - * - * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. - */ - public function getIterator() - { - return new StatementIterator($this); - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. - * - * @throws SQLSrvException - */ - public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - // do not try fetching from the statement if it's not expected to contain result - // in order to prevent exceptional situation - if ($this->stmt === null || ! $this->result) { - return false; - } - - $args = func_get_args(); - $fetchMode = $fetchMode ?: $this->defaultFetchMode; - - if ($fetchMode === FetchMode::COLUMN) { - return $this->fetchColumn(); - } - - if (isset(self::$fetchMap[$fetchMode])) { - return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false; - } - - if (in_array($fetchMode, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) { - $className = $this->defaultFetchClass; - $ctorArgs = $this->defaultFetchClassCtorArgs; - - if (count($args) >= 2) { - $className = $args[1]; - $ctorArgs = $args[2] ?? []; - } - - return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false; - } - - throw new SQLSrvException('Fetch mode is not supported!'); - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - $rows = []; - - switch ($fetchMode) { - case FetchMode::CUSTOM_OBJECT: - while (($row = $this->fetch(...func_get_args())) !== false) { - $rows[] = $row; - } - - break; - - case FetchMode::COLUMN: - while (($row = $this->fetchColumn()) !== false) { - $rows[] = $row; - } - - break; - - default: - while (($row = $this->fetch($fetchMode)) !== false) { - $rows[] = $row; - } - } - - return $rows; - } - - /** - * {@inheritdoc} - * - * @deprecated Use fetchOne() instead. - */ - public function fetchColumn($columnIndex = 0) - { - $row = $this->fetch(FetchMode::NUMERIC); - - if ($row === false) { - return false; - } - - return $row[$columnIndex] ?? null; - } - - /** - * {@inheritdoc} - */ - public function fetchNumeric() - { - return $this->doFetch(SQLSRV_FETCH_NUMERIC); - } - - /** - * {@inheritdoc} - */ - public function fetchAssociative() - { - return $this->doFetch(SQLSRV_FETCH_ASSOC); - } - - /** - * {@inheritdoc} - */ - public function fetchOne() - { - return FetchUtils::fetchOne($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllNumeric(): array - { - return FetchUtils::fetchAllNumeric($this); - } - - /** - * {@inheritdoc} - */ - public function fetchAllAssociative(): array - { - return FetchUtils::fetchAllAssociative($this); - } - - /** - * {@inheritdoc} - */ - public function fetchFirstColumn(): array - { - return FetchUtils::fetchFirstColumn($this); - } - - /** - * {@inheritdoc} - */ - public function rowCount() - { - if ($this->stmt === null) { - return 0; - } - - return sqlsrv_rows_affected($this->stmt) ?: 0; - } - - public function free(): void - { - // not having the result means there's nothing to close - if ($this->stmt === null || ! $this->result) { - return; - } - - // emulate it by fetching and discarding rows, similarly to what PDO does in this case - // @link http://php.net/manual/en/pdostatement.closecursor.php - // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 - // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them - while (sqlsrv_fetch($this->stmt)) { - } - - $this->result = false; - } - - /** - * @return mixed|false - */ - private function doFetch(int $fetchType) - { - // do not try fetching from the statement if it's not expected to contain the result - // in order to prevent exceptional situation - if ($this->stmt === null || ! $this->result) { - return false; - } - - return sqlsrv_fetch_array($this->stmt, $fetchType) ?? false; - } } diff --git a/lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php b/lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php new file mode 100644 index 00000000000..40471945892 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php @@ -0,0 +1,521 @@ + SQLSRV_FETCH_BOTH, + FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC, + FetchMode::NUMERIC => SQLSRV_FETCH_NUMERIC, + ]; + + /** + * The name of the default class to instantiate when fetching class instances. + * + * @var string + */ + private $defaultFetchClass = '\stdClass'; + + /** + * The constructor arguments for the default class to instantiate when fetching class instances. + * + * @var mixed[] + */ + private $defaultFetchClassCtorArgs = []; + + /** + * The fetch style. + * + * @var int + */ + private $defaultFetchMode = FetchMode::MIXED; + + /** + * The last insert ID. + * + * @var LastInsertId|null + */ + private $lastInsertId; + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * Append to any INSERT query to retrieve the last insert id. + * + * @deprecated This constant has been deprecated and will be made private in 3.0 + */ + public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; + + /** + * @param resource $conn + * @param string $sql + */ + public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null) + { + $this->conn = $conn; + $this->sql = $sql; + + if (stripos($sql, 'INSERT INTO ') !== 0) { + return; + } + + $this->sql .= self::LAST_INSERT_ID_SQL; + $this->lastInsertId = $lastInsertId; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + if (! is_numeric($param)) { + throw new Exception( + 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.' + ); + } + + $this->variables[$param] = $value; + $this->types[$param] = $type; + + return true; + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) + { + if (! is_numeric($column)) { + throw new Exception('sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'); + } + + $this->variables[$column] =& $variable; + $this->types[$column] = $type; + + // unset the statement resource if it exists as the new one will need to be bound to the new variable + $this->stmt = null; + + return true; + } + + /** + * {@inheritdoc} + * + * @deprecated Use free() instead. + */ + public function closeCursor() + { + $this->free(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + if ($this->stmt === null) { + return 0; + } + + return sqlsrv_num_fields($this->stmt) ?: 0; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + if ($errors) { + return $errors[0]['code']; + } + + return false; + } + + /** + * {@inheritdoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params) { + $hasZeroIndex = array_key_exists(0, $params); + + foreach ($params as $key => $val) { + if ($hasZeroIndex && is_int($key)) { + $this->bindValue($key + 1, $val); + } else { + $this->bindValue($key, $val); + } + } + } + + if (! $this->stmt) { + $this->stmt = $this->prepare(); + } + + if (! sqlsrv_execute($this->stmt)) { + throw Exception::fromSqlSrvErrors(); + } + + if ($this->lastInsertId) { + sqlsrv_next_result($this->stmt); + sqlsrv_fetch($this->stmt); + $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0)); + } + + $this->result = true; + + return true; + } + + /** + * Prepares SQL Server statement resource + * + * @return resource + * + * @throws Exception + */ + private function prepare() + { + $params = []; + + foreach ($this->variables as $column => &$variable) { + switch ($this->types[$column]) { + case ParameterType::LARGE_OBJECT: + $params[$column - 1] = [ + &$variable, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_VARBINARY('max'), + ]; + break; + + case ParameterType::BINARY: + $params[$column - 1] = [ + &$variable, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), + ]; + break; + + default: + $params[$column - 1] =& $variable; + break; + } + } + + $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); + + if (! $stmt) { + throw Exception::fromSqlSrvErrors(); + } + + return $stmt; + } + + /** + * {@inheritdoc} + * + * @deprecated Use one of the fetch- or iterate-related methods. + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; + $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; + + return true; + } + + /** + * {@inheritdoc} + * + * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. + */ + public function getIterator() + { + return new StatementIterator($this); + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. + * + * @throws Exception + */ + public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if ($this->stmt === null || ! $this->result) { + return false; + } + + $args = func_get_args(); + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + if ($fetchMode === FetchMode::COLUMN) { + return $this->fetchColumn(); + } + + if (isset(self::$fetchMap[$fetchMode])) { + return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false; + } + + if (in_array($fetchMode, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) { + $className = $this->defaultFetchClass; + $ctorArgs = $this->defaultFetchClassCtorArgs; + + if (count($args) >= 2) { + $className = $args[1]; + $ctorArgs = $args[2] ?? []; + } + + return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false; + } + + throw new Exception('Fetch mode is not supported!'); + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + $rows = []; + + switch ($fetchMode) { + case FetchMode::CUSTOM_OBJECT: + while (($row = $this->fetch(...func_get_args())) !== false) { + $rows[] = $row; + } + + break; + + case FetchMode::COLUMN: + while (($row = $this->fetchColumn()) !== false) { + $rows[] = $row; + } + + break; + + default: + while (($row = $this->fetch($fetchMode)) !== false) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchOne() instead. + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(FetchMode::NUMERIC); + + if ($row === false) { + return false; + } + + return $row[$columnIndex] ?? null; + } + + /** + * {@inheritdoc} + */ + public function fetchNumeric() + { + return $this->doFetch(SQLSRV_FETCH_NUMERIC); + } + + /** + * {@inheritdoc} + */ + public function fetchAssociative() + { + return $this->doFetch(SQLSRV_FETCH_ASSOC); + } + + /** + * {@inheritdoc} + */ + public function fetchOne() + { + return FetchUtils::fetchOne($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllNumeric(): array + { + return FetchUtils::fetchAllNumeric($this); + } + + /** + * {@inheritdoc} + */ + public function fetchAllAssociative(): array + { + return FetchUtils::fetchAllAssociative($this); + } + + /** + * {@inheritdoc} + */ + public function fetchFirstColumn(): array + { + return FetchUtils::fetchFirstColumn($this); + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + if ($this->stmt === null) { + return 0; + } + + return sqlsrv_rows_affected($this->stmt) ?: 0; + } + + public function free(): void + { + // not having the result means there's nothing to close + if ($this->stmt === null || ! $this->result) { + return; + } + + // emulate it by fetching and discarding rows, similarly to what PDO does in this case + // @link http://php.net/manual/en/pdostatement.closecursor.php + // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 + // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them + while (sqlsrv_fetch($this->stmt)) { + } + + $this->result = false; + } + + /** + * @return mixed|false + */ + private function doFetch(int $fetchType) + { + // do not try fetching from the statement if it's not expected to contain the result + // in order to prevent exceptional situation + if ($this->stmt === null || ! $this->result) { + return false; + } + + return sqlsrv_fetch_array($this->stmt, $fetchType) ?? false; + } +} diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index b56ac7f9054..691e7d42ca6 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -4,7 +4,7 @@ use Doctrine\Common\EventManager; use Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver as DrizzlePDOMySQLDriver; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Driver as IBMDB2Driver; use Doctrine\DBAL\Driver\Mysqli\Driver as MySQLiDriver; use Doctrine\DBAL\Driver\OCI8\Driver as OCI8Driver; use Doctrine\DBAL\Driver\PDOMySql\Driver as PDOMySQLDriver; @@ -50,7 +50,7 @@ final class DriverManager 'pdo_pgsql' => PDOPgSQLDriver::class, 'pdo_oci' => PDOOCIDriver::class, 'oci8' => OCI8Driver::class, - 'ibm_db2' => DB2Driver::class, + 'ibm_db2' => IBMDB2Driver::class, 'pdo_sqlsrv' => PDOSQLSrvDriver::class, 'mysqli' => MySQLiDriver::class, 'drizzle_pdo_mysql' => DrizzlePDOMySQLDriver::class, diff --git a/lib/Doctrine/DBAL/Exception/DriverException.php b/lib/Doctrine/DBAL/Exception/DriverException.php index 2a3338733db..0df2abf147d 100644 --- a/lib/Doctrine/DBAL/Exception/DriverException.php +++ b/lib/Doctrine/DBAL/Exception/DriverException.php @@ -15,15 +15,15 @@ class DriverException extends DBALException /** * The previous DBAL driver exception. * - * @var \Doctrine\DBAL\Driver\DriverException + * @var \Doctrine\DBAL\Driver\Exception */ private $driverException; /** - * @param string $message The exception message. - * @param \Doctrine\DBAL\Driver\DriverException $driverException The DBAL driver exception to chain. + * @param string $message The exception message. + * @param \Doctrine\DBAL\Driver\Exception $driverException The DBAL driver exception to chain. */ - public function __construct($message, \Doctrine\DBAL\Driver\DriverException $driverException) + public function __construct($message, \Doctrine\DBAL\Driver\Exception $driverException) { $exception = null; diff --git a/lib/Doctrine/DBAL/Portability/Connection.php b/lib/Doctrine/DBAL/Portability/Connection.php index ad79f98e889..9fb001fcf14 100644 --- a/lib/Doctrine/DBAL/Portability/Connection.php +++ b/lib/Doctrine/DBAL/Portability/Connection.php @@ -4,7 +4,8 @@ use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\ColumnCase; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Connection as BaseConnection; +use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use PDO; use function func_get_args; @@ -15,7 +16,7 @@ /** * Portability wrapper for a Connection. */ -class Connection extends \Doctrine\DBAL\Connection +class Connection extends BaseConnection { public const PORTABILITY_ALL = 255; public const PORTABILITY_NONE = 0; diff --git a/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php b/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php index 4f463c0ad3a..301b7ae0184 100644 --- a/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php @@ -3,7 +3,7 @@ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Types\Type; use Throwable; @@ -37,7 +37,7 @@ public function dropDatabase($database) $exception = $exception->getPrevious(); assert($exception instanceof Throwable); - if (! $exception instanceof DriverException) { + if (! $exception instanceof Exception) { throw $exception; } diff --git a/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php b/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php index 3fac92d70a6..a04ff08c430 100644 --- a/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php @@ -3,7 +3,7 @@ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Types\Type; use PDOException; @@ -35,7 +35,7 @@ public function dropDatabase($database) $exception = $exception->getPrevious(); assert($exception instanceof Throwable); - if (! $exception instanceof DriverException) { + if (! $exception instanceof Exception) { throw $exception; } diff --git a/lib/Doctrine/DBAL/Statement.php b/lib/Doctrine/DBAL/Statement.php index 3d6dfc27f26..bc39b077687 100644 --- a/lib/Doctrine/DBAL/Statement.php +++ b/lib/Doctrine/DBAL/Statement.php @@ -3,7 +3,7 @@ namespace Doctrine\DBAL; use Doctrine\DBAL\Abstraction\Result; -use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; @@ -296,7 +296,7 @@ public function fetchNumeric() } return $this->stmt->fetch(FetchMode::NUMERIC); - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -314,7 +314,7 @@ public function fetchAssociative() } return $this->stmt->fetch(FetchMode::ASSOCIATIVE); - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -332,7 +332,7 @@ public function fetchOne() } return $this->stmt->fetch(FetchMode::COLUMN); - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -350,7 +350,7 @@ public function fetchAllNumeric(): array } return $this->stmt->fetchAll(FetchMode::NUMERIC); - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -368,7 +368,7 @@ public function fetchAllAssociative(): array } return $this->stmt->fetchAll(FetchMode::ASSOCIATIVE); - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -386,7 +386,7 @@ public function fetchFirstColumn(): array } return $this->stmt->fetchAll(FetchMode::COLUMN); - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -410,7 +410,7 @@ public function iterateNumeric(): Traversable yield $row; } } - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -434,7 +434,7 @@ public function iterateAssociative(): Traversable yield $row; } } - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } @@ -458,7 +458,7 @@ public function iterateColumn(): Traversable yield $value; } } - } catch (DriverException $e) { + } catch (Exception $e) { throw DBALException::driverException($this->conn->getDriver(), $e); } } diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php index c53c9ccf172..3d53f342968 100644 --- a/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php +++ b/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php @@ -2,8 +2,8 @@ namespace Doctrine\DBAL\Tools\Console\Command; -use Doctrine\DBAL\Driver\PDOConnection; -use Doctrine\DBAL\Driver\PDOStatement; +use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; +use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement; use InvalidArgumentException; use PDOException; use RuntimeException; diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 20aa33d134b..3e8b65cc0e3 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -86,7 +86,7 @@ phpcs wrongly complains about it, and that has been reported here: https://github.com/squizlabs/PHP_CodeSniffer/issues/2950 --> - lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php + lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php lib/Doctrine/DBAL/SQLParserUtils.php lib/Doctrine/DBAL/Tools/Dumper.php @@ -96,12 +96,12 @@ - lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php + lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php - tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php + tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/StatementTest.php @@ -112,12 +112,12 @@ - lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php + lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php - lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php + lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d1e9bcdd481..b7c77873a34 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -20,16 +20,16 @@ parameters: # changing these would be a BC break, to be done in next major - "~^Casting to bool something that's already bool.~" - "~^Casting to int something that's already int.~" - - '~^Method Doctrine\\DBAL\\Driver\\IBMDB2\\DB2Connection::exec\(\) should return int but returns bool\.\z~' + - '~^Method Doctrine\\DBAL\\Driver\\IBMDB2\\Connection::exec\(\) should return int but returns bool\.\z~' - '~^Property Doctrine\\DBAL\\Schema\\Table::\$_primaryKeyName \(string\) does not accept (default value of type )?false\.\z~' - '~^Property Doctrine\\DBAL\\Schema\\Schema::\$_schemaConfig \(Doctrine\\DBAL\\Schema\\SchemaConfig\) does not accept default value of type false\.\z~' - '~^Method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::onEvent\(\) should return string\|null but returns false\.\z~' - '~^Method Doctrine\\DBAL\\Schema\\(Oracle|PostgreSql|SQLServer)SchemaManager::_getPortableTableDefinition\(\) should return array but returns string\.\z~' - - '~^Method Doctrine\\DBAL\\Driver\\OCI8\\OCI8Connection::lastInsertId\(\) should return string but returns (int|false)\.\z~' - - '~^Method Doctrine\\DBAL\\Driver\\SQLSrv\\SQLSrvConnection::errorCode\(\) should return string\|null but returns false\.\z~' + - '~^Method Doctrine\\DBAL\\Driver\\OCI8\\Connection::lastInsertId\(\) should return string but returns (int|false)\.\z~' + - '~^Method Doctrine\\DBAL\\Driver\\SQLSrv\\Connection::errorCode\(\) should return string\|null but returns false\.\z~' # https://bugs.php.net/bug.php?id=78126 - - '~^Call to an undefined method Doctrine\\DBAL\\Driver\\PDOConnection::sqliteCreateFunction\(\)\.\z~' + - '~^Call to an undefined method Doctrine\\DBAL\\Driver\\PDO\\Connection::sqliteCreateFunction\(\)\.\z~' # https://github.com/phpstan/phpstan/issues/1847 - '~^Parameter #2 \$registeredAliases of static method Doctrine\\DBAL\\Query\\QueryException::unknownAlias\(\) expects array, array given\.\z~' @@ -49,7 +49,7 @@ parameters: - '~^.+ on an unknown class Doctrine\\Common\\(Collections\\Collection|Persistence\\Proxy)\.\z~' # inheritance variance inference issue - - '~^Method Doctrine\\DBAL\\Driver\\PDOConnection::\w+\(\) should return Doctrine\\DBAL\\Driver\\Statement but returns PDOStatement\.\z~' + - '~^Method Doctrine\\DBAL\\Driver\\PDO\\Connection::\w+\(\) should return Doctrine\\DBAL\\Driver\\Statement but returns PDOStatement\.\z~' # may not exist when pdo_sqlsrv is not loaded but PDO is - '~^Access to undefined constant PDO::SQLSRV_ENCODING_BINARY\.\z~' @@ -58,7 +58,7 @@ parameters: - '~unknown class OCI-(Lob|Collection)~' # https://github.com/JetBrains/phpstorm-stubs/pull/766 - - '~^Method Doctrine\\DBAL\\Driver\\Mysqli\\MysqliStatement::_fetch\(\) never returns null so it can be removed from the return typehint\.$~' + - '~^Method Doctrine\\DBAL\\Driver\\Mysqli\\Statement::_fetch\(\) never returns null so it can be removed from the return typehint\.$~' # The ReflectionException in the case when the class does not exist is acceptable and does not need to be handled - '~^Parameter #1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given\.$~' diff --git a/psalm.xml b/psalm.xml index a125f087f99..ffbb51d92f1 100644 --- a/psalm.xml +++ b/psalm.xml @@ -29,7 +29,7 @@ This one is just too convoluted for Psalm to figure out, by its author's own admission --> - + @@ -39,7 +39,7 @@ --> - + @@ -48,7 +48,7 @@ This one is clearly deliberate; it's a hack to adapt \PDO to Doctrine\DBAL\Driver\Connection --> - + @@ -65,7 +65,7 @@ Requires a release of https://github.com/JetBrains/phpstorm-stubs/pull/727 --> - + diff --git a/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php b/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php index 386614d78ec..c851355540f 100644 --- a/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php +++ b/tests/Doctrine/Tests/DBAL/DBALExceptionTest.php @@ -4,7 +4,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Driver\DriverException as InnerDriverException; +use Doctrine\DBAL\Driver\Exception as InnerDriverException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\Tests\DbalTestCase; use Exception; diff --git a/tests/Doctrine/Tests/DBAL/Driver/AbstractDriverTest.php b/tests/Doctrine/Tests/DBAL/Driver/AbstractDriverTest.php index 29a8441f646..3c489d61325 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/AbstractDriverTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/AbstractDriverTest.php @@ -5,7 +5,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface; +use Doctrine\DBAL\Driver\Exception as DriverExceptionInterface; use Doctrine\DBAL\Driver\ExceptionConverterDriver; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\ConstraintViolationException; diff --git a/tests/Doctrine/Tests/DBAL/Driver/AbstractSQLServerDriverTest.php b/tests/Doctrine/Tests/DBAL/Driver/AbstractSQLServerDriverTest.php index bd9b8b6b11e..c55fba56fa8 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/AbstractSQLServerDriverTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/AbstractSQLServerDriverTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\DBAL\Driver; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\AbstractSQLServerDriver\PortWithoutHost; +use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SQLServer2005Platform; use Doctrine\DBAL\Platforms\SQLServer2008Platform; diff --git a/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2ConnectionTest.php index 5b0f522e9be..d8ea287418c 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2ConnectionTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\DBAL\Driver\IBMDB2; -use Doctrine\DBAL\Driver\IBMDB2\DB2Connection; +use Doctrine\DBAL\Driver\IBMDB2\Connection; use Doctrine\Tests\DbalTestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -13,7 +13,7 @@ class DB2ConnectionTest extends DbalTestCase /** * The ibm_db2 driver connection mock under test. * - * @var DB2Connection|MockObject + * @var Connection|MockObject */ private $connectionMock; @@ -25,7 +25,7 @@ protected function setUp(): void parent::setUp(); - $this->connectionMock = $this->getMockBuilder(DB2Connection::class) + $this->connectionMock = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); } diff --git a/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2DriverTest.php b/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2DriverTest.php index e48d3c40b66..33e76c5e53d 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2DriverTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/IBMDB2/DB2DriverTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver as DriverInterface; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Driver; use Doctrine\Tests\DBAL\Driver\AbstractDB2DriverTest; class DB2DriverTest extends AbstractDB2DriverTest @@ -15,6 +15,6 @@ public function testReturnsName(): void protected function createDriver(): DriverInterface { - return new DB2Driver(); + return new Driver(); } } diff --git a/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php b/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php index f47daf271c4..b270b4ecf70 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/Mysqli/MysqliConnectionTest.php @@ -2,8 +2,8 @@ namespace Doctrine\Tests\DBAL\Driver\Mysqli; -use Doctrine\DBAL\Driver\Mysqli\MysqliConnection; -use Doctrine\DBAL\Driver\Mysqli\MysqliException; +use Doctrine\DBAL\Driver\Mysqli\Connection; +use Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\Tests\DbalFunctionalTestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -17,7 +17,7 @@ class MysqliConnectionTest extends DbalFunctionalTestCase /** * The mysqli driver connection mock under test. * - * @var MysqliConnection|MockObject + * @var Connection|MockObject */ private $connectionMock; @@ -33,7 +33,7 @@ protected function setUp(): void $this->markTestSkipped('MySQL only test.'); } - $this->connectionMock = $this->getMockBuilder(MysqliConnection::class) + $this->connectionMock = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); } @@ -52,9 +52,9 @@ public function testRestoresErrorHandlerOnException(): void $defaultHandler = set_error_handler($handler); try { - new MysqliConnection(['host' => '255.255.255.255'], 'user', 'pass'); + new Connection(['host' => '255.255.255.255'], 'user', 'pass'); self::fail('An exception was supposed to be raised'); - } catch (MysqliException $e) { + } catch (Exception $e) { self::assertSame('Network is unreachable', $e->getMessage()); } diff --git a/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8ConnectionTest.php index 434aa71b009..bf2a43e72e1 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8ConnectionTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\DBAL\Driver\OCI8; -use Doctrine\DBAL\Driver\OCI8\OCI8Connection; +use Doctrine\DBAL\Driver\OCI8\Connection; use Doctrine\Tests\DbalTestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -13,7 +13,7 @@ class OCI8ConnectionTest extends DbalTestCase /** * The oci8 driver connection mock under test. * - * @var OCI8Connection|MockObject + * @var Connection|MockObject */ private $connectionMock; @@ -25,7 +25,7 @@ protected function setUp(): void parent::setUp(); - $this->connectionMock = $this->getMockBuilder(OCI8Connection::class) + $this->connectionMock = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); } diff --git a/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php b/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php index 77cb231f9a3..e18c9ca3635 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php @@ -2,9 +2,9 @@ namespace Doctrine\Tests\DBAL\Driver\OCI8; -use Doctrine\DBAL\Driver\OCI8\OCI8Connection; -use Doctrine\DBAL\Driver\OCI8\OCI8Exception; -use Doctrine\DBAL\Driver\OCI8\OCI8Statement; +use Doctrine\DBAL\Driver\OCI8\Connection; +use Doctrine\DBAL\Driver\OCI8\Exception; +use Doctrine\DBAL\Driver\OCI8\Statement; use Doctrine\Tests\DbalTestCase; use ReflectionProperty; @@ -36,7 +36,7 @@ protected function setUp(): void */ public function testExecute(array $params): void { - $statement = $this->getMockBuilder(OCI8Statement::class) + $statement = $this->getMockBuilder(Statement::class) ->onlyMethods(['bindValue', 'errorInfo']) ->disableOriginalConstructor() ->getMock(); @@ -67,7 +67,7 @@ public function testExecute(array $params): void // can't pass to constructor since we don't have a real database handle, // but execute must check the connection for the executeMode - $conn = $this->getMockBuilder(OCI8Connection::class) + $conn = $this->getMockBuilder(Connection::class) ->onlyMethods(['getExecuteMode']) ->disableOriginalConstructor() ->getMock(); @@ -78,7 +78,7 @@ public function testExecute(array $params): void $reflProperty->setAccessible(true); $reflProperty->setValue($statement, $conn); - $this->expectException(OCI8Exception::class); + $this->expectException(Exception::class); $statement->execute($params); } @@ -104,9 +104,9 @@ public static function executeDataProvider(): iterable */ public function testConvertNonTerminatedLiteral(string $sql, string $message): void { - $this->expectException(OCI8Exception::class); + $this->expectException(Exception::class); $this->expectExceptionMessageMatches($message); - OCI8Statement::convertPositionalToNamedPlaceholders($sql); + Statement::convertPositionalToNamedPlaceholders($sql); } /** diff --git a/tests/Doctrine/Tests/DBAL/Driver/PDOExceptionTest.php b/tests/Doctrine/Tests/DBAL/Driver/PDO/ExceptionTest.php similarity index 79% rename from tests/Doctrine/Tests/DBAL/Driver/PDOExceptionTest.php rename to tests/Doctrine/Tests/DBAL/Driver/PDO/ExceptionTest.php index 6cd2396e679..53f3e16710c 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/PDOExceptionTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/PDO/ExceptionTest.php @@ -1,14 +1,15 @@ wrappedException = new \PDOException(self::MESSAGE, self::SQLSTATE); + $this->wrappedException = new PDOException(self::MESSAGE, self::SQLSTATE); $this->wrappedException->errorInfo = [self::SQLSTATE, self::ERROR_CODE]; - $this->exception = new PDOException($this->wrappedException); + $this->exception = new Exception($this->wrappedException); } public function testReturnsCode(): void diff --git a/tests/Doctrine/Tests/DBAL/Driver/PDOPgSql/DriverTest.php b/tests/Doctrine/Tests/DBAL/Driver/PDOPgSql/DriverTest.php index 5e3d24583f7..1607207cc47 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/PDOPgSql/DriverTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/PDOPgSql/DriverTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\DBAL\Driver\PDOPgSql; use Doctrine\DBAL\Driver as DriverInterface; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Driver\PDOPgSql\Driver; use Doctrine\Tests\DBAL\Driver\AbstractPostgreSQLDriverTest; use Doctrine\Tests\TestUtil; @@ -89,7 +89,7 @@ private function skipWhenNotUsingPdoPgsql(): void /** * @param array $driverOptions */ - private function connect(array $driverOptions): PDOConnection + private function connect(array $driverOptions): Connection { $params = TestUtil::getConnectionParams(); diff --git a/tests/Doctrine/Tests/DBAL/Driver/SQLSrv/SQLSrvConnectionTest.php b/tests/Doctrine/Tests/DBAL/Driver/SQLSrv/SQLSrvConnectionTest.php index feb87d78d1d..17cc43213ee 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/SQLSrv/SQLSrvConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/SQLSrv/SQLSrvConnectionTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\DBAL\Driver\SQLSrv; -use Doctrine\DBAL\Driver\SQLSrv\SQLSrvConnection; +use Doctrine\DBAL\Driver\SQLSrv\Connection; use Doctrine\Tests\DbalTestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -13,7 +13,7 @@ class SQLSrvConnectionTest extends DbalTestCase /** * The sqlsrv driver connection mock under test. * - * @var SQLSrvConnection|MockObject + * @var Connection|MockObject */ private $connectionMock; @@ -25,7 +25,7 @@ protected function setUp(): void parent::setUp(); - $this->connectionMock = $this->getMockBuilder(SQLSrvConnection::class) + $this->connectionMock = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); } diff --git a/tests/Doctrine/Tests/DBAL/Driver/StatementIteratorTest.php b/tests/Doctrine/Tests/DBAL/Driver/StatementIteratorTest.php index 89d88c8349e..fbe3e294a5e 100644 --- a/tests/Doctrine/Tests/DBAL/Driver/StatementIteratorTest.php +++ b/tests/Doctrine/Tests/DBAL/Driver/StatementIteratorTest.php @@ -2,12 +2,9 @@ namespace Doctrine\Tests\DBAL\Driver; -use Doctrine\DBAL\Driver\IBMDB2\DB2Statement; -use Doctrine\DBAL\Driver\Mysqli\MysqliStatement; -use Doctrine\DBAL\Driver\OCI8\OCI8Statement; +use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\SQLAnywhere\SQLAnywhereStatement; -use Doctrine\DBAL\Driver\SQLSrv\SQLSrvStatement; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Portability\Statement as PortabilityStatement; @@ -93,13 +90,13 @@ private function assertIterationCallsFetchOncePerStep(Traversable $iterator, int public static function statementProvider(): iterable { if (extension_loaded('ibm_db2')) { - yield [DB2Statement::class]; + yield [Driver\IBMDB2\Statement::class]; } - yield [MysqliStatement::class]; + yield [Driver\Mysqli\Statement::class]; if (extension_loaded('oci8')) { - yield [OCI8Statement::class]; + yield [Driver\OCI8\Statement::class]; } yield [PortabilityStatement::class]; @@ -109,6 +106,6 @@ public static function statementProvider(): iterable return; } - yield [SQLSrvStatement::class]; + yield [Driver\SQLSrv\Statement::class]; } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php b/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php index b3330b7c093..785d0ac33d2 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php @@ -5,10 +5,10 @@ use DateTime; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Driver as IBMDB2Driver; use Doctrine\DBAL\Driver\Mysqli\Driver as MySQLiDriver; use Doctrine\DBAL\Driver\OCI8\Driver as Oci8Driver; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver; use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSrvDriver; use Doctrine\DBAL\FetchMode; @@ -258,7 +258,7 @@ public function testFetchAllWithMissingTypes(callable $fetch): void } if ( - $this->connection->getDriver() instanceof DB2Driver + $this->connection->getDriver() instanceof IBMDB2Driver ) { $this->markTestSkipped( 'ibm_ibm2 may or may not report the error depending on the PHP version and the connection state' diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php index 1ffc5a7b9bb..62eea072b3b 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php @@ -2,8 +2,8 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\IBMDB2; -use Doctrine\DBAL\Driver\IBMDB2\DB2Connection; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Connection; +use Doctrine\DBAL\Driver\IBMDB2\Driver; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed; use Doctrine\Tests\DbalFunctionalTestCase; @@ -22,7 +22,7 @@ protected function setUp(): void parent::setUp(); - if ($this->connection->getDriver() instanceof DB2Driver) { + if ($this->connection->getDriver() instanceof Driver) { return; } @@ -37,7 +37,7 @@ protected function tearDown(): void public function testConnectionFailure(): void { $this->expectException(ConnectionFailed::class); - new DB2Connection(['dbname' => 'garbage'], '', ''); + new Connection(['dbname' => 'garbage'], '', ''); } public function testPrepareFailure(): void diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2DriverTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DriverTest.php similarity index 74% rename from tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2DriverTest.php rename to tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DriverTest.php index 214251b76ee..daf31b4d2ed 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2DriverTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DriverTest.php @@ -2,13 +2,13 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\IBMDB2; -use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\IBMDB2\Driver; use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest; use function extension_loaded; -class DB2DriverTest extends AbstractDriverTest +class DriverTest extends AbstractDriverTest { protected function setUp(): void { @@ -18,7 +18,7 @@ protected function setUp(): void parent::setUp(); - if ($this->connection->getDriver() instanceof DB2Driver) { + if ($this->connection->getDriver() instanceof Driver) { return; } @@ -35,8 +35,8 @@ public function testReturnsDatabaseNameWithoutDatabaseNameParameter(): void $this->markTestSkipped('IBM DB2 does not support connecting without database name.'); } - protected function createDriver(): Driver + protected function createDriver(): DriverInterface { - return new DB2Driver(); + return new Driver(); } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/StatementTest.php similarity index 86% rename from tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php rename to tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/StatementTest.php index 2d504933060..eb235185003 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/StatementTest.php @@ -4,7 +4,7 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\IBMDB2; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Driver; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\Tests\DbalFunctionalTestCase; @@ -14,7 +14,7 @@ use const E_NOTICE; use const E_WARNING; -class DB2StatementTest extends DbalFunctionalTestCase +class StatementTest extends DbalFunctionalTestCase { protected function setUp(): void { @@ -24,7 +24,7 @@ protected function setUp(): void parent::setUp(); - if ($this->connection->getDriver() instanceof DB2Driver) { + if ($this->connection->getDriver() instanceof Driver) { return; } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/Mysqli/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/Mysqli/ConnectionTest.php index fc7e5e9d389..72257392e1e 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/Mysqli/ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/Mysqli/ConnectionTest.php @@ -2,9 +2,9 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\Mysqli; +use Doctrine\DBAL\Driver\Mysqli\Connection; use Doctrine\DBAL\Driver\Mysqli\Driver; -use Doctrine\DBAL\Driver\Mysqli\MysqliConnection; -use Doctrine\DBAL\Driver\Mysqli\MysqliException; +use Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\Tests\DbalFunctionalTestCase; use Doctrine\Tests\TestUtil; @@ -39,12 +39,12 @@ public function testDriverOptions(): void $driverOptions = [MYSQLI_OPT_CONNECT_TIMEOUT => 1]; $connection = $this->getConnection($driverOptions); - self::assertInstanceOf(MysqliConnection::class, $connection); + self::assertInstanceOf(Connection::class, $connection); } public function testUnsupportedDriverOption(): void { - $this->expectException(MysqliException::class); + $this->expectException(Exception::class); $this->getConnection(['hello' => 'world']); // use local infile } @@ -58,11 +58,11 @@ public function testPing(): void /** * @param mixed[] $driverOptions */ - private function getConnection(array $driverOptions): MysqliConnection + private function getConnection(array $driverOptions): Connection { $params = TestUtil::getConnectionParams(); - return new MysqliConnection( + return new Connection( $params, $params['user'] ?? '', $params['password'] ?? '', diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/OCI8/OCI8ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/OCI8/ConnectionTest.php similarity index 91% rename from tests/Doctrine/Tests/DBAL/Functional/Driver/OCI8/OCI8ConnectionTest.php rename to tests/Doctrine/Tests/DBAL/Functional/Driver/OCI8/ConnectionTest.php index eb6b767dd88..cd551e41b12 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/OCI8/OCI8ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/OCI8/ConnectionTest.php @@ -2,16 +2,16 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\OCI8; +use Doctrine\DBAL\Driver\OCI8\Connection; use Doctrine\DBAL\Driver\OCI8\Driver; -use Doctrine\DBAL\Driver\OCI8\OCI8Connection; use Doctrine\DBAL\Schema\Table; use Doctrine\Tests\DbalFunctionalTestCase; use function extension_loaded; -class OCI8ConnectionTest extends DbalFunctionalTestCase +class ConnectionTest extends DbalFunctionalTestCase { - /** @var OCI8Connection */ + /** @var Connection */ protected $driverConnection; protected function setUp(): void diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/PDOConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php similarity index 83% rename from tests/Doctrine/Tests/DBAL/Functional/Driver/PDOConnectionTest.php rename to tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php index f0104e5abd6..602e4b541ce 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/PDOConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php @@ -1,9 +1,9 @@ driverConnection = $this->connection->getWrappedConnection(); - if ($this->driverConnection instanceof PDOConnection) { + if ($this->driverConnection instanceof Connection) { return; } @@ -54,9 +54,9 @@ public function testDoesNotRequireQueryForServerVersion(): void public function testThrowsWrappedExceptionOnConstruct(): void { - $this->expectException(PDOException::class); + $this->expectException(Exception::class); - new PDOConnection('foo'); + new Connection('foo'); } /** @@ -64,7 +64,7 @@ public function testThrowsWrappedExceptionOnConstruct(): void */ public function testThrowsWrappedExceptionOnExec(): void { - $this->expectException(PDOException::class); + $this->expectException(Exception::class); $this->driverConnection->exec('foo'); } @@ -94,14 +94,14 @@ public function testThrowsWrappedExceptionOnPrepare(): void // so that PDO actually communicates with the database server to check the query. $this->driverConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - $this->expectException(PDOException::class); + $this->expectException(Exception::class); $this->driverConnection->prepare('foo'); } public function testThrowsWrappedExceptionOnQuery(): void { - $this->expectException(PDOException::class); + $this->expectException(Exception::class); $this->driverConnection->query('foo'); } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/PDOPgsqlConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/PDOPgSql/ConnectionTest.php similarity index 92% rename from tests/Doctrine/Tests/DBAL/Functional/Driver/PDOPgsqlConnectionTest.php rename to tests/Doctrine/Tests/DBAL/Functional/Driver/PDOPgSql/ConnectionTest.php index 3dbeb6edee1..6259c4c5a29 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/PDOPgsqlConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/PDOPgSql/ConnectionTest.php @@ -1,6 +1,6 @@ expectException(SQLSrvException::class); + $this->expectException(Exception::class); $stmt->execute(); } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php b/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php index 74a2463962a..a543681388c 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/PDOStatementTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\DBAL\Functional; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\Schema\Table; use Doctrine\Tests\DbalFunctionalTestCase; use PDO; @@ -19,7 +19,7 @@ protected function setUp(): void parent::setUp(); - if (! $this->connection->getWrappedConnection() instanceof PDOConnection) { + if (! $this->connection->getWrappedConnection() instanceof Connection) { $this->markTestSkipped('PDO-only test'); } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Ticket/DBAL630Test.php b/tests/Doctrine/Tests/DBAL/Functional/Ticket/DBAL630Test.php index 83e50b16165..ca63bc0dc6d 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Ticket/DBAL630Test.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Ticket/DBAL630Test.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\DBAL\Functional\Ticket; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\DBAL\ParameterType; use Doctrine\Tests\DbalFunctionalTestCase; use PDO; @@ -173,10 +173,10 @@ public static function booleanTypeConversionWithoutPdoTypeProvider(): iterable ]; } - private function getWrappedConnection(): PDOConnection + private function getWrappedConnection(): Connection { $connection = $this->connection->getWrappedConnection(); - self::assertInstanceOf(PDOConnection::class, $connection); + self::assertInstanceOf(Connection::class, $connection); return $connection; } diff --git a/tests/Doctrine/Tests/DBAL/Functional/Types/BinaryTest.php b/tests/Doctrine/Tests/DBAL/Functional/Types/BinaryTest.php index ff8a503b5a3..70217b09ede 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Types/BinaryTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Types/BinaryTest.php @@ -4,7 +4,7 @@ namespace Doctrine\Tests\DBAL\Functional\Types; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Driver; use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Schema\Table; @@ -46,7 +46,7 @@ public function testInsertAndSelect(): void $value2 = random_bytes(64); /** @see https://bugs.php.net/bug.php?id=76322 */ - if ($this->connection->getDriver() instanceof DB2Driver) { + if ($this->connection->getDriver() instanceof Driver) { $value1 = str_replace("\x00", "\xFF", $value1); $value2 = str_replace("\x00", "\xFF", $value2); } diff --git a/tests/Doctrine/Tests/DBAL/Functional/WriteTest.php b/tests/Doctrine/Tests/DBAL/Functional/WriteTest.php index 5a39a71bef5..aac223c39ff 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/WriteTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/WriteTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\DBAL\Functional; use DateTime; -use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; @@ -347,13 +347,13 @@ public function testDeleteWhereIsNull(): void * * @return string|false * - * @throws DriverException + * @throws Exception */ private function lastInsertId(?string $name = null) { try { return $this->connection->lastInsertId($name); - } catch (DriverException $e) { + } catch (Exception $e) { if ($e->getCode() === 'IM001') { $this->markTestSkipped($e->getMessage()); } diff --git a/tests/Doctrine/Tests/DBAL/UtilTest.php b/tests/Doctrine/Tests/DBAL/UtilTest.php index 13211764a27..d07b9ec5068 100644 --- a/tests/Doctrine/Tests/DBAL/UtilTest.php +++ b/tests/Doctrine/Tests/DBAL/UtilTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\DBAL; -use Doctrine\DBAL\Driver\OCI8\OCI8Statement; +use Doctrine\DBAL\Driver\OCI8\Statement; use Doctrine\Tests\DbalTestCase; class UtilTest extends DbalTestCase @@ -73,7 +73,7 @@ public static function dataConvertPositionalToNamedParameters(): iterable */ public function testConvertPositionalToNamedParameters(string $inputSQL, string $expectedOutputSQL, array $expectedOutputParamsMap): void { - [$statement, $params] = OCI8Statement::convertPositionalToNamedPlaceholders($inputSQL); + [$statement, $params] = Statement::convertPositionalToNamedPlaceholders($inputSQL); self::assertEquals($expectedOutputSQL, $statement); self::assertEquals($expectedOutputParamsMap, $params);