diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 573b60c963f..67ffe820341 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -18,7 +18,6 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\QueryBuilder; -use Doctrine\DBAL\Result as BaseResult; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; @@ -1264,7 +1263,9 @@ public function prepare($sql) * @param array|array $params Query parameters * @param array|array $types Parameter types * - * @return ResultStatement&BaseResult The executed statement. + * @return ForwardCompatibility\DriverStatement|ForwardCompatibility\DriverResultStatement + * + * The executed statement or the cached result statement if a query cache profile is used * * @throws Exception */ @@ -1320,7 +1321,7 @@ public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheP * @param array|array $params Query parameters * @param array|array $types Parameter types * - * @return ResultStatement&BaseResult + * @return ForwardCompatibility\DriverResultStatement * * @throws CacheException */ @@ -1365,15 +1366,11 @@ public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp) } /** - * @return ResultStatement&BaseResult + * @return ForwardCompatibility\Result */ private function ensureForwardCompatibilityStatement(ResultStatement $stmt) { - if ($stmt instanceof BaseResult) { - return $stmt; - } - - return new ForwardCompatibility\Result($stmt); + return ForwardCompatibility\Result::ensure($stmt); } /** diff --git a/lib/Doctrine/DBAL/ForwardCompatibility/DriverResultStatement.php b/lib/Doctrine/DBAL/ForwardCompatibility/DriverResultStatement.php new file mode 100644 index 00000000000..0cf9aa3dda3 --- /dev/null +++ b/lib/Doctrine/DBAL/ForwardCompatibility/DriverResultStatement.php @@ -0,0 +1,9 @@ +stmt = $stmt; } /** - * @return DriverResultStatement + * @return Driver\ResultStatement */ public function getIterator() { @@ -301,4 +310,104 @@ private function ensureHasKeyValue(): void throw NoKeyValue::fromColumnCount($columnCount); } } + + /** + * {@inheritDoc} + * + * @deprecated This feature will no longer be available on Result object in 3.0.x version. + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4019', + 'Result::bindValue() is deprecated, no replacement.' + ); + + if ($this->stmt instanceof Driver\Statement) { + return $this->stmt->bindValue($param, $value, $type); + } + + throw Exception::notSupported('bindValue'); + } + + /** + * {@inheritDoc} + * + * @deprecated This feature will no longer be available on Result object in 3.0.x version. + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4019', + 'Result::bindParam() is deprecated, no replacement.' + ); + + if ($this->stmt instanceof Driver\Statement) { + return $this->stmt->bindParam($param, $variable, $type, $length); + } + + throw Exception::notSupported('bindParam'); + } + + /** + * {@inheritDoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorCode() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4019', + 'Result::errorCode() is deprecated, the error information is available via exceptions.' + ); + + if ($this->stmt instanceof Driver\Statement) { + return $this->stmt->errorCode(); + } + + throw Exception::notSupported('errorCode'); + } + + /** + * {@inheritDoc} + * + * @deprecated The error information is available via exceptions. + */ + public function errorInfo() + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4019', + 'Result::errorInfo() is deprecated, the error information is available via exceptions.' + ); + + if ($this->stmt instanceof Driver\Statement) { + return $this->stmt->errorInfo(); + } + + throw Exception::notSupported('errorInfo'); + } + + /** + * {@inheritDoc} + * + * @deprecated This feature will no longer be available on Result object in 3.0.x version. + */ + public function execute($params = null) + { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4019', + 'Result::execute() is deprecated, no replacement.' + ); + + if ($this->stmt instanceof Driver\Statement) { + return $this->stmt->execute($params); + } + + throw Exception::notSupported('execute'); + } } diff --git a/lib/Doctrine/DBAL/Query/QueryBuilder.php b/lib/Doctrine/DBAL/Query/QueryBuilder.php index 4d27ce25dd2..419937d3586 100644 --- a/lib/Doctrine/DBAL/Query/QueryBuilder.php +++ b/lib/Doctrine/DBAL/Query/QueryBuilder.php @@ -3,8 +3,8 @@ namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\ForwardCompatibility; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; @@ -201,14 +201,16 @@ public function getState() /** * Executes this query using the bound parameters and their types. * - * @return ResultStatement|int + * @return ForwardCompatibility\DriverStatement|int * * @throws Exception */ public function execute() { if ($this->type === self::SELECT) { - return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes); + return ForwardCompatibility\Result::ensure( + $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes) + ); } return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); diff --git a/tests/Doctrine/Tests/DBAL/ForwardCompatibility/ResultTest.php b/tests/Doctrine/Tests/DBAL/ForwardCompatibility/ResultTest.php index a519e33c8c4..0798c7dbd3d 100644 --- a/tests/Doctrine/Tests/DBAL/ForwardCompatibility/ResultTest.php +++ b/tests/Doctrine/Tests/DBAL/ForwardCompatibility/ResultTest.php @@ -3,9 +3,10 @@ namespace Doctrine\Tests\DBAL\ForwardCompatibility; use Doctrine\DBAL\Cache\ArrayStatement; -use Doctrine\DBAL\Driver\ResultStatement as DriverResultStatement; +use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ForwardCompatibility\Result; +use Doctrine\DBAL\ParameterType; use PDO; use PHPUnit\Framework\TestCase; use Traversable; @@ -40,6 +41,24 @@ public function setUp(): void ); } + public function testEnsureWithResult(): void + { + $instance = Result::ensure($this->instance); + $this->assertSame($this->instance, $instance, 'Result is not wrapped twice'); + } + + public function testEnsureWithResultStatement(): void + { + $instance = Result::ensure($this->createMock(Driver\ResultStatement::class)); + $this->assertInstanceOf(Result::class, $instance); + } + + public function testEnsureWithStatement(): void + { + $instance = Result::ensure($this->createMock(Driver\Statement::class)); + $this->assertInstanceOf(Result::class, $instance); + } + public function testIsTraversable(): void { $this->instance->setFetchMode(PDO::FETCH_ASSOC); @@ -335,7 +354,7 @@ public function testIterateColumn(): void ); } - public function testRowCountIsSupportedByWrappedStatement(): void + public function testRowCountIsSupportedByWrappedArrayStatement(): void { $this->assertSame(3, $this->instance->rowCount()); } @@ -343,7 +362,120 @@ public function testRowCountIsSupportedByWrappedStatement(): void public function testRowCountIsNotSupportedByWrappedStatement(): void { $this->expectExceptionObject(Exception::notSupported('rowCount')); - $instance = new Result($this->createMock(DriverResultStatement::class)); + $instance = new Result($this->createMock(Driver\ResultStatement::class)); $instance->rowCount(); } + + public function testBindValueIsSupportedByWrappedStatement(): void + { + $param = ':key'; + $value = 'value'; + + $statement = $this->createMock(Driver\Statement::class); + $statement + ->expects($this->once()) + ->method('bindValue') + ->with($param, $value, ParameterType::STRING) + ->willReturn(true); + + $instance = new Result($statement); + + $this->assertTrue($instance->bindValue($param, $value)); + } + + public function testBindValueIsNotSupportedByWrappedResultStatement(): void + { + $this->expectExceptionObject(Exception::notSupported('bindValue')); + $this->instance->bindValue(':key', 'value'); + } + + public function testBindParamIsSupportedByWrappedStatement(): void + { + $param = ':key'; + $value = 'value'; + + $statement = $this->createMock(Driver\Statement::class); + $statement + ->expects($this->once()) + ->method('bindParam') + ->with($param, $value, ParameterType::STRING) + ->willReturn(true); + + $instance = new Result($statement); + + $this->assertTrue($instance->bindParam($param, $value)); + } + + public function testBindParamIsNotSupportedByWrappedResultStatement(): void + { + $param = ':key'; + $value = 'value'; + + $this->expectExceptionObject(Exception::notSupported('bindParam')); + $this->instance->bindParam($param, $value); + } + + public function testErrorCodeIsSupportedByWrappedStatement(): void + { + $errorCode = 32; + + $statement = $this->createMock(Driver\Statement::class); + $statement + ->expects($this->once()) + ->method('errorCode') + ->willReturn($errorCode); + + $instance = new Result($statement); + + $this->assertSame($errorCode, $instance->errorCode()); + } + + public function testErrorCodeIsNotSupportedByWrappedResultStatement(): void + { + $this->expectExceptionObject(Exception::notSupported('errorCode')); + $this->instance->errorCode(); + } + + public function testErrorInfoIsSupportedByWrappedStatement(): void + { + $errorInfo = ['Some info']; + + $statement = $this->createMock(Driver\Statement::class); + $statement + ->expects($this->once()) + ->method('errorInfo') + ->willReturn($errorInfo); + + $instance = new Result($statement); + + $this->assertSame($errorInfo, $instance->errorInfo()); + } + + public function testErrorInfoIsNotSupportedByWrappedResultStatement(): void + { + $this->expectExceptionObject(Exception::notSupported('errorInfo')); + $this->instance->errorInfo(); + } + + public function testExecuteIsSupportedByWrappedStatement(): void + { + $params = [':key' => 'value']; + + $statement = $this->createMock(Driver\Statement::class); + $statement + ->expects($this->once()) + ->method('execute') + ->with($params) + ->willReturn(true); + + $instance = new Result($statement); + + $this->assertTrue($instance->execute($params)); + } + + public function testExecuteIsNotSupportedByWrappedResultStatement(): void + { + $this->expectExceptionObject(Exception::notSupported('execute')); + $this->instance->execute([':key' => 'value']); + } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php index b43ade0ae27..61128bdf6bd 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php @@ -2,11 +2,14 @@ namespace Doctrine\Tests\DBAL\Functional; +use Doctrine\Common\Cache\ArrayCache; +use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Connection; use Doctrine\DBAL\ConnectionException; +use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as DriverConnection; -use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\ForwardCompatibility; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; @@ -356,7 +359,53 @@ public function testUserProvidedPDOConnection(): void $result = $connection->executeQuery('SELECT 1'); - self::assertInstanceOf(ResultStatement::class, $result); + self::assertInstanceOf(ForwardCompatibility\Result::class, $result); + } + + public function testResultCompatibilityWhenExecutingQueryWithoutParam(): void + { + $result = $this->connection->executeQuery( + $this->connection->getDatabasePlatform()->getDummySelectSQL() + ); + + self::assertInstanceOf(Result::class, $result); + self::assertInstanceOf(Driver\Statement::class, $result); + } + + public function testResultCompatibilityWhenExecutingQueryWithParams(): void + { + $result = $this->connection->executeQuery( + $this->connection->getDatabasePlatform()->getDummySelectSQL(), + ['param1' => 'value'] + ); + + self::assertInstanceOf(Result::class, $result); + self::assertInstanceOf(Driver\Statement::class, $result); + } + + public function testResultCompatibilityWhenExecutingQueryWithQueryCacheParam(): void + { + $result = $this->connection->executeQuery( + $this->connection->getDatabasePlatform()->getDummySelectSQL(), + [], + [], + new QueryCacheProfile(1, 'cacheKey', new ArrayCache()) + ); + + self::assertInstanceOf(Result::class, $result); + self::assertInstanceOf(Driver\ResultStatement::class, $result); + } + + public function testResultCompatibilityWhenExecutingCacheQuery(): void + { + $result = $this->connection->executeCacheQuery( + $this->connection->getDatabasePlatform()->getDummySelectSQL(), + [], + [], + new QueryCacheProfile(1, 'cacheKey', new ArrayCache()) + ); + self::assertInstanceOf(Result::class, $result); + self::assertInstanceOf(Driver\ResultStatement::class, $result); } } diff --git a/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php b/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php index a83362ed588..63969855009 100644 --- a/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php @@ -3,15 +3,18 @@ namespace Doctrine\Tests\DBAL\Query; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Query\QueryException; +use Doctrine\DBAL\Result; use Doctrine\Tests\DbalTestCase; +use PHPUnit\Framework\MockObject\MockObject; class QueryBuilderTest extends DbalTestCase { - /** @var Connection */ + /** @var Connection&MockObject */ protected $conn; protected function setUp(): void @@ -1075,4 +1078,22 @@ public function testOrHavingEmptyStringStartingWithNonEmptyExpression(): void self::assertSame('SELECT id FROM foo HAVING (a = b) OR (c = d)', $qb->getSQL()); } + + public function testExecuteSelect(): void + { + $qb = new QueryBuilder($this->conn); + + $this->conn + ->expects($this->any()) + ->method('executeQuery') + ->willReturn($this->createMock(Driver\Statement::class)); + + $result = $qb + ->select('id') + ->from('foo') + ->execute(); + + self::assertInstanceOf(Driver\Statement::class, $result); + self::assertInstanceOf(Result::class, $result); + } }