diff --git a/composer.lock b/composer.lock index 2f2ad59..a0b6714 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f2dedce35c8d1cedeb14af8229afaf8d", + "content-hash": "9d2d01c20515ddf6f3b141bf12b661fc", "packages": [ { "name": "brick/varexporter", @@ -263,12 +263,12 @@ "source": { "type": "git", "url": "https://github.com/php-db/phpdb.git", - "reference": "003ff3440af8d2b51c6f190f1d256f5185b84819" + "reference": "cb328d33d1290df05568793d519d2bac23faa34f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-db/phpdb/zipball/003ff3440af8d2b51c6f190f1d256f5185b84819", - "reference": "003ff3440af8d2b51c6f190f1d256f5185b84819", + "url": "https://api.github.com/repos/php-db/phpdb/zipball/cb328d33d1290df05568793d519d2bac23faa34f", + "reference": "cb328d33d1290df05568793d519d2bac23faa34f", "shasum": "" }, "require": { @@ -324,7 +324,7 @@ "issues": "https://github.com/php-db/phpdb/issues", "source": "https://github.com/php-db/phpdb" }, - "time": "2026-01-25T20:41:58+00:00" + "time": "2026-02-25T10:50:24+00:00" }, { "name": "psr/container", @@ -757,11 +757,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.37", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/28cd424c5ea984128c95cfa7ea658808e8954e49", - "reference": "28cd424c5ea984128c95cfa7ea658808e8954e49", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -806,20 +806,20 @@ "type": "github" } ], - "time": "2026-01-24T08:21:55+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.12", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4" + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4", - "reference": "e4c5a22bf43d3d2bd5a780ad261a622ff62c49a4", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32", "shasum": "" }, "require": { @@ -855,11 +855,14 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", + "keywords": [ + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.12" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16" }, - "time": "2026-01-22T13:40:00+00:00" + "time": "2026-02-14T09:05:21+00:00" }, { "name": "phpunit/php-code-coverage", @@ -953,28 +956,28 @@ }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1002,15 +1005,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", @@ -1198,16 +1213,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.49", + "version": "11.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4f1750675ba411dd6c2d5fa8a3cca07f6742020e" + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f1750675ba411dd6c2d5fa8a3cca07f6742020e", - "reference": "4f1750675ba411dd6c2d5fa8a3cca07f6742020e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", "shasum": "" }, "require": { @@ -1222,7 +1237,7 @@ "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -1234,6 +1249,7 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -1279,7 +1295,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.49" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" }, "funding": [ { @@ -1303,7 +1319,7 @@ "type": "tidelift" } ], - "time": "2026-01-24T16:09:28+00:00" + "time": "2026-02-18T12:37:06+00:00" }, { "name": "sebastian/cli-parser", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a0af83e..4be4a26 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -30,24 +30,6 @@ parameters: count: 1 path: src/Connection.php - - - message: '#^PHPDoc tag @var for variable \$connectionInstance contains unknown class PhpDb\\Mysql\\Driver\\ConnectionInterface\.$#' - identifier: class.notFound - count: 1 - path: src/Container/DriverInterfaceFactory.php - - - - message: '#^PHPDoc tag @var for variable \$resultInstance contains unknown class PhpDb\\Mysql\\Driver\\ResultInterface\.$#' - identifier: class.notFound - count: 1 - path: src/Container/DriverInterfaceFactory.php - - - - message: '#^PHPDoc tag @var for variable \$statementInstance contains unknown class PhpDb\\Mysql\\Driver\\StatementInterface\.$#' - identifier: class.notFound - count: 1 - path: src/Container/DriverInterfaceFactory.php - - message: '#^Instanceof between PhpDb\\Mysql\\Connection and PhpDb\\Adapter\\Driver\\DriverAwareInterface will always evaluate to true\.$#' identifier: instanceof.alwaysTrue @@ -186,42 +168,6 @@ parameters: count: 1 path: src/Statement.php - - - message: '#^Call to an undefined method PhpDb\\Mysql\\Driver\:\:quoteValue\(\)\.$#' - identifier: method.notFound - count: 2 - path: test/integration/AdapterPlatformTest.php - - - - message: '#^Parameter \#1 \$connection of class PhpDb\\Mysql\\Driver constructor expects PhpDb\\Mysql\\Connection, PDO given\.$#' - identifier: argument.type - count: 1 - path: test/integration/AdapterPlatformTest.php - - - - message: '#^Parameter \#1 \$connection of class PhpDb\\Mysql\\Driver constructor expects PhpDb\\Mysql\\Connection, mysqli given\.$#' - identifier: argument.type - count: 1 - path: test/integration/AdapterPlatformTest.php - - - - message: '#^Parameter \#2 \$statementPrototype of class PhpDb\\Mysql\\Driver constructor expects PhpDb\\Mysql\\Statement, PhpDb\\Adapter\\Driver\\Pdo\\Statement given\.$#' - identifier: argument.type - count: 1 - path: test/integration/AdapterPlatformTest.php - - - - message: '#^Property PhpDbIntegrationTest\\Mysql\\Platform\\AdapterPlatformTest\:\:\$adapters \(array\\) does not accept array\\.$#' - identifier: assign.propertyType - count: 1 - path: test/integration/AdapterPlatformTest.php - - - - message: '#^Property PhpDbIntegrationTest\\Mysql\\Platform\\AdapterPlatformTest\:\:\$adapters \(array\\) does not accept array\\.$#' - identifier: assign.propertyType - count: 1 - path: test/integration/AdapterPlatformTest.php - - message: '#^Parameter \#1 \$container of callable PhpDb\\Mysql\\Container\\DriverInterfaceFactory expects Laminas\\ServiceManager\\ServiceManager, Psr\\Container\\ContainerInterface given\.$#' identifier: argument.type @@ -240,21 +186,3 @@ parameters: count: 1 path: test/integration/Pdo/ConnectionTest.php - - - message: '#^Unreachable statement \- code above always terminates\.$#' - identifier: deadCode.unreachable - count: 1 - path: test/integration/Pdo/QueryTest.php - - - - message: '#^Unreachable statement \- code above always terminates\.$#' - identifier: deadCode.unreachable - count: 1 - path: test/integration/Pdo/TableGatewayTest.php - - - - message: '#^Unreachable statement \- code above always terminates\.$#' - identifier: deadCode.unreachable - count: 1 - path: test/integration/TableGatewayTest.php - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b218937..a2f9255 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -34,4 +34,4 @@ - \ No newline at end of file + diff --git a/src/Container/DriverInterfaceFactory.php b/src/Container/DriverInterfaceFactory.php index ac41816..0ffd825 100644 --- a/src/Container/DriverInterfaceFactory.php +++ b/src/Container/DriverInterfaceFactory.php @@ -5,8 +5,10 @@ namespace PhpDb\Mysql\Container; use Laminas\ServiceManager\ServiceManager; +use PhpDb\Adapter\Driver\ConnectionInterface; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\ResultInterface; +use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Exception\ContainerException; use PhpDb\Mysql\Connection; use PhpDb\Mysql\Driver; @@ -29,16 +31,16 @@ public function __invoke( ); } - /** @var Driver\ConnectionInterface&Connection $connectionInstance */ + /** @var ConnectionInterface&Connection $connectionInstance */ $connectionInstance = $container->build(Connection::class, $options); - /** @var Driver\StatementInterface&Statement $statementInstance */ + /** @var StatementInterface&Statement $statementInstance */ $statementInstance = $container->build( Statement::class, $options['options'] ?? [] ); - /** @var Driver\ResultInterface&Result $resultInstance */ + /** @var ResultInterface&Result $resultInstance */ $resultInstance = $container->has(ResultInterface::class) ? $container->get(ResultInterface::class) : new Result(); diff --git a/src/Pdo/Driver.php b/src/Pdo/Driver.php index 924e255..4bb69d8 100644 --- a/src/Pdo/Driver.php +++ b/src/Pdo/Driver.php @@ -24,7 +24,7 @@ public function createResult($resource): ResultInterface /** @var ResultInterface&Result $result */ $result = clone $this->resultPrototype; - $rowCount = 0; + $rowCount = null; $lastGeneratedValue = $this->getLastGeneratedValue(); diff --git a/test/integration/AdapterPlatformTest.php b/test/integration/AdapterPlatformTest.php index 8c28d22..757785f 100644 --- a/test/integration/AdapterPlatformTest.php +++ b/test/integration/AdapterPlatformTest.php @@ -4,114 +4,53 @@ namespace PhpDbIntegrationTest\Mysql\Platform; -use mysqli; -use Override; -use PhpDb\Adapter\Driver\Pdo; use PhpDb\Mysql\AdapterPlatform; -use PhpDb\Mysql\Connection; use PhpDb\Mysql\Driver; -use PhpDb\Mysql\Pdo\Connection as PdoConnection; use PhpDb\Mysql\Pdo\Driver as PdoDriver; -use PhpDb\Mysql\Result; -use PhpDb\Mysql\Statement; +use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; -use function extension_loaded; -use function getenv; - #[Group('integration')] -#[CoversMethod(Driver::class, 'quoteValue')] -#[CoversMethod(PdoDriver::class, 'quoteValue')] +#[CoversMethod(AdapterPlatform::class, 'quoteValue')] final class AdapterPlatformTest extends TestCase { - /** @var array */ - public array $adapters = []; - - protected array $mysqliParams; + use SetupTrait; - protected array $pdoParams; - - #[Override] - protected function setUp(): void + /** @return array */ + public static function quoteValueProvider(): array { - //$this->markTestSkipped(self::class . ' test need refactored'); - - if (extension_loaded('mysqli')) { - $this->mysqliParams = [ - 'hostname' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_HOSTNAME'), - 'username' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_USERNAME'), - 'password' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_PASSWORD'), - 'database' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_DATABASE'), - ]; - - $this->adapters['mysqli'] = new Connection($this->mysqliParams); - } - - if (extension_loaded('pdo')) { - $this->pdoParams = [ - 'hostname' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_HOSTNAME'), - 'username' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_USERNAME'), - 'password' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_PASSWORD'), - 'database' => getenv('TESTS_PHPDB_ADAPTER_MYSQL_DATABASE'), - ]; - - $this->adapters['pdo_mysql'] = new PdoConnection($this->pdoParams); - } + return [ + 'plain string' => ['value', '\'value\''], + 'single quote' => ["it's", "'it\\'s'"], + 'backslash' => ['val\\ue', "'val\\\\ue'"], + 'empty string' => ['', "''"], + 'double quote' => ['say "hello"', "'say \\\"hello\\\"'"], + 'null byte' => ["val\x00ue", "'val\\0ue'"], + ]; } - /** - * @return void - */ - public function testQuoteValueWithMysqli() + #[DataProvider('quoteValueProvider')] + public function testQuoteValueWithMysqli(string $input, string $expected): void { - if (! $this->adapters['mysqli'] instanceof Mysqli) { - $this->markTestSkipped('MySQL (Mysqli) not configured in unit test configuration file'); - } - $mysql = new Driver( - $this->adapters['mysqli'], - new Statement(), - new Result() - ); - $value = $mysql->quoteValue('value'); - self::assertEquals('\'value\'', $value); + $this->driver = Driver::class; + $adapter = $this->getAdapter(); - $mysql = new AdapterPlatform( - new Driver( - new Connection($this->mysqliParams), - new Statement(), - new Result() - ) - ); - $value = $mysql->quoteValue('value'); - self::assertEquals('\'value\'', $value); + $platform = new AdapterPlatform($adapter->getDriver()); + $value = $platform->quoteValue($input); + self::assertSame($expected, $value); } - /** - * @return void - */ - public function testQuoteValueWithPdoMysql() + #[DataProvider('quoteValueProvider')] + public function testQuoteValueWithPdoMysql(string $input, string $expected): void { - if (! $this->adapters['pdo_mysql'] instanceof \PDO) { - $this->markTestSkipped('MySQL (PDO_Mysql) not configured in unit test configuration file'); - } - $mysql = new Driver( - $this->adapters['pdo_mysql'], - new Pdo\Statement(), - new Pdo\Result() - ); - $value = $mysql->quoteValue('value'); - self::assertEquals('\'value\'', $value); + $this->driver = PdoDriver::class; + $adapter = $this->getAdapter(); - $mysql = new AdapterPlatform( - new PdoDriver( - new PdoConnection($this->pdoParams), - new Pdo\Statement(), - new Pdo\Result() - ) - ); - $value = $mysql->quoteValue('value'); - self::assertEquals('\'value\'', $value); + $platform = new AdapterPlatform($adapter->getDriver()); + $value = $platform->quoteValue($input); + self::assertSame($expected, $value); } } diff --git a/test/integration/Pdo/QueryTest.php b/test/integration/Pdo/QueryTest.php index 3894988..4d8b5e0 100644 --- a/test/integration/Pdo/QueryTest.php +++ b/test/integration/Pdo/QueryTest.php @@ -80,12 +80,31 @@ public function testSetSessionTimeZone(): void */ public function testSelectWithNotPermittedBindParamName(): void { - $this->markTestIncomplete('Incorrect bound param name characters are not caught in a raw query.'); - $this->expectException(RuntimeException::class); $this->getAdapter()->query('SET @@session.time_zone = :tz$', [':tz$' => 'SYSTEM']); } + public function testSelectResultCountReturnsActualRowCount(): void + { + $result = $this->getAdapter()->query('SELECT * FROM test WHERE value = ?', ['bar']); + $this->assertInstanceOf(ResultSet::class, $result); + self::assertSame(3, $result->count()); + } + + public function testSelectResultCountWithWhereClause(): void + { + $result = $this->getAdapter()->query('SELECT * FROM test WHERE name = ?', ['foo']); + $this->assertInstanceOf(ResultSet::class, $result); + self::assertSame(1, $result->count()); + } + + public function testSelectResultCountReturnsZeroForNoResults(): void + { + $result = $this->getAdapter()->query('SELECT * FROM test WHERE name = ?', ['nonexistent']); + $this->assertInstanceOf(ResultSet::class, $result); + self::assertSame(0, $result->count()); + } + /** * @see https://github.com/laminas/laminas-db/issues/47 */ diff --git a/test/integration/Pdo/TableGatewayTest.php b/test/integration/Pdo/TableGatewayTest.php index 875bd7a..e564a30 100644 --- a/test/integration/Pdo/TableGatewayTest.php +++ b/test/integration/Pdo/TableGatewayTest.php @@ -39,8 +39,6 @@ public function testConstructor(): void public function testSelect(): void { - $this->markTestIncomplete('Unsure how resultset would ever have count without buffered results.'); - $tableGateway = new TableGateway('test', $this->getAdapter(['db' => ['driver' => Driver::class]])); /** @var ResultSet $rowset */ $rowset = $tableGateway->select(); diff --git a/test/integration/TableGatewayTest.php b/test/integration/TableGatewayTest.php index 2083fda..7573fb6 100644 --- a/test/integration/TableGatewayTest.php +++ b/test/integration/TableGatewayTest.php @@ -25,8 +25,6 @@ final class TableGatewayTest extends TestCase */ public function testSelectWithEmptyCurrentWithBufferResult(): void { - $this->markTestSkipped('Unsure as to how rowset could ever be buffered with empty result'); - /** @var AdapterInterface&Adapter $adapter */ $adapter = $this->getAdapter([ 'db' => [ @@ -40,7 +38,6 @@ public function testSelectWithEmptyCurrentWithBufferResult(): void $tableGateway = new TableGateway('test', $adapter); /** @var AbstractResultSet $rowset */ $rowset = $tableGateway->select('id = 0'); - $this->assertEquals(true, $rowset->isBuffered()); $this->assertNull($rowset->current()); diff --git a/test/unit/ConnectionTest.php b/test/unit/ConnectionTest.php index 8648437..d1f7e2a 100644 --- a/test/unit/ConnectionTest.php +++ b/test/unit/ConnectionTest.php @@ -8,6 +8,9 @@ use Override; use PhpDb\Exception\RuntimeException; use PhpDb\Mysql\Connection; +use PhpDb\Mysql\Driver; +use PhpDb\Mysql\Result; +use PhpDb\Mysql\Statement; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\MockObject\MockObject; @@ -47,8 +50,8 @@ protected function tearDown(): void public function testSetDriver(): void { - $this->markTestIncomplete('This test needs refactored'); - //self::assertEquals($this->connection, $this->connection->setDriver(new Mysqli([]))); + $driver = new Driver($this->connection, new Statement(), new Result()); + self::assertSame($this->connection, $this->connection->setDriver($driver)); } public function testSetConnectionParameters(): void diff --git a/test/unit/Pdo/DriverTest.php b/test/unit/Pdo/DriverTest.php index 0096646..934f4de 100644 --- a/test/unit/Pdo/DriverTest.php +++ b/test/unit/Pdo/DriverTest.php @@ -5,6 +5,7 @@ namespace PhpDbTest\Mysql\Pdo; use Override; +use PDOStatement; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; @@ -17,6 +18,7 @@ #[CoversMethod(Driver::class, 'getDatabasePlatformName')] #[CoversMethod(Driver::class, 'getResultPrototype')] +#[CoversMethod(Driver::class, 'createResult')] final class DriverTest extends TestCase { protected Driver $pdo; @@ -95,4 +97,21 @@ public function testGetResultPrototype(): void self::assertInstanceOf(Result::class, $resultPrototype); } + + public function testCreateResultPassesNullRowCount(): void + { + $pdoStatement = $this->getMockBuilder(PDOStatement::class)->getMock(); + $pdoStatement->expects($this->once()) + ->method('rowCount') + ->willReturn(4); + + $connection = $this->createMock(Connection::class); + $statement = $this->createMock(Statement::class); + $driver = new Driver($connection, $statement, new Result()); + + $result = $driver->createResult($pdoStatement); + + self::assertInstanceOf(Result::class, $result); + self::assertSame(4, $result->count()); + } } diff --git a/test/unit/Pdo/ResultTest.php b/test/unit/Pdo/ResultTest.php index 2e67947..34b2685 100644 --- a/test/unit/Pdo/ResultTest.php +++ b/test/unit/Pdo/ResultTest.php @@ -17,6 +17,7 @@ use function uniqid; #[CoversMethod(Result::class, 'current')] +#[CoversMethod(Result::class, 'count')] #[Group('result-pdo')] final class ResultTest extends TestCase { @@ -25,13 +26,13 @@ final class ResultTest extends TestCase */ public function testCurrent(): void { - $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - $stub->expects($this->any()) + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->any()) ->method('fetch') ->willReturnCallback(fn() => uniqid()); $result = new Result(); - $result->initialize($stub, null); + $result->initialize($mock, null); self::assertEquals($result->current(), $result->current()); } @@ -49,13 +50,13 @@ public function testFetchModeException(): void */ public function testFetchModeAnonymousObject(): void { - $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - $stub->expects($this->any()) + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->any()) ->method('fetch') ->willReturnCallback(fn() => new stdClass()); $result = new Result(); - $result->initialize($stub, null); + $result->initialize($mock, null); $result->setFetchMode(PDO::FETCH_OBJ); self::assertEquals(5, $result->getFetchMode()); @@ -67,17 +68,66 @@ public function testFetchModeAnonymousObject(): void */ public function testFetchModeRange(): void { - $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - $stub->expects($this->any()) + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->any()) ->method('fetch') ->willReturnCallback(fn() => new stdClass()); $result = new Result(); - $result->initialize($stub, null); + $result->initialize($mock, null); $result->setFetchMode(PDO::FETCH_NAMED); self::assertEquals(11, $result->getFetchMode()); self::assertInstanceOf('stdClass', $result->current()); } + public function testCountWithNullRowCountDelegatesToPdoStatement(): void + { + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->once()) + ->method('rowCount') + ->willReturn(4); + + $result = new Result(); + $result->initialize($mock, null, null); + + self::assertSame(4, $result->count()); + } + + public function testCountWithZeroRowCountReturnsZeroWithoutQueryingPdo(): void + { + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->never()) + ->method('rowCount'); + + $result = new Result(); + $result->initialize($mock, null, 0); + + self::assertSame(0, $result->count()); + } + + public function testCountWithIntRowCountReturnsValueWithoutQueryingPdo(): void + { + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->never()) + ->method('rowCount'); + + $result = new Result(); + $result->initialize($mock, null, 7); + + self::assertSame(7, $result->count()); + } + + public function testCountWithClosureRowCountInvokesClosure(): void + { + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + $mock->expects($this->never()) + ->method('rowCount'); + + $result = new Result(); + $result->initialize($mock, null, fn() => 3); + + self::assertSame(3, $result->count()); + } + public function testMultipleRewind(): void { $data = [ @@ -86,15 +136,15 @@ public function testMultipleRewind(): void ]; $position = 0; - $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - assert($stub instanceof PDOStatement); // to suppress IDE type warnings - $stub->expects($this->any()) + $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); + assert($mock instanceof PDOStatement); // to suppress IDE type warnings + $mock->expects($this->any()) ->method('fetch') ->willReturnCallback(function () use ($data, &$position) { return $data[$position++]; }); $result = new Result(); - $result->initialize($stub, null); + $result->initialize($mock, null); $result->rewind(); $result->rewind(); diff --git a/test/unit/Pdo/StatementTest.php b/test/unit/Pdo/StatementTest.php index 1ff8be7..7c6ceb3 100644 --- a/test/unit/Pdo/StatementTest.php +++ b/test/unit/Pdo/StatementTest.php @@ -9,6 +9,7 @@ use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Adapter\Driver\PdoDriverInterface; +use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\ParameterContainer; use PhpDb\Mysql\Pdo\Connection; use PhpDb\Mysql\Pdo\Driver; @@ -40,7 +41,7 @@ protected function setUp(): void $this->pdo = new Driver( $this->createMock(Connection::class), $this->statement, - $this->createMock(Result::class), + new Result(), ); } @@ -93,31 +94,36 @@ public function testGetSql(): void self::assertEquals('SELECT 1', $this->statement->getSql()); } - /** - * @todo Implement testPrepare(). - */ public function testPrepare(): void { - $this->markTestSkipped('Needs to be covered by integration group'); - // $this->statement->initialize(new TestAsset\SqliteMemoryPdo()); - // self::assertNull($this->statement->prepare('SELECT 1')); + $mockPdoStatement = $this->createMock(PDOStatement::class); + $pdo = new TestAsset\CtorlessPdo($mockPdoStatement); + $this->statement->initialize($pdo); + + $result = $this->statement->prepare('SELECT 1'); + self::assertInstanceOf(Statement::class, $result); } public function testIsPrepared(): void { - $this->markTestSkipped('Needs to be covered by integration group'); - // self::assertFalse($this->statement->isPrepared()); - // $this->statement->initialize(new TestAsset\SqliteMemoryPdo()); - // $this->statement->prepare('SELECT 1'); - // self::assertTrue($this->statement->isPrepared()); + self::assertFalse($this->statement->isPrepared()); + + $mockPdoStatement = $this->createMock(PDOStatement::class); + $pdo = new TestAsset\CtorlessPdo($mockPdoStatement); + $this->statement->initialize($pdo); + $this->statement->prepare('SELECT 1'); + + self::assertTrue($this->statement->isPrepared()); } public function testExecute(): void { - $this->markTestSkipped('Needs to be covered by integration group'); - // $this->statement->setDriver(new Pdo(new Connection($pdo = new TestAsset\SqliteMemoryPdo()))); - // $this->statement->initialize($pdo); - // $this->statement->prepare('SELECT 1'); - // self::assertInstanceOf(Result::class, $this->statement->execute()); + $mockPdoStatement = $this->createMock(PDOStatement::class); + $pdo = new TestAsset\CtorlessPdo($mockPdoStatement); + $this->statement->initialize($pdo); + $this->statement->prepare('SELECT 1'); + + $result = $this->statement->execute(); + self::assertInstanceOf(ResultInterface::class, $result); } }