diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml index b81e9772..e3f738e6 100644 --- a/analyzer-baseline.toml +++ b/analyzer-baseline.toml @@ -441,7 +441,7 @@ count = 1 [[issues]] file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" code = "mixed-argument" -message = "Invalid argument type for argument #1 of `Exception::__construct`: expected `string`, but found `mixed`." +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Exception\InvalidQueryException::forDriverError`: expected `string`, but found `mixed`.' count = 1 [[issues]] @@ -1125,7 +1125,7 @@ count = 2 [[issues]] file = "src/Adapter/Platform/AbstractPlatform.php" code = "unhandled-thrown-type" -message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\VunerablePlatformQuoteException` in `PhpDb\Adapter\Platform\AbstractPlatform::quoteValue`.' +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Platform\AbstractPlatform::quoteValue`.' count = 1 [[issues]] @@ -1155,7 +1155,7 @@ count = 1 [[issues]] file = "src/Adapter/Platform/Sql92.php" code = "unhandled-thrown-type" -message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\VunerablePlatformQuoteException` in `PhpDb\Adapter\Platform\Sql92::quoteValue`.' +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Platform\Sql92::quoteValue`.' count = 1 [[issues]] diff --git a/lint-baseline.toml b/lint-baseline.toml index 8c9f919c..84b6ad8d 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -8363,6 +8363,7 @@ file = "test/unit/TableGateway/TableGatewayTest.php" code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." count = 2 + [[issues]] file = "src/TableGateway/AbstractTableGateway.php" code = "halstead" @@ -8380,3 +8381,9 @@ file = "src/Sql/Ddl/Column/AbstractPrecisionColumn.php" code = "halstead" message = "Method has a high halstead difficulty" count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 diff --git a/mago.toml b/mago.toml index 16edff1b..1ddf6a0f 100644 --- a/mago.toml +++ b/mago.toml @@ -16,7 +16,7 @@ baseline = "lint-baseline.toml" # condition, per the convention in docs/book/exceptions.md. The method count tracks the # number of distinct failures a component can report, so the maintainability signal does # not apply. "test/" repeats the phpdb-qa-tools default, which this key replaces. -too-many-methods = { exclude = ["test/", "src/Sql/Exception/", "src/TableGateway/Exception/"] } +too-many-methods = { exclude = ["test/", "src/Sql/Exception/", "src/TableGateway/Exception/", "src/Adapter/Exception/"] } [analyzer] baseline = "analyzer-baseline.toml" diff --git a/src/Adapter/Adapter.php b/src/Adapter/Adapter.php index 6d0a1961..7e2a42b8 100644 --- a/src/Adapter/Adapter.php +++ b/src/Adapter/Adapter.php @@ -63,7 +63,7 @@ public function executeQuery(Driver\StatementInterface|string $sql): Driver\Resu : $this->driver->getConnection()->execute($sql); if (! $result instanceof Driver\ResultInterface) { - throw new Exception\RuntimeException('Query execution did not produce a result'); + throw Exception\RuntimeException::forMissingQueryResult(); } return $result; @@ -171,9 +171,7 @@ public function query( $parametersOrQueryMode instanceof ParameterContainer, is_array($parametersOrQueryMode), => $this->prepareQuery($sql, $parametersOrQueryMode), - default => throw new Exception\InvalidArgumentException( - 'Flag incorrectly set', - ), + default => throw Exception\InvalidArgumentException::forIncorrectFlag(), }; $result = $this->executeQuery($sql); @@ -199,7 +197,7 @@ public function __get(string $name): Driver\DriverInterface|Platform\PlatformInt return match (strtolower($name)) { 'driver' => $this->driver, 'platform' => $this->platform, - default => throw new Exception\InvalidArgumentException('Invalid magic property on adapter'), + default => throw Exception\InvalidArgumentException::forInvalidMagicProperty(), }; } } diff --git a/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php b/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php index d7e77a27..665e0559 100644 --- a/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php +++ b/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php @@ -8,8 +8,6 @@ use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Exception\RuntimeException; -use function sprintf; - /** * Trait implementation of DriverFeatureProviderInterface. * @@ -27,11 +25,7 @@ trait DriverFeatureProviderTrait public function addFeature(DriverFeatureInterface $feature): DriverFeatureProviderInterface { if (! $this instanceof DriverInterface) { - throw new RuntimeException(sprintf( - '%s can only be composed into %s', - __TRAIT__, - DriverInterface::class, - )); + throw RuntimeException::forUncomposableTrait(__TRAIT__, DriverInterface::class); } $feature->setDriver($this); diff --git a/src/Adapter/Driver/Pdo/AbstractPdo.php b/src/Adapter/Driver/Pdo/AbstractPdo.php index 925fff9a..a73ade04 100644 --- a/src/Adapter/Driver/Pdo/AbstractPdo.php +++ b/src/Adapter/Driver/Pdo/AbstractPdo.php @@ -23,7 +23,6 @@ use function is_string; use function ltrim; use function preg_match; -use function sprintf; abstract class AbstractPdo implements PdoDriverInterface, ProfilerAwareInterface { @@ -44,9 +43,7 @@ public function checkEnvironment(): bool { if (! extension_loaded('PDO')) { // @codeCoverageIgnoreStart - throw new Exception\RuntimeException( - 'The PDO extension is required for this adapter but the extension is not loaded', - ); + throw Exception\RuntimeException::forMissingPdoExtension(); // @codeCoverageIgnoreEnd } @@ -94,12 +91,7 @@ public function formatParameterName(string|int $name, ?string $type = null): str $name = ltrim($name, ':'); // @see https://bugs.php.net/bug.php?id=43130 if (preg_match('/[^a-zA-Z0-9_]/', $name)) { - throw new Exception\RuntimeException(sprintf( - 'The PDO param %s contains invalid characters.' - . ' Only alphabetic characters, digits, and underscores (_)' - . ' are allowed.', - $name, - )); + throw Exception\RuntimeException::forInvalidPdoParam($name); } return ":{$name}"; } diff --git a/src/Adapter/Driver/Pdo/AbstractPdoConnection.php b/src/Adapter/Driver/Pdo/AbstractPdoConnection.php index 42fde323..1e3f314e 100644 --- a/src/Adapter/Driver/Pdo/AbstractPdoConnection.php +++ b/src/Adapter/Driver/Pdo/AbstractPdoConnection.php @@ -91,7 +91,7 @@ public function execute($sql): ?ResultInterface if (false === $resultResource) { $errorInfo = $this->resource->errorInfo(); - throw new Exception\InvalidQueryException($errorInfo[2]); + throw Exception\InvalidQueryException::forDriverError($errorInfo[2]); } /** @phpstan-ignore arguments.count */ @@ -107,9 +107,7 @@ public function execute($sql): ?ResultInterface final public function getDsn(): string { if (! $this->dsn) { - throw new Exception\RuntimeException( - 'The DSN has not been set or constructed from parameters in connect() for this Connection', - ); + throw Exception\RuntimeException::forMissingDsn(); } return $this->dsn; @@ -141,11 +139,11 @@ public function prepare(?string $sql = null): StatementInterface public function rollback(): ConnectionInterface { if (! $this->isConnected()) { - throw new Exception\RuntimeException('Must be connected before you can rollback'); + throw Exception\RuntimeException::forDisconnectedRollback(); } if (! $this->inTransaction()) { - throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback'); + throw Exception\RuntimeException::forRollbackWithoutTransaction(); } $this->resource->rollBack(); diff --git a/src/Adapter/Driver/Pdo/Result.php b/src/Adapter/Driver/Pdo/Result.php index b87fb0de..41460dd4 100644 --- a/src/Adapter/Driver/Pdo/Result.php +++ b/src/Adapter/Driver/Pdo/Result.php @@ -167,10 +167,7 @@ public function getGeneratedValue(): string|int|false|null public function getQueryResult(?ResultSetInterface $resultPrototype = null): ResultSetInterface { if (! $this->isQueryResult()) { - throw new Exception\RuntimeException( - 'Cannot produce a query result set from a result that is not a query result;' - . ' check isQueryResult() first', - ); + throw Exception\RuntimeException::forNonQueryResult(); } $resultPrototype ??= new ResultSet(); @@ -265,9 +262,7 @@ public function next() public function rewind() { if (self::STATEMENT_MODE_FORWARD === $this->statementMode && $this->position > 0) { - throw new Exception\RuntimeException( - 'This result is a forward only result set, calling rewind() after moving forward is not supported', - ); + throw Exception\RuntimeException::forForwardOnlyRewind(); } if (! $this->currentComplete) { $this->currentData = $this->resource->fetch($this->fetchMode); @@ -282,9 +277,7 @@ public function rewind() public function setFetchMode(int $fetchMode): void { if (! in_array($fetchMode, self::VALID_FETCH_MODES, true)) { - throw new Exception\InvalidArgumentException( - 'The fetch mode must be one of the PDO::FETCH_* constants.', - ); + throw Exception\InvalidArgumentException::forInvalidFetchMode(); } $this->fetchMode = (int) $fetchMode; @@ -293,9 +286,7 @@ public function setFetchMode(int $fetchMode): void public function setStatementMode(string $statementMode = self::STATEMENT_MODE_FORWARD): void { if (! in_array($statementMode, [self::STATEMENT_MODE_SCROLLABLE, self::STATEMENT_MODE_FORWARD], true)) { - throw new Exception\InvalidArgumentException( - 'The statement mode must be one of the defined constants.', - ); + throw Exception\InvalidArgumentException::forInvalidStatementMode(); } $this->statementMode = $statementMode; diff --git a/src/Adapter/Driver/Pdo/Statement.php b/src/Adapter/Driver/Pdo/Statement.php index dea6152d..794acd37 100644 --- a/src/Adapter/Driver/Pdo/Statement.php +++ b/src/Adapter/Driver/Pdo/Statement.php @@ -23,7 +23,6 @@ use function is_int; use function ltrim; use function preg_match; -use function sprintf; class Statement implements StatementInterface, PdoDriverAwareInterface, ProfilerAwareInterface { @@ -83,8 +82,8 @@ public function execute(array|ParameterContainer|null $parameters = null): ?Resu $code = 0; } - throw new Exception\InvalidQueryException( - 'Statement could not be executed (' . implode(' - ', $this->resource->errorInfo()) . ')', + throw Exception\InvalidQueryException::forFailedStatement( + implode(' - ', $this->resource->errorInfo()), $code, $e, ); @@ -138,7 +137,7 @@ public function isPrepared(): bool public function prepare(?string $sql = null): StatementInterface { if ($this->isPrepared) { - throw new Exception\RuntimeException('This statement has been prepared already'); + throw Exception\RuntimeException::forAlreadyPrepared(); } if (null === $sql) { @@ -149,7 +148,7 @@ public function prepare(?string $sql = null): StatementInterface if (false === $this->resource) { $error = $this->pdo->errorInfo(); - throw new Exception\RuntimeException($error[2]); + throw Exception\RuntimeException::forDriverError($error[2]); } $this->isPrepared = true; @@ -224,11 +223,7 @@ protected function bindParametersFromContainer(): void $parameter = $name + 1; } else { if (! preg_match('/^:?[a-zA-Z0-9_]+$/', $name)) { - throw new Exception\RuntimeException(sprintf( - 'The PDO param "%s" contains invalid characters.' - . ' Only alphabetic characters, digits, and underscores (_) are allowed.', - $name, - )); + throw Exception\RuntimeException::forInvalidPdoParam($name); } $parameter = ':' . ltrim($name, ':'); } diff --git a/src/Adapter/Exception/InvalidArgumentException.php b/src/Adapter/Exception/InvalidArgumentException.php index e459554c..d7267f77 100644 --- a/src/Adapter/Exception/InvalidArgumentException.php +++ b/src/Adapter/Exception/InvalidArgumentException.php @@ -1,7 +1,52 @@ positions[$name]; } if (! array_key_exists($name, $this->data)) { - throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); + throw Exception\InvalidArgumentException::forMissingData(); } return $this->errata[$name]; } @@ -212,7 +212,7 @@ public function offsetGetMaxLength(string|int $name): mixed $name = $this->positions[$name]; } if (! array_key_exists($name, $this->data)) { - throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); + throw Exception\InvalidArgumentException::forMissingData(); } return $this->maxLength[$name]; } @@ -276,7 +276,7 @@ public function offsetSet(mixed $name, mixed $value, mixed $errata = null, mixed } elseif (null === $name) { $name = (string) count($this->data); } else { - throw new Exception\InvalidArgumentException('Keys must be string, integer or null'); + throw Exception\InvalidArgumentException::forInvalidKeyType(); } if ($isNewPosition) { @@ -345,7 +345,7 @@ public function offsetUnsetErrata(string|int $name): void $name = $this->positions[$name]; } if (! array_key_exists($name, $this->errata)) { - throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); + throw Exception\InvalidArgumentException::forMissingData(); } $this->errata[$name] = null; } @@ -361,7 +361,7 @@ public function offsetUnsetMaxLength(string|int $name): void $name = $this->positions[$name]; } if (! array_key_exists($name, $this->maxLength)) { - throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); + throw Exception\InvalidArgumentException::forMissingData(); } $this->maxLength[$name] = null; } diff --git a/src/Adapter/Platform/AbstractPlatform.php b/src/Adapter/Platform/AbstractPlatform.php index d2159216..dd09a77b 100644 --- a/src/Adapter/Platform/AbstractPlatform.php +++ b/src/Adapter/Platform/AbstractPlatform.php @@ -7,7 +7,7 @@ use Override; use PDO; use PhpDb\Adapter\Driver; -use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; +use PhpDb\Adapter\Exception; use function addcslashes; use function array_map; @@ -144,10 +144,7 @@ public function quoteTrustedValue(int|float|string|bool $value): ?string public function quoteValue(string $value): string { if (! isset($this->driver)) { - throw VunerablePlatformQuoteException::forPlatformAndMethod( - static::class, - __METHOD__, - ); + throw Exception\RuntimeException::forVulnerablePlatformQuote(static::class, __METHOD__); } return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\''; } diff --git a/src/Adapter/Platform/Sql92.php b/src/Adapter/Platform/Sql92.php index 7bade002..61ab0608 100644 --- a/src/Adapter/Platform/Sql92.php +++ b/src/Adapter/Platform/Sql92.php @@ -5,7 +5,7 @@ namespace PhpDb\Adapter\Platform; use Override; -use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; +use PhpDb\Adapter\Exception; use PhpDb\Sql\Platform\Platform; use PhpDb\Sql\Platform\PlatformDecoratorInterface; @@ -40,10 +40,7 @@ public function getSqlPlatformDecorator(): PlatformDecoratorInterface public function quoteValue(string $value): string { if (! isset($this->driver)) { - throw VunerablePlatformQuoteException::forPlatformAndMethod( - static::class, - __METHOD__, - ); + throw Exception\RuntimeException::forVulnerablePlatformQuote(static::class, __METHOD__); } return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\''; } diff --git a/src/Adapter/Profiler/Profiler.php b/src/Adapter/Profiler/Profiler.php index 573520c1..da67fecb 100644 --- a/src/Adapter/Profiler/Profiler.php +++ b/src/Adapter/Profiler/Profiler.php @@ -55,9 +55,7 @@ public function getProfiles(): array public function profilerFinish(): ProfilerInterface { if (! isset($this->profiles[$this->currentIndex])) { - throw new Exception\RuntimeException( - 'A profile must be started before ' . __FUNCTION__ . ' can be called.', - ); + throw Exception\RuntimeException::forUnstartedProfile(__FUNCTION__); } $current = &$this->profiles[$this->currentIndex]; $current['end'] = microtime(true); diff --git a/test/unit/Adapter/AdapterTest.php b/test/unit/Adapter/AdapterTest.php index 14efdba6..1a1f94fd 100644 --- a/test/unit/Adapter/AdapterTest.php +++ b/test/unit/Adapter/AdapterTest.php @@ -13,7 +13,6 @@ use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Adapter\Exception\InvalidArgumentException; use PhpDb\Adapter\Exception\RuntimeException; -use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; use PhpDb\Adapter\ParameterContainer; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\Profiler; @@ -32,6 +31,7 @@ #[CoversMethod(Adapter::class, 'getDriver')] #[CoversMethod(Adapter::class, 'getPlatform')] #[CoversMethod(Adapter::class, 'getQueryResultSetPrototype')] +#[CoversMethod(Adapter::class, 'executeQuery')] #[CoversMethod(Adapter::class, 'getCurrentSchema')] #[CoversMethod(Adapter::class, 'query')] #[CoversMethod(Adapter::class, 'createStatement')] @@ -91,8 +91,8 @@ public function testExecuteQueryThrowsWhenExecutionDoesNotProduceAResult(): void $sql = 'SELECT foo'; $this->mockConnection->method('execute')->willReturn(null); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Query execution did not produce a result'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_QUERY_RESULT); $this->adapter->executeQuery($sql); } @@ -155,11 +155,11 @@ public function testGetHelpersQuoteValueClosureCallsPlatform(): void $this->mockPlatform ->method('quoteValue') ->with('test') - ->willThrowException(VunerablePlatformQuoteException::forPlatformAndMethod('test', 'test')); + ->willThrowException(RuntimeException::forVulnerablePlatformQuote('test', 'test')); $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_VALUE); - $this->expectException(VunerablePlatformQuoteException::class); + self::expectException(RuntimeException::class); $functions[0]('test'); } @@ -226,8 +226,8 @@ public function testMagicGetReturnsDriverAndPlatformCaseInsensitively(): void self::assertSame($this->mockPlatform, $this->adapter->PlatForm); self::assertSame($this->mockPlatform, $this->adapter->platform); - $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('Invalid magic'); + self::expectException('InvalidArgumentException'); + self::expectExceptionMessage(InvalidArgumentException::INVALID_MAGIC_PROPERTY); /** @phpstan-ignore property.notFound, expr.resultUnused */ $this->adapter->foo; } @@ -294,8 +294,8 @@ public function testProducedResultSetPrototypeIsDifferentForEachQuery(): void public function testQueryThrowsOnInvalidParameterType(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Flag incorrectly set'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::INCORRECT_FLAG); $this->adapter->query('SELECT 1', 'invalid_mode'); } diff --git a/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php b/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php index 09d9862e..ef473075 100644 --- a/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php +++ b/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php @@ -4,6 +4,7 @@ namespace PhpDbTest\Adapter\Driver\Feature; +use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\Feature\DriverFeatureInterface; use PhpDb\Adapter\Driver\Feature\DriverFeatureProviderInterface; use PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait; @@ -13,6 +14,8 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; +use function sprintf; + #[Group('unit')] #[CoversMethod(DriverFeatureProviderTrait::class, 'addFeature')] #[CoversMethod(DriverFeatureProviderTrait::class, 'addFeatures')] @@ -49,8 +52,12 @@ public function testAddFeatureThrowsWhenUsedOutsideDriverInterface(): void $feature = $this->createMock(DriverFeatureInterface::class); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('can only be composed into'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf( + RuntimeException::UNCOMPOSABLE_TRAIT, + DriverFeatureProviderTrait::class, + DriverInterface::class, + )); $nonDriver->addFeature($feature); } diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionTest.php index 1de9bcb3..8f876d09 100644 --- a/test/unit/Adapter/Driver/Pdo/ConnectionTest.php +++ b/test/unit/Adapter/Driver/Pdo/ConnectionTest.php @@ -101,7 +101,7 @@ public function testExecuteCallsProfilerFinishBeforeThrowingOnError(): void $driver = new TestPdo($connection); $connection->setProfiler($profiler); - $this->expectException(InvalidQueryException::class); + self::expectException(InvalidQueryException::class); @$connection->execute('INVALID SQL STATEMENT HERE %%%'); } @@ -152,8 +152,8 @@ public function testGetDsnThrowsWhenDsnIsNull(): void $reflection = new ReflectionProperty($connection, 'dsn'); $reflection->setValue($connection, null); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The DSN has not been set'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_DSN); $connection->getDsn(); } diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php index 73ea607e..65d9cc27 100644 --- a/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php +++ b/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php @@ -112,8 +112,8 @@ public function testRollbackDisconnectedThrowsException(): void { $this->wrapper->disconnect(); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Must be connected before you can rollback'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::DISCONNECTED_ROLLBACK); $this->wrapper->rollback(); } @@ -132,8 +132,8 @@ public function testRollbackSetsInTransactionAtFalse(): void public function testRollbackWithoutBeginThrowsException(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Must call beginTransaction() before you can rollback'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::ROLLBACK_WITHOUT_TRANSACTION); $this->wrapper->rollback(); } diff --git a/test/unit/Adapter/Driver/Pdo/ResultTest.php b/test/unit/Adapter/Driver/Pdo/ResultTest.php index 1f9a6656..73f5b277 100644 --- a/test/unit/Adapter/Driver/Pdo/ResultTest.php +++ b/test/unit/Adapter/Driver/Pdo/ResultTest.php @@ -260,7 +260,7 @@ public function testGetQueryResultThrowsWhenResultIsNotAQueryResult(): void $result = new Result(); $result->initialize($stub, null); - $this->expectException(RuntimeException::class); + self::expectException(RuntimeException::class); $result->getQueryResult(); } @@ -371,7 +371,7 @@ public function testRewindThrowsExceptionOnForwardOnlyAfterAdvancing(): void $result->rewind(); $result->next(); - $this->expectException(RuntimeException::class); + self::expectException(RuntimeException::class); $result->rewind(); } @@ -387,8 +387,8 @@ public function testSetFetchModeThrowsOnInvalidFetchMode(): void { $result = new Result(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The fetch mode must be one of the PDO::FETCH_* constants.'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::INVALID_FETCH_MODE); $result->setFetchMode(9999); } @@ -397,7 +397,7 @@ public function testSetFetchModeThrowsOnInvalidMode(): void { $result = new Result(); - $this->expectException(InvalidArgumentException::class); + self::expectException(InvalidArgumentException::class); $result->setFetchMode(13); } @@ -405,7 +405,7 @@ public function testSetStatementModeThrowsOnInvalidMode(): void { $result = new Result(); - $this->expectException(InvalidArgumentException::class); + self::expectException(InvalidArgumentException::class); $result->setStatementMode('invalid'); } diff --git a/test/unit/Adapter/Driver/Pdo/StatementTest.php b/test/unit/Adapter/Driver/Pdo/StatementTest.php index 53a5be69..5b3eb200 100644 --- a/test/unit/Adapter/Driver/Pdo/StatementTest.php +++ b/test/unit/Adapter/Driver/Pdo/StatementTest.php @@ -23,6 +23,8 @@ use PHPUnit\Framework\TestCase; use ReflectionProperty; +use function sprintf; + #[CoversMethod(Statement::class, 'setDriver')] #[CoversMethod(Statement::class, 'setParameterContainer')] #[CoversMethod(Statement::class, 'getParameterContainer')] @@ -255,7 +257,7 @@ public function testExecuteCallsProfilerFinishOnFailure(): void $reflection = new ReflectionProperty($this->statement, 'resource'); $reflection->setValue($this->statement, $pdoStmt); - $this->expectException(InvalidQueryException::class); + self::expectException(InvalidQueryException::class); $this->statement->execute(); } @@ -324,7 +326,7 @@ public function testExecuteThrowsInvalidQueryExceptionOnPdoException(): void $reflection = new ReflectionProperty($this->statement, 'resource'); $reflection->setValue($this->statement, $pdoStmt); - $this->expectException(InvalidQueryException::class); + self::expectException(InvalidQueryException::class); $this->statement->execute(); } @@ -335,8 +337,8 @@ public function testExecuteThrowsOnInvalidParameterName(string $name): void $this->statement->initialize($pdo); $this->statement->prepare('SELECT 1'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('contains invalid characters'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::INVALID_PDO_PARAM, $name)); $this->statement->execute([$name => 'value']); } @@ -443,7 +445,7 @@ public function testPrepareThrowsRuntimeExceptionOnPdoFailure(): void $this->statement->initialize($pdo); $this->statement->setSql('INVALID SQL'); - $this->expectException(RuntimeException::class); + self::expectException(RuntimeException::class); $this->statement->prepare(); } @@ -452,8 +454,8 @@ public function testPrepareThrowsWhenAlreadyPrepared(): void $this->statement->initialize(new SqliteMemoryPdo()); $this->statement->prepare('SELECT 1'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This statement has been prepared already'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::ALREADY_PREPARED); $this->statement->prepare('SELECT 2'); } diff --git a/test/unit/Adapter/Exception/InvalidArgumentExceptionTest.php b/test/unit/Adapter/Exception/InvalidArgumentExceptionTest.php new file mode 100644 index 00000000..fb178ab8 --- /dev/null +++ b/test/unit/Adapter/Exception/InvalidArgumentExceptionTest.php @@ -0,0 +1,80 @@ +, string}> */ + public static function namedConstructorProvider(): array + { + return [ + 'incorrect flag' => [ + 'forIncorrectFlag', + [], + InvalidArgumentException::INCORRECT_FLAG, + ], + 'invalid fetch mode' => [ + 'forInvalidFetchMode', + [], + InvalidArgumentException::INVALID_FETCH_MODE, + ], + 'invalid key type' => [ + 'forInvalidKeyType', + [], + InvalidArgumentException::INVALID_KEY_TYPE, + ], + 'invalid magic property' => [ + 'forInvalidMagicProperty', + [], + InvalidArgumentException::INVALID_MAGIC_PROPERTY, + ], + 'invalid statement mode' => [ + 'forInvalidStatementMode', + [], + InvalidArgumentException::INVALID_STATEMENT_MODE, + ], + 'missing data' => [ + 'forMissingData', + [], + InvalidArgumentException::MISSING_DATA, + ], + ]; + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorRendersItsTemplate(string $method, array $arguments, string $template): void + { + $exception = InvalidArgumentException::{$method}(...$arguments); + + self::assertSame(sprintf($template, ...$arguments), $exception->getMessage()); + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorReturnsTheComponentExceptionType(string $method, array $arguments): void + { + self::assertInstanceOf(ExceptionInterface::class, InvalidArgumentException::{$method}(...$arguments)); + } +} diff --git a/test/unit/Adapter/Exception/InvalidQueryExceptionTest.php b/test/unit/Adapter/Exception/InvalidQueryExceptionTest.php new file mode 100644 index 00000000..0a9b1b11 --- /dev/null +++ b/test/unit/Adapter/Exception/InvalidQueryExceptionTest.php @@ -0,0 +1,59 @@ +getMessage(), + ); + } + + #[Test] + public function forFailedStatementKeepsTheCodeAndCause(): void + { + $cause = new RuntimeException(); + $exception = InvalidQueryException::forFailedStatement('HY000', 42, $cause); + + self::assertSame(42, $exception->getCode()); + self::assertSame($cause, $exception->getPrevious()); + } + + #[Test] + public function forFailedStatementRendersItsTemplate(): void + { + $exception = InvalidQueryException::forFailedStatement('HY000 - 1 - near "x"', 42, new RuntimeException()); + + self::assertSame( + sprintf(InvalidQueryException::FAILED_STATEMENT, 'HY000 - 1 - near "x"'), + $exception->getMessage(), + ); + } + + #[Test] + public function isReachableThroughTheComponentExceptionInterface(): void + { + self::assertInstanceOf(ExceptionInterface::class, InvalidQueryException::forDriverError('x')); + } +} diff --git a/test/unit/Adapter/Exception/RuntimeExceptionTest.php b/test/unit/Adapter/Exception/RuntimeExceptionTest.php new file mode 100644 index 00000000..78ece44b --- /dev/null +++ b/test/unit/Adapter/Exception/RuntimeExceptionTest.php @@ -0,0 +1,125 @@ +, string}> */ + public static function namedConstructorProvider(): array + { + return [ + 'already prepared' => [ + 'forAlreadyPrepared', + [], + RuntimeException::ALREADY_PREPARED, + ], + 'disconnected rollback' => [ + 'forDisconnectedRollback', + [], + RuntimeException::DISCONNECTED_ROLLBACK, + ], + 'driver error' => [ + 'forDriverError', + ['SQLSTATE[42S02]: Base table not found'], + RuntimeException::DRIVER_ERROR, + ], + 'forward only rewind' => [ + 'forForwardOnlyRewind', + [], + RuntimeException::FORWARD_ONLY_REWIND, + ], + 'invalid pdo param' => [ + 'forInvalidPdoParam', + ['bad-name'], + RuntimeException::INVALID_PDO_PARAM, + ], + 'missing dsn' => [ + 'forMissingDsn', + [], + RuntimeException::MISSING_DSN, + ], + 'missing pdo extension' => [ + 'forMissingPdoExtension', + [], + RuntimeException::MISSING_PDO_EXTENSION, + ], + 'missing query result' => [ + 'forMissingQueryResult', + [], + RuntimeException::MISSING_QUERY_RESULT, + ], + 'non query result' => [ + 'forNonQueryResult', + [], + RuntimeException::NON_QUERY_RESULT, + ], + 'rollback without transaction' => [ + 'forRollbackWithoutTransaction', + [], + RuntimeException::ROLLBACK_WITHOUT_TRANSACTION, + ], + 'uncomposable trait' => [ + 'forUncomposableTrait', + [DriverFeatureProviderTrait::class, DriverInterface::class], + RuntimeException::UNCOMPOSABLE_TRAIT, + ], + 'unstarted profile' => [ + 'forUnstartedProfile', + ['profilerFinish'], + RuntimeException::UNSTARTED_PROFILE, + ], + 'vulnerable platform quote' => [ + 'forVulnerablePlatformQuote', + [Sql92::class, 'Sql92::quoteValue'], + RuntimeException::VULNERABLE_PLATFORM_QUOTE, + ], + ]; + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorRendersItsTemplate(string $method, array $arguments, string $template): void + { + $exception = RuntimeException::{$method}(...$arguments); + + self::assertSame(sprintf($template, ...$arguments), $exception->getMessage()); + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorReturnsTheComponentExceptionType(string $method, array $arguments): void + { + self::assertInstanceOf(ExceptionInterface::class, RuntimeException::{$method}(...$arguments)); + } +} diff --git a/test/unit/Adapter/ParameterContainerTest.php b/test/unit/Adapter/ParameterContainerTest.php index 2e43c62e..4c391040 100644 --- a/test/unit/Adapter/ParameterContainerTest.php +++ b/test/unit/Adapter/ParameterContainerTest.php @@ -147,8 +147,8 @@ public function testOffsetGetErrataThrowsWhenNameDoesNotExist(): void { $container = new ParameterContainer(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data does not exist for this name/position'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::MISSING_DATA); $container->offsetGetErrata('nonexistent'); } @@ -165,8 +165,8 @@ public function testOffsetGetMaxLengthThrowsWhenNameDoesNotExist(): void { $container = new ParameterContainer(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data does not exist for this name/position'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::MISSING_DATA); $container->offsetGetMaxLength('nonexistent'); } @@ -294,8 +294,8 @@ public function testOffsetSetThrowsOnInvalidKeyType(): void { $container = new ParameterContainer(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Keys must be string, integer or null'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::INVALID_KEY_TYPE); $container->offsetSet(1.5, 'value'); } @@ -357,8 +357,8 @@ public function testOffsetUnsetErrataThrowsWhenNameDoesNotExist(): void { $container = new ParameterContainer(['foo' => 'bar']); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data does not exist for this name/position'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::MISSING_DATA); $container->offsetUnsetErrata('foo'); } @@ -384,8 +384,8 @@ public function testOffsetUnsetMaxLengthThrowsWhenNameDoesNotExist(): void { $container = new ParameterContainer(['foo' => 'bar']); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data does not exist for this name/position'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::MISSING_DATA); $container->offsetUnsetMaxLength('foo'); } diff --git a/test/unit/Adapter/Platform/Sql92Test.php b/test/unit/Adapter/Platform/Sql92Test.php index b19279bd..1b11b20e 100644 --- a/test/unit/Adapter/Platform/Sql92Test.php +++ b/test/unit/Adapter/Platform/Sql92Test.php @@ -6,7 +6,7 @@ use Override; use PhpDb\Adapter\Driver\DriverInterface; -use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; +use PhpDb\Adapter\Exception\RuntimeException; use PhpDb\Adapter\Platform\AbstractPlatform; use PhpDb\Adapter\Platform\Sql92; use PhpDbTest\Adapter\Platform\TestAsset\TestPlatform; @@ -50,7 +50,7 @@ public function testAbstractPlatformQuoteValueThrowsWithoutDriver(): void { $platform = new TestPlatform(); - $this->expectException(VunerablePlatformQuoteException::class); + $this->expectException(RuntimeException::class); $platform->quoteValue('value'); } @@ -159,19 +159,19 @@ public function testQuoteValueEscapesSpecialCharacters(): void public function testQuoteValueListThrowsWithoutDriver(): void { - $this->expectException(VunerablePlatformQuoteException::class); + $this->expectException(RuntimeException::class); self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteValueList("Foo O'Bar")); } public function testQuoteValueRaisesNoticeWithoutPlatformSupport(): void { - $this->expectException(VunerablePlatformQuoteException::class); + $this->expectException(RuntimeException::class); $this->platform->quoteValue('value'); } public function testQuoteValueThrowsWithoutDriver(): void { - $this->expectException(VunerablePlatformQuoteException::class); + $this->expectException(RuntimeException::class); self::assertEquals("'value'", @$this->platform->quoteValue('value')); self::assertEquals("'Foo O\\'Bar'", @$this->platform->quoteValue("Foo O'Bar")); self::assertEquals( diff --git a/test/unit/Adapter/Profiler/ProfilerTest.php b/test/unit/Adapter/Profiler/ProfilerTest.php index 38724686..ff1d6117 100644 --- a/test/unit/Adapter/Profiler/ProfilerTest.php +++ b/test/unit/Adapter/Profiler/ProfilerTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; +use function sprintf; + #[CoversMethod(Profiler::class, 'profilerStart')] #[CoversMethod(Profiler::class, 'profilerFinish')] #[CoversMethod(Profiler::class, 'getLastProfile')] @@ -51,8 +53,8 @@ public function testProfilerFinishThrowsWithoutStart(): void self::assertSame($this->profiler, $ret); $profiler = new Profiler(); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('A profile must be started before profilerFinish can be called'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::UNSTARTED_PROFILE, 'profilerFinish')); $profiler->profilerFinish(); } diff --git a/test/unit/Exception/ExceptionHierarchyTest.php b/test/unit/Exception/ExceptionHierarchyTest.php index 978f0df8..16c28db4 100644 --- a/test/unit/Exception/ExceptionHierarchyTest.php +++ b/test/unit/Exception/ExceptionHierarchyTest.php @@ -62,7 +62,6 @@ final class ExceptionHierarchyTest extends TestCase AdapterException\InvalidQueryException::class => UnexpectedValueException::class, AdapterException\RuntimeException::class => RuntimeException::class, AdapterException\UnexpectedValueException::class => UnexpectedValueException::class, - AdapterException\VunerablePlatformQuoteException::class => RuntimeException::class, DbException\ContainerException::class => RuntimeException::class, DbException\ErrorException::class => Exception::class, DbException\InvalidArgumentException::class => InvalidArgumentException::class, @@ -82,12 +81,6 @@ final class ExceptionHierarchyTest extends TestCase TableGatewayException\RuntimeException::class => RuntimeException::class, ]; - /** - * Exempt from the naming guard: its factory is named for its arguments rather than the - * fault, and holds its message inline. - */ - private const string UNCONVERTED_CLASS = AdapterException\VunerablePlatformQuoteException::class; - /** @return array */ public static function exceptionClassProvider(): array { @@ -106,10 +99,6 @@ public static function namedConstructorProvider(): array { $cases = []; foreach (array_keys(self::EXCEPTION_CLASSES) as $class) { - if (self::UNCONVERTED_CLASS === $class) { - continue; - } - foreach (self::namedConstructors($class) as $method) { $cases["{$class}::{$method}()"] = [ $class, diff --git a/test/unit/Sql/Predicate/PredicateTest.php b/test/unit/Sql/Predicate/PredicateTest.php index fbbb5335..f257625a 100644 --- a/test/unit/Sql/Predicate/PredicateTest.php +++ b/test/unit/Sql/Predicate/PredicateTest.php @@ -5,7 +5,7 @@ namespace PhpDbTest\Sql\Predicate; use ErrorException; -use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; +use PhpDb\Adapter\Exception\RuntimeException as AdapterRuntimeException; use PhpDb\Adapter\Platform\Sql92; use PhpDb\Sql\Argument; use PhpDb\Sql\Expression; @@ -469,7 +469,7 @@ private function makeSqlString(Predicate $where): string // } finally { // ErrorHandler::stop(); // } - self::expectException(VunerablePlatformQuoteException::class); + self::expectException(AdapterRuntimeException::class); return $select->getSqlString(new Sql92()); } }