diff --git a/.gitignore b/.gitignore index 811cd36..956c545 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /infection.json5 /infection.log /summary.log +/mago-results.txt diff --git a/README.md b/README.md index bf14ee7..3895ef3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# PhpDb Adapter Mysql +# PhpDb Mysql +[![PHP Version](https://img.shields.io/packagist/php-v/php-db/phpdb-mysql)](https://packagist.org/packages/php-db/phpdb-mysql) [![Continuous Integration](https://github.com/php-db/phpdb-mysql/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/php-db/phpdb-mysql/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/php-db/phpdb-mysql/graph/badge.svg)](https://codecov.io/gh/php-db/phpdb-mysql) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fphp-db%2Fphpdb-mysql%2F0.5.x)](https://dashboard.stryker-mutator.io/reports/github.com/php-db/phpdb-mysql/0.5.x) +[![Latest Stable Version](https://img.shields.io/packagist/v/php-db/phpdb-mysql)](https://packagist.org/packages/php-db/phpdb-mysql) +[![License](https://img.shields.io/github/license/php-db/phpdb-mysql)](LICENSE) This package provides MySQL support for PhpDb. diff --git a/analysis-baseline.toml b/analysis-baseline.toml new file mode 100644 index 0000000..384d919 --- /dev/null +++ b/analysis-baseline.toml @@ -0,0 +1,685 @@ +variant = "loose" + +[[issues]] +file = "src/AdapterPlatform.php" +code = "falsable-return-statement" +message = '''Function `PhpDb\Mysql\AdapterPlatform::quoteViaDriver` is declared to return `null|string` but possibly returns 'false' (inferred as `false|string`).''' +count = 1 + +[[issues]] +file = "src/AdapterPlatform.php" +code = "impossible-condition" +message = "This condition (type `false`) will always evaluate to false." +count = 1 + +[[issues]] +file = "src/AdapterPlatform.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Mysql\AdapterPlatform::quoteViaDriver`: expected `null|string`, but found `false|string`.' +count = 1 + +[[issues]] +file = "src/AdapterPlatform.php" +code = "missing-constant-type" +message = "Class constant `PLATFORM_NAME` is missing a type hint." +count = 1 + +[[issues]] +file = "src/AdapterPlatform.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Mysql\AdapterPlatform::quoteViaDriver`: expected `string`, but possibly received `bool|float|int|string`.' +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "incompatible-property-type" +message = 'Property `PhpDb\Mysql\Connection::$resource` has an incompatible type declaration from docblock.' +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "invalid-property-assignment-value" +message = "Invalid type for property `$resource`: expected `mysqli`, but got `null`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `mysqli::options`: expected `int`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `mysqli::set_charset`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `mysqli::ssl_set`: expected `null|string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `mysqli::options`: expected `int|string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `mysqli::real_connect`: expected `null|string`, but found `mixed`." +count = 2 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `mysqli::ssl_set`: expected `null|string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #3 of `mysqli::real_connect`: expected `null|string`, but found `mixed`." +count = 2 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #3 of `mysqli::ssl_set`: expected `null|string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #4 of `mysqli::real_connect`: expected `null|string`, but found `mixed`." +count = 2 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #4 of `mysqli::ssl_set`: expected `null|string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #5 of `mysqli::ssl_set`: expected `null|string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 7 + +[[issues]] +file = "src/Connection.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 6 + +[[issues]] +file = "src/Connection.php" +code = "mixed-operand" +message = "Left operand in `&&` operation has `mixed` type." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "mixed-return-statement" +message = "Could not infer a precise return type for function `{closure:src/Connection.php:118:31}`. Saw type `nonnull`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "possibly-null-argument" +message = "Argument #1 of method `Exception::__construct` is possibly `null`, but parameter type `string` does not accept it." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('charset')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('driver_options')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Connection.php" +code = "redundant-condition" +message = "This condition (type `true`) will always evaluate to true." +count = 2 + +[[issues]] +file = "src/Container/ConnectionInterfaceFactory.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Mysql\Connection::__construct`: expected `array|mysqli|null`, but provided type `non-empty-array` is less specific.' +count = 1 + +[[issues]] +file = "src/Container/ConnectionInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Adapter\Exception\InvalidConnectionParametersException::__construct`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/ConnectionInterfaceFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Container/DriverInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Laminas\ServiceManager\ServiceManager::build`: expected `array|null`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/MetadataInterfaceFactory.php" +code = "missing-constant-type" +message = "Class constant `ADAPTER_SERVICE_NAME` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Container/MetadataInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Source\AbstractSource::__construct`: expected `PhpDb\Adapter\AdapterInterface&PhpDb\Adapter\SchemaAwareInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Container/MetadataInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Psr\Container\ContainerInterface::get`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/MetadataInterfaceFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Container/PdoConnectionInterfaceFactory.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Mysql\Pdo\Connection::__construct`: expected `PDO|array`, but provided type `non-empty-array` is less specific.' +count = 1 + +[[issues]] +file = "src/Container/PdoConnectionInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Adapter\Exception\InvalidConnectionParametersException::__construct`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/PdoConnectionInterfaceFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Container/PdoDriverInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Laminas\ServiceManager\ServiceManager::build`: expected `array|null`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/PdoDriverInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #4 of `PhpDb\Mysql\Pdo\Driver::__construct`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/PdoStatementFactory.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Adapter\Driver\Pdo\Statement::__construct` is possibly `null`, but parameter type `array` does not accept it.' +count = 1 + +[[issues]] +file = "src/Container/PlatformInterfaceFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Container/StatementInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Mysql\Statement::__construct`: expected `bool`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Driver.php" +code = "docblock-type-mismatch" +message = "Docblock type mismatch for variable `$resource`." +count = 1 + +[[issues]] +file = "src/Driver.php" +code = "incompatible-parameter-type" +message = 'Parameter `$resource` of `PhpDb\Mysql\Driver::createresult()` expects type `mysqli|mysqli_stmt|unknown-ref(PhpDb\Mysql\mysqli_result)` but parent `PhpDb\Adapter\Driver\DriverInterface::createresult()` expects type `resource`' +count = 1 + +[[issues]] +file = "src/Driver.php" +code = "incompatible-parameter-type" +message = 'Parameter `$sqlOrResource` of `PhpDb\Mysql\Driver::createstatement()` expects type `mysqli|mysqli_stmt|string` but parent `PhpDb\Adapter\Driver\DriverInterface::createstatement()` expects type `resource|string`' +count = 1 + +[[issues]] +file = "src/Driver.php" +code = "missing-property-type" +message = "Property `$options` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Driver.php" +code = "non-existent-class-like" +message = 'Cannot find class, interface, enum, or type alias `PhpDb\Mysql\mysqli_result`.' +count = 1 + +[[issues]] +file = "src/Driver.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Mysql\Result::initialize`: expected `mysqli|mysqli_result|mysqli_stmt`, but possibly received `mysqli|mysqli_stmt|unknown-ref(PhpDb\Mysql\mysqli_result)`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "mixed-array-assignment" +message = "Unsafe array assignment on type `mixed`." +count = 9 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-invalid-argument" +message = "Possible argument type mismatch for argument #2 of `implode`: expected `array|null`, but possibly received `non-empty-list`." +count = 7 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-null-argument" +message = "Argument #2 of function `preg_match_all` is possibly `null`, but parameter type `string` does not accept it." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-null-argument" +message = "Argument #3 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-null-operand" +message = "Possibly null middle operand used in string concatenation (type `null|string`)." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-null-operand" +message = "Possibly null right operand used in string concatenation (type `null|string`)." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array index accessed on `list>`." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(1)` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-undefined-variable" +message = "Variable `$isFK` might not have been defined on all execution paths leading to this point." +count = 1 + +[[issues]] +file = "src/Metadata/Source.php" +code = "possibly-undefined-variable" +message = "Variable `$name` might not have been defined on all execution paths leading to this point." +count = 2 + +[[issues]] +file = "src/Metadata/Source.php" +code = "reference-constraint-violation" +message = "Invalid assignment to by-reference parameter `$c`." +count = 7 + +[[issues]] +file = "src/Metadata/Source.php" +code = "too-many-arguments" +message = 'Too many arguments provided for method `PhpDb\Metadata\Source\AbstractSource::prepareDataHierarchy`.' +count = 6 + +[[issues]] +file = "src/Metadata/Source.php" +code = "unused-method" +message = "Method `loadconstraintdatanames()` is never used." +count = 1 + +[[issues]] +file = "src/Pdo/Connection.php" +code = "invalid-type-cast" +message = "Casting `mixed` to `array`." +count = 1 + +[[issues]] +file = "src/Pdo/Connection.php" +code = "less-specific-argument" +message = "Argument type mismatch for argument #1 of `strtolower`: expected `string`, but provided type `array-key` is less specific." +count = 1 + +[[issues]] +file = "src/Pdo/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `array_diff_key`: expected `array<('K.array_diff_key() extends array-key), ('V.array_diff_key() extends mixed)>`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Pdo/Connection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #4 of `PDO::__construct`: expected `array|null`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Pdo/Connection.php" +code = "mixed-array-assignment" +message = "Unsafe array assignment on type `mixed`." +count = 1 + +[[issues]] +file = "src/Pdo/Connection.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 3 + +[[issues]] +file = "src/Pdo/Driver.php" +code = "incompatible-parameter-type" +message = 'Parameter `$resource` of `PhpDb\Mysql\Pdo\Driver::createresult()` expects type `PDOStatement` but parent `PhpDb\Adapter\Driver\DriverInterface::createresult()` expects type `resource`' +count = 1 + +[[issues]] +file = "src/Pdo/Driver.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$connection`: expected `(PhpDb\Adapter\Driver\PdoConnectionInterface&PhpDb\Adapter\Driver\AbstractConnection&PhpDb\Adapter\Driver\PdoDriverAwareInterface)|PDO`, but got `(PhpDb\Adapter\Driver\PdoConnectionInterface&PhpDb\Adapter\Driver\PdoDriverAwareInterface)|PDO`.' +count = 1 + +[[issues]] +file = "src/Result.php" +code = "missing-constructor" +message = 'Class `PhpDb\Mysql\Result` has typed properties without default values but no constructor to initialize them.' +count = 1 + +[[issues]] +file = "src/Result.php" +code = "possibly-null-array-index" +message = "Possibly using `null` as an array index to access element." +count = 1 + +[[issues]] +file = "src/Result.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array index accessed on `list`." +count = 1 + +[[issues]] +file = "src/Result.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array index accessed on `list`." +count = 1 + +[[issues]] +file = "src/Result.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Result.php" +code = "unused-property" +message = "Property `$numberOfRows` is never used." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Mysql\Sql\Ddl\AlterTableDecorator::processChangeColumns`: expected `array>`, but found `list{array{}|non-empty-list}`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but found `truthy-mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteValue`: expected `string`, but found `truthy-mixed`.' +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::processExpression`: expected `PhpDb\Sql\ExpressionInterface`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `strtoupper`: expected `string`, but found `truthy-mixed`." +count = 4 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `uksort`: expected `array`, but found `mixed`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 6 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "possibly-null-argument" +message = "Argument #3 of function `substr_replace` is possibly `null`, but parameter type `array|int` does not accept it." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "possibly-null-operand" +message = "Left operand in arithmetic operation might be `null` (type `int|null`)." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)|int(1)|int(2)` accessed on `array{0: int, 1: int, 2: int, 3: int, ...}`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)|int(1)|int(2)` accessed on `array{0: int, 1: int, 2: int, 3: int}`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array{0: int, 1: int, 2: int, 3: int, ...}`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array{0: int, 1: int, 2: int, 3: int}`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteValue`: expected `string`, but found `truthy-mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::processExpression`: expected `PhpDb\Sql\ExpressionInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `strtoupper`: expected `string`, but found `truthy-mixed`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `uksort`: expected `array`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 3 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "possibly-null-argument" +message = "Argument #3 of function `substr_replace` is possibly `null`, but parameter type `array|int` does not accept it." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "possibly-null-operand" +message = "Left operand in arithmetic operation might be `null` (type `int|null`)." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)|int(1)|int(2)` accessed on `array{0: int, 1: int, 2: int, 3: int, ...}`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)|int(1)|int(2)` accessed on `array{0: int, 1: int, 2: int, 3: int}`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array{0: int, 1: int, 2: int, 3: int, ...}`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTableDecorator.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array{0: int, 1: int, 2: int, 3: int}`." +count = 1 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "invalid-property-assignment-value" +message = "Invalid type for property `$specifications`: expected `array>|array`, but got `array{'limit': string('LIMIT 18446744073709551615'), ...|string>}`." +count = 1 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Mysql\Sql\SelectDecorator::processLimit`: expected `array|null`, but found `list{int|string}`.' +count = 1 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Mysql\Sql\SelectDecorator::processOffset`: expected `array|null`, but found `list{int|string}`.' +count = 1 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `list{mixed}` is less specific than the declared return type `array|null` for function `PhpDb\Mysql\Sql\SelectDecorator::processLimit` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `list{mixed}` is less specific than the declared return type `array|null` for function `PhpDb\Mysql\Sql\SelectDecorator::processOffset` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 2 + +[[issues]] +file = "src/Sql/SelectDecorator.php" +code = "write-only-property" +message = "Property `$subject` is written to but never read." +count = 1 + +[[issues]] +file = "src/Statement.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Statement.php" +code = "uninitialized-property" +message = 'Property `$driver` is not initialized in the constructor of class `PhpDb\Mysql\Statement`.' +count = 1 + +[[issues]] +file = "src/Statement.php" +code = "uninitialized-property" +message = 'Property `$mysqli` is not initialized in the constructor of class `PhpDb\Mysql\Statement`.' +count = 1 + +[[issues]] +file = "src/Statement.php" +code = "uninitialized-property" +message = 'Property `$resource` is not initialized in the constructor of class `PhpDb\Mysql\Statement`.' +count = 1 diff --git a/mago.toml b/mago.toml index 4e2bc2b..7cf3255 100644 --- a/mago.toml +++ b/mago.toml @@ -1,7 +1,10 @@ -#:schema https://mago.carthage.software/1.45.0/schema.json +#:schema https://mago.carthage.software/1.46.0/schema.json extends = "vendor/php-db/phpdb-qa-tools/mago.toml" php-version = "8.3.0" [source] paths = ["src", "test"] includes = ["vendor"] + +[analyzer] +baseline = "analysis-baseline.toml" diff --git a/src/AdapterPlatform.php b/src/AdapterPlatform.php index 7e00bac..9db9b86 100644 --- a/src/AdapterPlatform.php +++ b/src/AdapterPlatform.php @@ -14,7 +14,7 @@ use function implode; use function str_replace; -class AdapterPlatform extends AbstractPlatform +final class AdapterPlatform extends AbstractPlatform { final public const PLATFORM_NAME = 'MySQL'; @@ -61,7 +61,7 @@ public function getSqlPlatformDecorator(): PlatformDecoratorInterface #[Override] public function quoteIdentifierChain(array|string $identifierChain): string { - return '`' . implode('`.`', (array) str_replace('`', '``', $identifierChain)) . '`'; + return '`' . implode('`.`', (array) str_replace('`', replace: '``', subject: $identifierChain)) . '`'; } /** @@ -88,14 +88,13 @@ public function quoteValue(string $value): string protected function quoteViaDriver(string $value): ?string { - if ($this->driver instanceof DriverInterface) { - // todo: verify this can not return a PDOStatement instance - $resource = $this->driver->getConnection()->getResource(); - } else { - $resource = $this->driver; - } + // todo(@tyrsson): verify this can not return a PDOStatement instance + $resource = $this->driver instanceof DriverInterface + ? $this->driver->getConnection()->getResource() + : $this->driver; if ($resource instanceof mysqli) { + // @mago-expect lint:string-style return '\'' . $resource->real_escape_string($value) . '\''; } diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 9ba7e69..7f73013 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -12,6 +12,12 @@ final class ConfigProvider { + /** + * @return array{ + * aliases: array, + * factories: array, + * } + */ public function getDependencies(): array { return [ @@ -44,6 +50,14 @@ public function getDependencies(): array ]; } + /** + * @return array{ + * dependencies: array{ + * aliases: array, + * factories: array, + * }, + * } + */ public function __invoke(): array { return [ diff --git a/src/Connection.php b/src/Connection.php index 6d96c4c..4ab3800 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -6,6 +6,7 @@ use Exception as GenericException; use mysqli; +use mysqli_result; use Override; use PhpDb\Adapter\Driver\AbstractConnection; use PhpDb\Adapter\Driver\ConnectionInterface; @@ -24,9 +25,15 @@ use const MYSQLI_CLIENT_SSL; use const MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; +// @mago-expect lint:cyclomatic-complexity +// @mago-expect lint:kan-defect +// @mago-expect lint:too-many-methods +// @mago-expect analysis:class-must-be-final class Connection extends AbstractConnection implements DriverAwareInterface { - protected Driver $driver; + protected ?DriverInterface $driver = null; + + protected ?string $driverName = null; /** @var mysqli */ protected $resource; @@ -34,6 +41,8 @@ class Connection extends AbstractConnection implements DriverAwareInterface /** * Constructor * + * @param array|mysqli|null $connectionInfo + * * @throws InvalidArgumentException */ public function __construct( @@ -41,16 +50,22 @@ public function __construct( ) { if (is_array($connectionInfo)) { $this->setConnectionParameters($connectionInfo); - } elseif ($connectionInfo instanceof mysqli) { + + return; + } + + if ($connectionInfo instanceof mysqli) { $this->setResource($connectionInfo); - } elseif (null !== $connectionInfo) { - throw new Exception\InvalidArgumentException( - '$connection must be an array of parameters, a mysqli object or null', - ); + + return; } } - /** @inheritDoc */ + /** + * @inheritDoc + * + * @throws Exception\ExceptionInterface + */ #[Override] public function beginTransaction(): ConnectionInterface { @@ -64,7 +79,11 @@ public function beginTransaction(): ConnectionInterface return $this; } - /** @inheritDoc */ + /** + * @inheritDoc + * + * @throws Exception\ExceptionInterface + */ #[Override] public function commit(): ConnectionInterface { @@ -79,7 +98,12 @@ public function commit(): ConnectionInterface return $this; } - /** @inheritDoc */ + /** + * @inheritDoc + * + * @throws Exception\ExceptionInterface + */ + // @mago-expect lint:halstead #[Override] public function connect(): ConnectionInterface { @@ -87,14 +111,13 @@ public function connect(): ConnectionInterface return $this; } - /** @var array $p */ $p = $this->connectionParameters; // given a list of key names, test for existence in $p /** @var string[] $names */ - $findParameterValue = function (array $names) use ($p): ?string { + $findParameterValue = static function (array $names) use ($p): ?string { foreach ($names as $name) { - if (isset($p[$name])) { + if (null !== ($p[$name] ?? null)) { return $p[$name]; } } @@ -107,8 +130,7 @@ public function connect(): ConnectionInterface $username = $findParameterValue(['username', 'user']); $password = $findParameterValue(['password', 'passwd', 'pw']); $database = $findParameterValue(['database', 'dbname', 'db', 'schema']); - /** @var int|null $port */ - $port = isset($p['port']) ? (int) $p['port'] : null; + $port = null === ($p['port'] ?? null) ? null : (int) $p['port']; /** @var string|null $socket */ $socket = $p['socket'] ?? null; @@ -122,7 +144,7 @@ public function connect(): ConnectionInterface $this->resource = $this->createResource(); - if (! empty($p['driver_options'])) { + if ([] !== ($p['driver_options'] ?? [])) { foreach ($p['driver_options'] as $option => $value) { if (is_string($option)) { $option = strtoupper($option); @@ -146,14 +168,14 @@ public function connect(): ConnectionInterface $this->resource->ssl_set($clientKey, $clientCert, $caCert, $caPath, $cipher); //MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT is not valid option, needs to be set as flag if ( - isset($p['driver_options'][MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT]) + null !== ($p['driver_options'][MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT] ?? null) ) { $flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } } try { - $flags === null + null === $flags ? $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket) : $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket, $flags); } catch (GenericException) { @@ -172,7 +194,7 @@ public function connect(): ConnectionInterface ); } - if (! empty($p['charset'])) { + if ('' !== ($p['charset'] ?? '')) { $this->resource->set_charset($p['charset']); } @@ -193,10 +215,10 @@ public function disconnect(): ConnectionInterface /** * {@inheritDoc} * - * @throws Exception\InvalidQueryException + * @throws Exception\ExceptionInterface */ #[Override] - public function execute($sql): ?ResultInterface + public function execute(string $sql): ?ResultInterface { if (! $this->isConnected()) { $this->connect(); @@ -206,17 +228,29 @@ public function execute($sql): ?ResultInterface $resultResource = $this->resource->query($sql); - $this->profiler?->profilerFinish($sql); + $this->profiler?->profilerFinish(); // if the returnValue is something other than a mysqli_result, bypass wrapping it - if ($resultResource === false) { + if (false === $resultResource) { throw new Exception\InvalidQueryException($this->resource->error); } - return $this->driver->createResult($resultResource === true ? $this->resource : $resultResource); + if (null === $this->driver) { + throw new Exception\RuntimeException('Cannot execute without a driver; call setDriver() first.'); + } + + // @mago-expect analysis:invalid-argument - DriverInterface::createResult() is documented with a + // generic `resource` type to stay valid across every RDBMS platform (see php-db/phpdb#170 for a + // proposed @template-based fix); this class always passes real mysqli|mysqli_result objects, which + // is correct for this concrete implementation. + return $this->driver->createResult(true === $resultResource ? $this->resource : $resultResource); } - /** @inheritDoc */ + /** + * @inheritDoc + * + * @throws Exception\ExceptionInterface + */ #[Override] public function getCurrentSchema(): string|false { @@ -225,7 +259,19 @@ public function getCurrentSchema(): string|false } $result = $this->resource->query('SELECT DATABASE()'); - $r = $result->fetch_row(); + if (! $result instanceof mysqli_result) { + throw new Exception\RuntimeException('Failed to query current schema'); + } + + $r = $result->fetch_row(); + if (false === $r) { + throw new Exception\RuntimeException($this->resource->error); + } + + /** @var array{0: string|null}|null $r */ + if (null === $r || null === $r[0]) { + return false; + } return $r[0]; } @@ -238,12 +284,17 @@ public function getLastGeneratedValue(?string $name = null): string|int|false|nu } /** @inheritDoc */ + #[Override] public function isConnected(): bool { return $this->resource instanceof mysqli; } - /** @inheritDoc */ + /** + * @inheritDoc + * + * @throws Exception\ExceptionInterface + */ #[Override] public function rollback(): ConnectionInterface { @@ -262,6 +313,7 @@ public function rollback(): ConnectionInterface return $this; } + #[Override] public function setDriver(DriverInterface $driver): DriverAwareInterface { $this->driver = $driver; @@ -284,11 +336,11 @@ public function setResource(mysqli $resource): static /** * Create a new mysqli resource * - * todo: why do we have this random method here? + * todo(@tyrsson): why do we have this random method here? * * @return mysqli */ - protected function createResource() + protected function createResource(): mysqli { return new mysqli(); } diff --git a/src/Container/ConnectionInterfaceFactory.php b/src/Container/ConnectionInterfaceFactory.php index 8b7dea6..9d18ed9 100644 --- a/src/Container/ConnectionInterfaceFactory.php +++ b/src/Container/ConnectionInterfaceFactory.php @@ -13,13 +13,20 @@ final class ConnectionInterfaceFactory { + /** + * @param array|null $options + * + * @throws \PhpDb\Adapter\Exception\ExceptionInterface + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface $container, string $requestedName, ?array $options = null, ): ConnectionInterface&Connection { $conn = $options['connection'] ?? []; - if (! is_array($conn) || $conn === []) { + if (! is_array($conn) || [] === $conn) { throw new InvalidConnectionParametersException( 'Connection configuration must be an array of parameters passed via $options["connection"]', $conn, diff --git a/src/Container/DriverInterfaceFactory.php b/src/Container/DriverInterfaceFactory.php index 487d993..9cfb5b7 100644 --- a/src/Container/DriverInterfaceFactory.php +++ b/src/Container/DriverInterfaceFactory.php @@ -5,10 +5,8 @@ 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; @@ -16,14 +14,26 @@ use PhpDb\Mysql\Statement; use Psr\Container\ContainerInterface; +use function array_key_exists; + final class DriverInterfaceFactory { + /** + * @param array|null $options + * + * @throws \Laminas\ServiceManager\Exception\ExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \PhpDb\Exception\ExceptionInterface + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface&ServiceManager $container, string $requestedName, ?array $options = null, ): DriverInterface&Driver { - if (! isset($options['connection'])) { + if (null === $options || ! array_key_exists('connection', $options)) { throw ContainerException::forService( Driver::class, self::class, @@ -31,10 +41,8 @@ public function __invoke( ); } - /** @var ConnectionInterface&Connection $connectionInstance */ $connectionInstance = $container->build(Connection::class, $options); - /** @var StatementInterface&Statement $statementInstance */ $statementInstance = $container->build( Statement::class, $options['options'] ?? [], diff --git a/src/Container/MetadataInterfaceFactory.php b/src/Container/MetadataInterfaceFactory.php index 7548758..b1b8f3f 100644 --- a/src/Container/MetadataInterfaceFactory.php +++ b/src/Container/MetadataInterfaceFactory.php @@ -13,6 +13,14 @@ final class MetadataInterfaceFactory { public const ADAPTER_SERVICE_NAME = 'adapter_service_name'; + /** + * @param array|null $options + * + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface $container, string $requestedName, diff --git a/src/Container/PdoConnectionInterfaceFactory.php b/src/Container/PdoConnectionInterfaceFactory.php index 4dd003c..8b3abd7 100644 --- a/src/Container/PdoConnectionInterfaceFactory.php +++ b/src/Container/PdoConnectionInterfaceFactory.php @@ -13,13 +13,20 @@ final class PdoConnectionInterfaceFactory { + /** + * @param array|null $options + * + * @throws \PhpDb\Adapter\Exception\ExceptionInterface + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface $container, string $requestedName, ?array $options = null, ): PdoConnectionInterface&Connection { $conn = $options['connection'] ?? []; - if (! is_array($conn) || $conn === []) { + if (! is_array($conn) || [] === $conn) { throw new InvalidConnectionParametersException( 'Connection configuration must be an array of parameters passed via $options["connection"]', $conn, diff --git a/src/Container/PdoDriverInterfaceFactory.php b/src/Container/PdoDriverInterfaceFactory.php index a0f45d4..ee797f7 100644 --- a/src/Container/PdoDriverInterfaceFactory.php +++ b/src/Container/PdoDriverInterfaceFactory.php @@ -7,33 +7,40 @@ use Laminas\ServiceManager\ServiceManager; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; -use PhpDb\Adapter\Driver\PdoConnectionInterface; use PhpDb\Adapter\Driver\PdoDriverInterface; use PhpDb\Adapter\Driver\ResultInterface; -use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Exception\ContainerException; use PhpDb\Mysql\Pdo\Connection; use PhpDb\Mysql\Pdo\Driver; use Psr\Container\ContainerInterface; +use function array_key_exists; + final class PdoDriverInterfaceFactory { + /** + * @param array|null $options + * + * @throws \Laminas\ServiceManager\Exception\ExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface&ServiceManager $container, string $requestedName, ?array $options = null, ): PdoDriverInterface&Driver { - if (! isset($options['connection'])) { + if (null === $options || ! array_key_exists('connection', $options)) { throw ContainerException::forService( Driver::class, self::class, '$options["connection"] must contain an array of connection configuration.', ); } - /** @var PdoConnectionInterface&Connection $connectionInstance */ $connectionInstance = $container->build(Connection::class, $options); - /** @var StatementInterface&Statement $statementInstance */ $statementInstance = $container->build( Statement::class, $options['options'] ?? [], diff --git a/src/Container/PdoStatementFactory.php b/src/Container/PdoStatementFactory.php index 0a0c0c2..76fdcab 100644 --- a/src/Container/PdoStatementFactory.php +++ b/src/Container/PdoStatementFactory.php @@ -10,6 +10,11 @@ final class PdoStatementFactory { + /** + * @param array|null $options + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface $container, string $requestedName, diff --git a/src/Container/PlatformInterfaceFactory.php b/src/Container/PlatformInterfaceFactory.php index cbb318d..a6c6165 100644 --- a/src/Container/PlatformInterfaceFactory.php +++ b/src/Container/PlatformInterfaceFactory.php @@ -13,6 +13,13 @@ final class PlatformInterfaceFactory { + /** + * @param array|null $options + * + * @throws \Psr\Container\ContainerExceptionInterface + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface $container, string $requestedName, diff --git a/src/Container/StatementInterfaceFactory.php b/src/Container/StatementInterfaceFactory.php index 9eb78a1..479149c 100644 --- a/src/Container/StatementInterfaceFactory.php +++ b/src/Container/StatementInterfaceFactory.php @@ -10,6 +10,11 @@ final class StatementInterfaceFactory { + /** + * @param array|null $options + * + * @mago-expect analysis:unused-parameter + */ public function __invoke( ContainerInterface $container, string $requestedName, diff --git a/src/Driver.php b/src/Driver.php index 91b7d14..cbe8117 100644 --- a/src/Driver.php +++ b/src/Driver.php @@ -6,8 +6,8 @@ use mysqli; use mysqli_stmt; +use Override; use PhpDb\Adapter\Driver\ConnectionInterface; -use PhpDb\Adapter\Driver\DriverAwareInterface; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; @@ -16,7 +16,6 @@ use PhpDb\Adapter\Profiler\ProfilerInterface; use function array_intersect_key; -use function array_merge; use function extension_loaded; use function is_string; @@ -29,6 +28,11 @@ final class Driver implements DriverInterface, ProfilerAwareInterface 'buffer_results' => false, ]; + /** + * @param array $options + * + * @throws \PhpDb\Exception\ExceptionInterface + */ public function __construct( protected readonly ConnectionInterface&Connection $connection, protected readonly StatementInterface&Statement $statementPrototype = new Statement(), @@ -37,17 +41,13 @@ public function __construct( ) { $this->checkEnvironment(); - $options = array_intersect_key(array_merge($this->options, $options), $this->options); + $options = array_intersect_key([...$this->options, ...$options], $this->options); - if ($this->connection instanceof DriverAwareInterface) { - $this->connection->setDriver($this); - } - - if ($this->statementPrototype instanceof DriverAwareInterface) { - $this->statementPrototype->setDriver($this); - } + $this->connection->setDriver($this); + $this->statementPrototype->setDriver($this); } + #[Override] public function checkEnvironment(): bool { if (! extension_loaded('mysqli')) { @@ -63,9 +63,9 @@ public function checkEnvironment(): bool * * @param mysqli|mysqli_result|mysqli_stmt $resource */ + #[Override] public function createResult($resource, ?bool $isBuffered = null): ResultInterface&Result { - /** @var Result $result */ $result = clone $this->resultPrototype; $result->initialize($resource, $this->connection->getLastGeneratedValue(), $isBuffered); return $result; @@ -75,7 +75,10 @@ public function createResult($resource, ?bool $isBuffered = null): ResultInterfa * Create statement * * @param mysqli|mysqli_stmt|string $sqlOrResource + * + * @throws Exception\ExceptionInterface */ + #[Override] public function createStatement($sqlOrResource = null): StatementInterface&Statement { /** @@ -88,28 +91,32 @@ public function createStatement($sqlOrResource = null): StatementInterface&State $statement = clone $this->statementPrototype; if ($sqlOrResource instanceof mysqli_stmt) { $statement->setResource($sqlOrResource); - } else { - if (is_string($sqlOrResource)) { - $statement->setSql($sqlOrResource); - } - if (! $this->connection->isConnected()) { - $this->connection->connect(); - } - /** @var mysqli $resource */ - $resource = $this->connection->getResource(); - $statement->initialize($resource); + + return $statement; + } + + if (is_string($sqlOrResource)) { + $statement->setSql($sqlOrResource); } + if (! $this->connection->isConnected()) { + $this->connection->connect(); + } + /** @var mysqli $resource */ + $resource = $this->connection->getResource(); + $statement->initialize($resource); return $statement; } /** * Format parameter name */ + #[Override] public function formatParameterName(string $name, ?string $type = null): string { return '?'; } + #[Override] public function getConnection(): ConnectionInterface&Connection { return $this->connection; @@ -118,6 +125,7 @@ public function getConnection(): ConnectionInterface&Connection /** * Get last generated value */ + #[Override] public function getLastGeneratedValue(): int|string|false|null { return $this->getConnection()->getLastGeneratedValue(); @@ -126,6 +134,7 @@ public function getLastGeneratedValue(): int|string|false|null /** * Get prepare type */ + #[Override] public function getPrepareType(): string { return self::PARAMETERIZATION_POSITIONAL; @@ -149,15 +158,12 @@ public function getStatementPrototype(): StatementInterface&Statement return $this->statementPrototype; } + #[Override] public function setProfiler(ProfilerInterface $profiler): ProfilerAwareInterface { $this->profiler = $profiler; - if ($this->connection instanceof ProfilerAwareInterface) { - $this->connection->setProfiler($profiler); - } - if ($this->statementPrototype instanceof ProfilerAwareInterface) { - $this->statementPrototype->setProfiler($profiler); - } + $this->connection->setProfiler($profiler); + $this->statementPrototype->setProfiler($profiler); return $this; } } diff --git a/src/Metadata/Source.php b/src/Metadata/Source.php index 8c8e8a1..5370519 100644 --- a/src/Metadata/Source.php +++ b/src/Metadata/Source.php @@ -6,8 +6,10 @@ use DateTime; use Exception; +use Override; use PhpDb\Adapter\AdapterInterface; use PhpDb\Metadata\Source\AbstractSource; +use PhpDb\ResultSet\ResultSetInterface; use function array_change_key_case; use function array_walk; @@ -20,11 +22,15 @@ use const CASE_LOWER; use const PREG_PATTERN_ORDER; +// @mago-expect lint:cyclomatic-complexity +// @mago-expect lint:kan-defect final class Source extends AbstractSource { + // @mago-expect lint:halstead + #[Override] protected function loadColumnData(string $table, string $schema): void { - if (isset($this->data['columns'][$schema][$table])) { + if (null !== ($this->data['columns'][$schema][$table] ?? null)) { return; } $this->prepareDataHierarchy('columns', $schema, $table); @@ -43,7 +49,7 @@ protected function loadColumnData(string $table, string $schema): void ['C', 'COLUMN_TYPE'], ]; - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); }); @@ -72,38 +78,27 @@ protected function loadColumnData(string $table, string $schema): void . ' = ' . $p->quoteTrustedValue($table); - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} != 'INFORMATION_SCHEMA'" + : " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} = {$p->quoteTrustedValue($schema)}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $columns = []; + /** @var array{ORDINAL_POSITION: string, COLUMN_DEFAULT: ?string, IS_NULLABLE: string, DATA_TYPE: string, CHARACTER_MAXIMUM_LENGTH: ?string, CHARACTER_OCTET_LENGTH: ?string, NUMERIC_PRECISION: ?string, NUMERIC_SCALE: ?string, COLUMN_NAME: string, COLUMN_TYPE: string} $row */ foreach ($results->toArray() as $row) { $erratas = []; $matches = []; if (preg_match('/^(?:enum|set)\((.+)\)$/i', $row['COLUMN_TYPE'], $matches)) { $permittedValues = $matches[1]; - if ( - preg_match_all( - "/\\s*'((?:[^']++|'')*+)'\\s*(?:,|\$)/", - $permittedValues, - $matches, - PREG_PATTERN_ORDER, - ) - ) { - $permittedValues = str_replace("''", "'", $matches[1]); - } else { - $permittedValues = [$permittedValues]; - } + $permittedValues = preg_match_all( + "/\\s*'((?:[^']++|'')*+)'\\s*(?:,|\$)/", + $permittedValues, + $matches, + PREG_PATTERN_ORDER, + ) + ? str_replace("''", replace: "'", subject: $matches[1]) + : [$permittedValues]; $erratas['permitted_values'] = $permittedValues; } $columns[$row['COLUMN_NAME']] = [ @@ -123,10 +118,12 @@ protected function loadColumnData(string $table, string $schema): void $this->data['columns'][$schema][$table] = $columns; } + // @mago-expect lint:halstead + #[Override] protected function loadConstraintData(string $table, string $schema): void { // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps - if (isset($this->data['constraints'][$schema][$table])) { + if (null !== ($this->data['constraints'][$schema][$table] ?? null)) { return; } @@ -147,7 +144,7 @@ protected function loadConstraintData(string $table, string $schema): void $p = $this->adapter->getPlatform(); - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); }); @@ -202,44 +199,29 @@ protected function loadConstraintData(string $table, string $schema): void . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) . ' IN (\'BASE TABLE\', \'VIEW\')'; - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} != 'INFORMATION_SCHEMA'" + : " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} = {$p->quoteTrustedValue($schema)}"; - $sql .= - ' ORDER BY CASE ' - . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_TYPE']) - . " WHEN 'PRIMARY KEY' THEN 1" - . " WHEN 'UNIQUE' THEN 2" - . " WHEN 'FOREIGN KEY' THEN 3" - . ' ELSE 4 END' - . ', ' - . $p->quoteIdentifierChain(['TC', 'CONSTRAINT_NAME']) - . ', ' - . $p->quoteIdentifierChain(['KCU', 'ORDINAL_POSITION']); + $sql .= " ORDER BY CASE {$p->quoteIdentifierChain([ + 'TC', + 'CONSTRAINT_TYPE', + ])} WHEN 'PRIMARY KEY' THEN 1 WHEN 'UNIQUE' THEN 2 WHEN 'FOREIGN KEY' THEN 3 ELSE 4 END, {$p->quoteIdentifierChain([ + 'TC', + 'CONSTRAINT_NAME', + ])}, {$p->quoteIdentifierChain(['KCU', 'ORDINAL_POSITION'])}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $realName = null; $constraints = []; + /** @var array{TABLE_NAME: string, CONSTRAINT_NAME: string, CONSTRAINT_TYPE: string, COLUMN_NAME: ?string, MATCH_OPTION: ?string, UPDATE_RULE: ?string, DELETE_RULE: ?string, REFERENCED_TABLE_SCHEMA: ?string, REFERENCED_TABLE_NAME: ?string, REFERENCED_COLUMN_NAME: ?string} $row */ foreach ($results->toArray() as $row) { if ($row['CONSTRAINT_NAME'] !== $realName) { - $realName = $row['CONSTRAINT_NAME']; - $isFK = 'FOREIGN KEY' === $row['CONSTRAINT_TYPE']; - if ($isFK) { - $name = $realName; - } else { - $name = '_laminas_' . $row['TABLE_NAME'] . '_' . $realName; - } + $realName = $row['CONSTRAINT_NAME']; + $isFK = 'FOREIGN KEY' === $row['CONSTRAINT_TYPE']; + $name = $isFK ? $realName : "_phpdb_{$row['TABLE_NAME']}_{$realName}"; $constraints[$name] = [ 'constraint_name' => $name, 'constraint_type' => $row['CONSTRAINT_TYPE'], @@ -266,9 +248,10 @@ protected function loadConstraintData(string $table, string $schema): void // phpcs:enable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps } + #[Override] protected function loadConstraintDataKeys(string $schema): void { - if (isset($this->data['constraint_keys'][$schema])) { + if (null !== ($this->data['constraint_keys'][$schema] ?? null)) { return; } @@ -283,7 +266,7 @@ protected function loadConstraintDataKeys(string $schema): void ['KCU', 'ORDINAL_POSITION'], ]; - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); }); @@ -308,22 +291,15 @@ protected function loadConstraintDataKeys(string $schema): void . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) . ' IN (\'BASE TABLE\', \'VIEW\')'; - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} != 'INFORMATION_SCHEMA'" + : " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} = {$p->quoteTrustedValue($schema)}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $data = []; + /** @var array $row */ foreach ($results->toArray() as $row) { $data[] = array_change_key_case($row, CASE_LOWER); } @@ -333,7 +309,7 @@ protected function loadConstraintDataKeys(string $schema): void protected function loadConstraintDataNames(string $schema): void { - if (isset($this->data['constraint_names'][$schema])) { + if (null !== ($this->data['constraint_names'][$schema] ?? null)) { return; } @@ -347,7 +323,7 @@ protected function loadConstraintDataNames(string $schema): void ['TC', 'CONSTRAINT_TYPE'], ]; - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); }); @@ -372,22 +348,15 @@ protected function loadConstraintDataNames(string $schema): void . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) . ' IN (\'BASE TABLE\', \'VIEW\')'; - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} != 'INFORMATION_SCHEMA'" + : " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} = {$p->quoteTrustedValue($schema)}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $data = []; + /** @var array $row */ foreach ($results->toArray() as $row) { $data[] = array_change_key_case($row, CASE_LOWER); } @@ -395,6 +364,7 @@ protected function loadConstraintDataNames(string $schema): void $this->data['constraint_names'][$schema] = $data; } + #[Override] protected function loadConstraintReferences(string $table, string $schema): void { parent::loadConstraintReferences($table, $schema); @@ -411,7 +381,7 @@ protected function loadConstraintReferences(string $table, string $schema): void ['KCU', 'REFERENCED_COLUMN_NAME'], ]; - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); }); @@ -451,22 +421,15 @@ protected function loadConstraintReferences(string $table, string $schema): void . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) . ' IN (\'BASE TABLE\', \'VIEW\')'; - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} != 'INFORMATION_SCHEMA'" + : " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} = {$p->quoteTrustedValue($schema)}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $data = []; + /** @var array $row */ foreach ($results->toArray() as $row) { $data[] = array_change_key_case($row, CASE_LOWER); } @@ -477,27 +440,27 @@ protected function loadConstraintReferences(string $table, string $schema): void /** * @throws Exception */ + #[Override] protected function loadSchemaData(): void { - if (isset($this->data['schemas'])) { + if (null !== ($this->data['schemas'] ?? null)) { return; } $this->prepareDataHierarchy('schemas'); $p = $this->adapter->getPlatform(); - $sql = - 'SELECT ' - . $p->quoteIdentifier('SCHEMA_NAME') - . ' FROM ' - . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'SCHEMATA']) - . ' WHERE ' - . $p->quoteIdentifier('SCHEMA_NAME') - . ' != \'INFORMATION_SCHEMA\''; + $sql = <<quoteIdentifier('SCHEMA_NAME')} + FROM {$p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'SCHEMATA'])} + WHERE {$p->quoteIdentifier('SCHEMA_NAME')} != 'INFORMATION_SCHEMA' + SQL; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $schemas = []; + /** @var array{SCHEMA_NAME: string} $row */ foreach ($results->toArray() as $row) { $schemas[] = $row['SCHEMA_NAME']; } @@ -505,9 +468,10 @@ protected function loadSchemaData(): void $this->data['schemas'] = $schemas; } + #[Override] protected function loadTableNameData(string $schema): void { - if (isset($this->data['table_names'][$schema])) { + if (null !== ($this->data['table_names'][$schema] ?? null)) { return; } $this->prepareDataHierarchy('table_names', $schema); @@ -522,7 +486,7 @@ protected function loadTableNameData(string $schema): void ['V', 'IS_UPDATABLE'], ]; - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); }); @@ -547,22 +511,15 @@ protected function loadTableNameData(string $schema): void . $p->quoteIdentifierChain(['T', 'TABLE_TYPE']) . ' IN (\'BASE TABLE\', \'VIEW\')'; - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - ' AND ' - . $p->quoteIdentifierChain(['T', 'TABLE_SCHEMA']) - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} != 'INFORMATION_SCHEMA'" + : " AND {$p->quoteIdentifierChain(['T', 'TABLE_SCHEMA'])} = {$p->quoteTrustedValue($schema)}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $tables = []; + /** @var array{TABLE_NAME: string, TABLE_TYPE: string, VIEW_DEFINITION: ?string, CHECK_OPTION: ?string, IS_UPDATABLE: ?string} $row */ foreach ($results->toArray() as $row) { $tables[$row['TABLE_NAME']] = [ 'table_type' => $row['TABLE_TYPE'], @@ -575,9 +532,10 @@ protected function loadTableNameData(string $schema): void $this->data['table_names'][$schema] = $tables; } + #[Override] protected function loadTriggerData(string $schema): void { - if (isset($this->data['triggers'][$schema])) { + if (null !== ($this->data['triggers'][$schema] ?? null)) { return; } @@ -605,7 +563,7 @@ protected function loadTriggerData(string $schema): void 'CREATED', ]; - array_walk($isColumns, function (&$c) use ($p) { + array_walk($isColumns, static function (&$c) use ($p) { $c = $p->quoteIdentifier($c); }); @@ -616,21 +574,17 @@ protected function loadTriggerData(string $schema): void . $p->quoteIdentifierChain(['INFORMATION_SCHEMA', 'TRIGGERS']) . ' WHERE '; - if ($schema !== self::DEFAULT_SCHEMA) { - $sql .= - $p->quoteIdentifier('TRIGGER_SCHEMA') - . ' = ' - . $p->quoteTrustedValue($schema); - } else { - $sql .= - $p->quoteIdentifier('TRIGGER_SCHEMA') - . ' != \'INFORMATION_SCHEMA\''; - } + $sql .= self::DEFAULT_SCHEMA === $schema + ? "{$p->quoteIdentifier('TRIGGER_SCHEMA')} != 'INFORMATION_SCHEMA'" + : "{$p->quoteIdentifier('TRIGGER_SCHEMA')} = {$p->quoteTrustedValue($schema)}"; + /** @var ResultSetInterface $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $data = []; + /** @var array{TRIGGER_NAME: string, EVENT_MANIPULATION: string, EVENT_OBJECT_CATALOG: string, EVENT_OBJECT_SCHEMA: string, EVENT_OBJECT_TABLE: string, ACTION_ORDER: string, ACTION_CONDITION: ?string, ACTION_STATEMENT: string, ACTION_ORIENTATION: string, ACTION_TIMING: string, ACTION_REFERENCE_OLD_TABLE: ?string, ACTION_REFERENCE_NEW_TABLE: ?string, ACTION_REFERENCE_OLD_ROW: ?string, ACTION_REFERENCE_NEW_ROW: ?string, CREATED: ?string} $row */ foreach ($results->toArray() as $row) { + /** @var array{trigger_name: string, event_manipulation: string, event_object_catalog: string, event_object_schema: string, event_object_table: string, action_order: string, action_condition: ?string, action_statement: string, action_orientation: string, action_timing: string, action_reference_old_table: ?string, action_reference_new_table: ?string, action_reference_old_row: ?string, action_reference_new_row: ?string, created: ?string} $row */ $row = array_change_key_case($row, CASE_LOWER); if (null !== $row['created']) { $row['created'] = new DateTime($row['created']); diff --git a/src/Pdo/Connection.php b/src/Pdo/Connection.php index f33c0c6..49fe7dc 100644 --- a/src/Pdo/Connection.php +++ b/src/Pdo/Connection.php @@ -19,11 +19,21 @@ use function is_string; use function strtolower; -class Connection extends AbstractPdoConnection +// @mago-expect lint:cyclomatic-complexity +// @mago-expect lint:kan-defect +final class Connection extends AbstractPdoConnection { + // @mago-expect analysis:write-only-property - read by the parent's final AbstractPdoConnection::getDsn() + protected ?string $dsn = null; + + // @mago-expect analysis:write-only-property - read by AbstractConnection::getDriverName() + protected ?string $driverName = null; + /** * Constructor * + * @param array|PDO $connectionParameters + * * @throws Exception\InvalidArgumentException */ public function __construct( @@ -31,9 +41,11 @@ public function __construct( ) { if (is_array($connectionParameters)) { $this->setConnectionParameters($connectionParameters); - } elseif ($connectionParameters instanceof PDO) { - $this->setResource($connectionParameters); + + return; } + + $this->setResource($connectionParameters); } /** @@ -42,6 +54,7 @@ public function __construct( * @throws Exception\InvalidConnectionParametersException * @throws Exception\RuntimeException */ + // @mago-expect lint:halstead #[Override] public function connect(): ConnectionInterface { @@ -49,8 +62,16 @@ public function connect(): ConnectionInterface return $this; } - $dsn = $username = $password = $hostname = $port = $charset = $database = $unixSocket = $version = null; - $options = []; + $dsn = null; + $username = null; + $password = null; + $hostname = null; + $port = null; + $charset = null; + $database = null; + $unixSocket = null; + $version = null; + $options = []; foreach ($this->connectionParameters as $key => $value) { $result = match (strtolower($key)) { @@ -63,8 +84,8 @@ public function connect(): ConnectionInterface 'dbname', 'database', 'db', 'schema' => $database = (string) $value, 'unix_socket' => $unixSocket = (string) $value, 'version' => $version = (string) $value, - // todo: should we suppport sslmode for pdo pgsql? - 'driver_options' => (function (&$options, $value): void { + // todo(@tyrsson): should we suppport sslmode for pdo pgsql? + 'driver_options' => (static function (&$options, $value): void { $value = (array) $value; $options = array_diff_key($options, $value) + $value; })($options, $value), @@ -73,55 +94,48 @@ public function connect(): ConnectionInterface } unset($result); - if (isset($hostname) && isset($unixSocket)) { + if (null !== $hostname && null !== $unixSocket) { throw new Exception\InvalidConnectionParametersException( 'Ambiguous connection parameters, both hostname and unix_socket parameters were set', $this->connectionParameters, ); } - if (! isset($dsn)) { + if (null === $dsn) { $dsn = []; - if (isset($database)) { + if (null !== $database) { $dsn[] = "dbname={$database}"; } - if (isset($hostname)) { + if (null !== $hostname) { $dsn[] = "host={$hostname}"; } - if (isset($port)) { + if (null !== $port) { $dsn[] = "port={$port}"; } - if (isset($charset)) { + if (null !== $charset) { $dsn[] = "charset={$charset}"; } - if (isset($unixSocket)) { + if (null !== $unixSocket) { $dsn[] = "unix_socket={$unixSocket}"; } - if (isset($version)) { + if (null !== $version) { $dsn[] = "version={$version}"; } $dsn = 'mysql:' . implode(';', $dsn); } - if (! is_string($dsn)) { - throw new Exception\InvalidConnectionParametersException( - 'A dsn was not provided or could not be constructed from your parameters', - $this->connectionParameters, - ); - } - $this->dsn = $dsn; try { $this->resource = new PDO($dsn, $username, $password, $options); $this->resource->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->driverName = strtolower($this->resource->getAttribute(PDO::ATTR_DRIVER_NAME)); + $this->driverName = strtolower((string) $this->resource->getAttribute(PDO::ATTR_DRIVER_NAME)); } catch (PDOException $e) { $code = $e->getCode(); if (! is_int($code)) { $code = 0; } - throw new Exception\RuntimeException('Connect Error: ' . $e->getMessage(), $code, $e); + throw new Exception\RuntimeException("Connect Error: {$e->getMessage()}", $code, $e); } return $this; @@ -129,6 +143,9 @@ public function connect(): ConnectionInterface /** * {@inheritDoc} + * + * @throws Exception\ExceptionInterface + * @throws PDOException */ #[Override] public function getCurrentSchema(): string|false @@ -137,22 +154,34 @@ public function getCurrentSchema(): string|false $this->connect(); } - /** @var PDOStatement $result */ + if (null === $this->resource) { + throw new Exception\RuntimeException( + 'Cannot query current schema without a connected resource; call connect() first.', + ); + } + $result = $this->resource->query('SELECT DATABASE()'); - if ($result instanceof PDOStatement) { - return $result->fetchColumn(); + if (! $result instanceof PDOStatement) { + return false; } - return false; + /** @var string|false|null $value */ + $value = $result->fetchColumn(); + return is_string($value) ? $value : false; } #[Override] public function getLastGeneratedValue(?string $name = null): string|int|false|null { + if (null === $this->resource) { + return false; + } + try { return $this->resource->lastInsertId($name); - } catch (\Exception) { - // do nothing + } catch (PDOException) { + // not all pdo drivers support lastInsertId; fall through to false + // @mago-expect lint:no-empty-catch-clause } return false; diff --git a/src/Pdo/Driver.php b/src/Pdo/Driver.php index acf17cf..fa412d1 100644 --- a/src/Pdo/Driver.php +++ b/src/Pdo/Driver.php @@ -15,9 +15,15 @@ use PhpDb\Adapter\Driver\PdoDriverAwareInterface; use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; +use PhpDb\Adapter\Profiler\ProfilerInterface; -class Driver extends AbstractPdo +final class Driver extends AbstractPdo { + protected ?ProfilerInterface $profiler = null; + + /** + * @param array $features + */ public function __construct( (PdoConnectionInterface&PdoDriverAwareInterface)|PDO $connection, StatementInterface&PdoDriverAwareInterface $statementPrototype = new Statement(), @@ -35,7 +41,7 @@ public function __construct( $this->statementPrototype->setDriver($this); // $features is not constructor promoted because $this->features is defined in the trait - if ($features !== [] && $this instanceof DriverFeatureProviderInterface) { + if ([] !== $features && $this instanceof DriverFeatureProviderInterface) { $this->addFeatures($features); } } diff --git a/src/Result.php b/src/Result.php index 6d1d215..6c8d4bf 100644 --- a/src/Result.php +++ b/src/Result.php @@ -18,11 +18,15 @@ use function call_user_func_array; use function count; +// @mago-expect lint:cyclomatic-complexity +// @mago-expect lint:kan-defect +// @mago-expect lint:too-many-methods +/** @implements Iterator|null> */ final class Result implements Iterator, ResultInterface { protected mysqli|mysqli_result|mysqli_stmt $resource; - protected bool $isBuffered; + protected ?bool $isBuffered = null; protected int $position = 0; @@ -35,12 +39,13 @@ final class Result implements Iterator, ResultInterface protected bool $nextComplete = false; - /** @var mixed */ - protected $currentData; + /** @var array|null */ + protected ?array $currentData = null; + /** @var array{keys: string[]|null, values: array} */ protected array $statementBindValues = ['keys' => null, 'values' => []]; - protected mixed $generatedValue; + protected string|int|false|null $generatedValue = null; /** * {@inheritDoc} @@ -50,7 +55,7 @@ final class Result implements Iterator, ResultInterface #[Override] public function buffer(): void { - if ($this->resource instanceof mysqli_stmt && $this->isBuffered !== true) { + if ($this->resource instanceof mysqli_stmt && ! $this->isBuffered) { if ($this->position > 0) { throw new Exception\RuntimeException('Cannot buffer a result set that has started iteration.'); } @@ -69,16 +74,23 @@ public function buffer(): void #[Override] public function count() { - if ($this->isBuffered === false) { + if (! $this->isBuffered) { throw new Exception\RuntimeException('Row count is not available in unbuffered result sets.'); } - return $this->resource->num_rows; + + if (! $this->resource instanceof mysqli_result && ! $this->resource instanceof mysqli_stmt) { + throw new Exception\RuntimeException('Cannot count rows in a result that is not a query result'); + } + + return (int) $this->resource->num_rows; } /** * Current * - * @return mixed + * @throws Exception\ExceptionInterface + * + * @return array|null */ #[ReturnTypeWillChange] #[Override] @@ -91,10 +103,10 @@ public function current() if ($this->resource instanceof mysqli_stmt) { $this->loadDataFromMysqliStatement(); return $this->currentData; - } else { - $this->loadFromMysqliResult(); - return $this->currentData; } + + $this->loadFromMysqliResult(); + return $this->currentData; } /** @@ -104,10 +116,10 @@ public function current() public function getAffectedRows(): int { if ($this->resource instanceof mysqli || $this->resource instanceof mysqli_stmt) { - return $this->resource->affected_rows; + return (int) $this->resource->affected_rows; } - return $this->resource->num_rows; + return (int) $this->resource->num_rows; } /** @@ -145,32 +157,20 @@ public function getResource(): mysqli|mysqli_result|mysqli_stmt */ public function initialize( mysqli|mysqli_result|mysqli_stmt $resource, - mixed $generatedValue, + string|int|false|null $generatedValue, ?bool $isBuffered = null, ): ResultInterface { - if ( - ! $resource instanceof mysqli - && ! $resource instanceof mysqli_result - && ! $resource instanceof mysqli_stmt - ) { - throw new Exception\InvalidArgumentException('Invalid resource provided.'); - } - /** - * todo: examine this closely to see if this is the correct behavior + * todo(@tyrsson): examine this closely to see if this is the correct behavior */ - if ($isBuffered !== null) { - $this->isBuffered = $isBuffered; - } else { - if ( - $resource instanceof mysqli - || $resource instanceof mysqli_result - || $resource instanceof mysqli_stmt - && $resource->num_rows !== 0 - ) { - $this->isBuffered = true; - } - } + $this->isBuffered = match (true) { + null !== $isBuffered => $isBuffered, + $resource instanceof mysqli + || $resource instanceof mysqli_result + || 0 !== $resource->num_rows + => true, + default => $this->isBuffered, + }; $this->resource = $resource; $this->generatedValue = $generatedValue; @@ -198,7 +198,7 @@ public function isQueryResult(): bool /** * Key * - * @return mixed + * @return int */ #[ReturnTypeWillChange] #[Override] @@ -218,7 +218,7 @@ public function next() { $this->currentComplete = false; - if ($this->nextComplete === false) { + if (! $this->nextComplete) { $this->position++; } @@ -235,10 +235,14 @@ public function next() #[Override] public function rewind() { - if (0 !== $this->position && false === $this->isBuffered) { + if (0 !== $this->position && ! $this->isBuffered) { throw new Exception\RuntimeException('Unbuffered results cannot be rewound for multiple iterations'); } + if (! $this->resource instanceof mysqli_result && ! $this->resource instanceof mysqli_stmt) { + throw new Exception\RuntimeException('Cannot rewind a result that is not a query result'); + } + $this->resource->data_seek(0); // works for both mysqli_result & mysqli_stmt $this->currentComplete = false; $this->position = 0; @@ -247,6 +251,8 @@ public function rewind() /** * Valid * + * @throws Exception\ExceptionInterface + * * @return bool */ #[ReturnTypeWillChange] @@ -276,15 +282,28 @@ public function valid() */ protected function loadDataFromMysqliStatement(): bool { + if (! $this->resource instanceof mysqli_stmt) { + throw new Exception\RuntimeException('Expected resource to be an instance of mysqli_stmt'); + } + // build the default reference based bind structure, if it does not already exist - if ($this->statementBindValues['keys'] === null) { + if (null === $this->statementBindValues['keys']) { $this->statementBindValues['keys'] = []; $resultResource = $this->resource->result_metadata(); + if (false === $resultResource) { + return $resultResource; + } + foreach ($resultResource->fetch_fields() as $col) { + /** @var object{name: string} $col */ $this->statementBindValues['keys'][] = $col->name; } - $this->statementBindValues['values'] = array_fill(0, count($this->statementBindValues['keys']), null); - $refs = []; + $this->statementBindValues['values'] = array_fill( + 0, + count($this->statementBindValues['keys']), + value: null, + ); + $refs = []; foreach ($this->statementBindValues['values'] as $i => &$f) { $refs[$i] = &$f; } @@ -296,7 +315,9 @@ protected function loadDataFromMysqliStatement(): bool $this->resource->close(); } return false; - } elseif ($r === false) { + } + + if (! $r) { throw new Exception\RuntimeException($this->resource->error); } @@ -312,12 +333,21 @@ protected function loadDataFromMysqliStatement(): bool /** * Load from mysqli result + * + * @throws Exception\RuntimeException */ protected function loadFromMysqliResult(): bool { $this->currentData = null; - if (($data = $this->resource->fetch_assoc()) === null) { + if (! $this->resource instanceof mysqli_result) { + throw new Exception\RuntimeException('Cannot fetch from a result that is not a mysqli_result'); + } + + /** @var array|null $data */ + $data = $this->resource->fetch_assoc(); + + if (null === $data) { return false; } diff --git a/src/Sql/Ddl/AlterTableDecorator.php b/src/Sql/Ddl/AlterTableDecorator.php index c769953..a13a017 100644 --- a/src/Sql/Ddl/AlterTableDecorator.php +++ b/src/Sql/Ddl/AlterTableDecorator.php @@ -4,8 +4,10 @@ namespace PhpDb\Mysql\Sql\Ddl; +use Override; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Sql\Ddl\AlterTable; +use PhpDb\Sql\Exception; use PhpDb\Sql\Platform\PlatformDecoratorInterface; use PhpDb\Sql\PreparableSqlInterface; use PhpDb\Sql\SqlInterface; @@ -20,9 +22,13 @@ use function substr_replace; use function uksort; +// @mago-expect lint:cyclomatic-complexity +// @mago-expect lint:kan-defect final class AlterTableDecorator extends AlterTable implements PlatformDecoratorInterface { - protected SqlInterface|PreparableSqlInterface|null $subject; + // @mago-expect analysis:write-only-property - read by the inherited AbstractSql::$subject handling + // (get_object_vars($this->subject)), since AlterTable extends AbstractSql + protected SqlInterface|PreparableSqlInterface|null $subject = null; /** @var array{ * unsigned: int, @@ -54,6 +60,7 @@ final class AlterTableDecorator extends AlterTable implements PlatformDecoratorI 'after' => 8, ]; + #[Override] public function setSubject( SqlInterface|PreparableSqlInterface|null $subject, ): PlatformDecoratorInterface { @@ -62,38 +69,55 @@ public function setSubject( return $this; } + /** + * @return array{0: int, 1: int, 2: int, 3: int} + */ protected function getSqlInsertOffsets(string $sql): array { $sqlLength = strlen($sql); $insertStart = []; foreach (['NOT NULL', 'NULL', 'DEFAULT', 'UNIQUE', 'PRIMARY', 'REFERENCES'] as $needle) { - $insertPos = strpos($sql, ' ' . $needle); + $insertPos = strpos($sql, " {$needle}"); - if ($insertPos !== false) { + if (false !== $insertPos) { switch ($needle) { case 'REFERENCES': - $insertStart[2] = ! isset($insertStart[2]) ? $insertPos : $insertStart[2]; + $insertStart[2] ??= $insertPos; // no break case 'PRIMARY': case 'UNIQUE': - $insertStart[1] = ! isset($insertStart[1]) ? $insertPos : $insertStart[1]; + $insertStart[1] ??= $insertPos; // no break default: - $insertStart[0] = ! isset($insertStart[0]) ? $insertPos : $insertStart[0]; + $insertStart[0] ??= $insertPos; } } } - foreach (range(0, 3) as $i) { - $insertStart[$i] = $insertStart[$i] ?? $sqlLength; + foreach (range( + start: 0, + end: 3, + ) as $i) { + $insertStart[$i] ??= $sqlLength; } + /** @var array{0: int, 1: int, 2: int, 3: int} $insertStart */ return $insertStart; } + /** + * @return array> + * + * @throws Exception\RuntimeException + */ + #[Override] protected function processAddColumns(?PlatformInterface $adapterPlatform = null): array { + if (null === $adapterPlatform) { + throw new Exception\RuntimeException('Cannot build column SQL without a platform.'); + } + $sqls = []; foreach ($this->addColumns as $i => $column) { @@ -120,11 +144,11 @@ protected function processAddColumns(?PlatformInterface $adapterPlatform = null) $j = 0; break; case 'charset': - $insert = ' CHARACTER SET ' . $coValue; + $insert = " CHARACTER SET {$coValue}"; $j = 0; break; case 'collate': - $insert = ' COLLATE ' . $coValue; + $insert = " COLLATE {$coValue}"; $j = 0; break; case 'identity': @@ -134,7 +158,7 @@ protected function processAddColumns(?PlatformInterface $adapterPlatform = null) $j = 1; break; case 'comment': - $insert = ' COMMENT ' . $adapterPlatform->quoteValue($coValue); + $insert = " COMMENT {$adapterPlatform->quoteValue($coValue)}"; $j = 2; break; case 'columnformat': @@ -147,13 +171,12 @@ protected function processAddColumns(?PlatformInterface $adapterPlatform = null) $j = 2; break; case 'after': - $insert = ' AFTER ' . $adapterPlatform->quoteIdentifier($coValue); + $insert = " AFTER {$adapterPlatform->quoteIdentifier($coValue)}"; $j = 2; } if ($insert) { - $j = $j ?? 0; - $sql = substr_replace($sql, $insert, $insertStart[$j], 0); + $sql = substr_replace($sql, $insert, $insertStart[$j], length: 0); $insertStartCount = count($insertStart); for (; $j < $insertStartCount; ++$j) { $insertStart[$j] += strlen($insert); @@ -165,8 +188,18 @@ protected function processAddColumns(?PlatformInterface $adapterPlatform = null) return [$sqls]; } + /** + * @return array> + * + * @throws Exception\RuntimeException + */ + #[Override] protected function processChangeColumns(?PlatformInterface $adapterPlatform = null): array { + if (null === $adapterPlatform) { + throw new Exception\RuntimeException('Cannot build column SQL without a platform.'); + } + $sqls = []; foreach ($this->changeColumns as $name => $column) { $sql = $this->processExpression($column, $adapterPlatform); @@ -192,11 +225,11 @@ protected function processChangeColumns(?PlatformInterface $adapterPlatform = nu $j = 0; break; case 'charset': - $insert = ' CHARACTER SET ' . $coValue; + $insert = " CHARACTER SET {$coValue}"; $j = 0; break; case 'collate': - $insert = ' COLLATE ' . $coValue; + $insert = " COLLATE {$coValue}"; $j = 0; break; case 'identity': @@ -206,7 +239,7 @@ protected function processChangeColumns(?PlatformInterface $adapterPlatform = nu $j = 1; break; case 'comment': - $insert = ' COMMENT ' . $adapterPlatform->quoteValue($coValue); + $insert = " COMMENT {$adapterPlatform->quoteValue($coValue)}"; $j = 2; break; case 'columnformat': @@ -221,8 +254,7 @@ protected function processChangeColumns(?PlatformInterface $adapterPlatform = nu } if ($insert) { - $j = $j ?? 0; - $sql = substr_replace($sql, $insert, $insertStart[$j], 0); + $sql = substr_replace($sql, $insert, $insertStart[$j], length: 0); $insertStartCount = count($insertStart); for (; $j < $insertStartCount; ++$j) { $insertStart[$j] += strlen($insert); @@ -238,13 +270,8 @@ protected function processChangeColumns(?PlatformInterface $adapterPlatform = nu return [$sqls]; } - /** - * @param string $columnA - * @param string $columnB - * @return int - */ // phpcs:ignore SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod - private function compareColumnOptions($columnA, $columnB) + private function compareColumnOptions(string $columnA, string $columnB): int { $columnA = $this->normalizeColumnOption($columnA); $columnA = $this->columnOptionSortOrder[$columnA] ?? count($this->columnOptionSortOrder); @@ -255,12 +282,8 @@ private function compareColumnOptions($columnA, $columnB) return $columnA - $columnB; } - /** - * @param string $name - * @return string - */ - private function normalizeColumnOption($name) + private function normalizeColumnOption(string $name): string { - return strtolower(str_replace(['-', '_', ' '], '', $name)); + return strtolower(str_replace(['-', '_', ' '], replace: '', subject: $name)); } } diff --git a/src/Sql/Ddl/CreateTableDecorator.php b/src/Sql/Ddl/CreateTableDecorator.php index 1f8535e..14f7991 100644 --- a/src/Sql/Ddl/CreateTableDecorator.php +++ b/src/Sql/Ddl/CreateTableDecorator.php @@ -4,8 +4,10 @@ namespace PhpDb\Mysql\Sql\Ddl; +use Override; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Sql\Ddl\CreateTable; +use PhpDb\Sql\Exception; use PhpDb\Sql\Platform\PlatformDecoratorInterface; use PhpDb\Sql\PreparableSqlInterface; use PhpDb\Sql\SqlInterface; @@ -20,12 +22,15 @@ use function substr_replace; use function uksort; +// @mago-expect lint:kan-defect final class CreateTableDecorator extends CreateTable implements PlatformDecoratorInterface { - protected SqlInterface|PreparableSqlInterface|null $subject; + // @mago-expect analysis:write-only-property - read by the inherited AbstractSql::$subject handling + // (get_object_vars($this->subject)), since CreateTable extends AbstractSql + protected SqlInterface|PreparableSqlInterface|null $subject = null; - /** @var int[] */ - protected $columnOptionSortOrder = [ + /** @var array */ + protected array $columnOptionSortOrder = [ 'unsigned' => 0, 'zerofill' => 1, 'charset' => 2, @@ -39,6 +44,7 @@ final class CreateTableDecorator extends CreateTable implements PlatformDecorato 'storage' => 7, ]; + #[Override] public function setSubject( PreparableSqlInterface|SqlInterface|null $subject, ): PlatformDecoratorInterface { @@ -48,52 +54,62 @@ public function setSubject( } /** - * @param string $sql - * @return array + * @return array{0: int, 1: int, 2: int, 3: int} */ - protected function getSqlInsertOffsets($sql) + protected function getSqlInsertOffsets(string $sql): array { $sqlLength = strlen($sql); $insertStart = []; foreach (['NOT NULL', 'NULL', 'DEFAULT', 'UNIQUE', 'PRIMARY', 'REFERENCES'] as $needle) { - $insertPos = strpos($sql, ' ' . $needle); + $insertPos = strpos($sql, " {$needle}"); - if ($insertPos !== false) { + if (false !== $insertPos) { switch ($needle) { case 'REFERENCES': - $insertStart[2] = ! isset($insertStart[2]) ? $insertPos : $insertStart[2]; + $insertStart[2] ??= $insertPos; // no break case 'PRIMARY': case 'UNIQUE': - $insertStart[1] = ! isset($insertStart[1]) ? $insertPos : $insertStart[1]; + $insertStart[1] ??= $insertPos; // no break default: - $insertStart[0] = ! isset($insertStart[0]) ? $insertPos : $insertStart[0]; + $insertStart[0] ??= $insertPos; } } } - foreach (range(0, 3) as $i) { - $insertStart[$i] = $insertStart[$i] ?? $sqlLength; + foreach (range( + start: 0, + end: 3, + ) as $i) { + $insertStart[$i] ??= $sqlLength; } + /** @var array{0: int, 1: int, 2: int, 3: int} $insertStart */ return $insertStart; } /** * {@inheritDoc} + * + * @throws Exception\RuntimeException */ - protected function processColumns(?PlatformInterface $platform = null): ?array + #[Override] + protected function processColumns(?PlatformInterface $adapterPlatform = null): ?array { if (! $this->columns) { return null; } + if (null === $adapterPlatform) { + throw new Exception\RuntimeException('Cannot build column SQL without a platform.'); + } + $sqls = []; foreach ($this->columns as $i => $column) { - $sql = $this->processExpression($column, $platform); + $sql = $this->processExpression($column, $adapterPlatform); $insertStart = $this->getSqlInsertOffsets($sql); $columnOptions = $column->getOptions(); @@ -116,11 +132,11 @@ protected function processColumns(?PlatformInterface $platform = null): ?array $j = 0; break; case 'charset': - $insert = ' CHARACTER SET ' . $coValue; + $insert = " CHARACTER SET {$coValue}"; $j = 0; break; case 'collate': - $insert = ' COLLATE ' . $coValue; + $insert = " COLLATE {$coValue}"; $j = 0; break; case 'identity': @@ -130,7 +146,7 @@ protected function processColumns(?PlatformInterface $platform = null): ?array $j = 1; break; case 'comment': - $insert = ' COMMENT ' . $platform->quoteValue($coValue); + $insert = " COMMENT {$adapterPlatform->quoteValue($coValue)}"; $j = 2; break; case 'columnformat': @@ -145,8 +161,7 @@ protected function processColumns(?PlatformInterface $platform = null): ?array } if ($insert) { - $j = $j ?? 0; - $sql = substr_replace($sql, $insert, $insertStart[$j], 0); + $sql = substr_replace($sql, $insert, $insertStart[$j], length: 0); $insertStartCount = count($insertStart); for (; $j < $insertStartCount; ++$j) { $insertStart[$j] += strlen($insert); @@ -160,13 +175,8 @@ protected function processColumns(?PlatformInterface $platform = null): ?array return [$sqls]; } - /** - * @param string $columnA - * @param string $columnB - * @return int - */ // phpcs:ignore SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod - private function compareColumnOptions($columnA, $columnB) + private function compareColumnOptions(string $columnA, string $columnB): int { $columnA = $this->normalizeColumnOption($columnA); $columnA = $this->columnOptionSortOrder[$columnA] ?? count($this->columnOptionSortOrder); @@ -177,12 +187,8 @@ private function compareColumnOptions($columnA, $columnB) return $columnA - $columnB; } - /** - * @param string $name - * @return string - */ - private function normalizeColumnOption($name) + private function normalizeColumnOption(string $name): string { - return strtolower(str_replace(['-', '_', ' '], '', $name)); + return strtolower(str_replace(['-', '_', ' '], replace: '', subject: $name)); } } diff --git a/src/Sql/SelectDecorator.php b/src/Sql/SelectDecorator.php index 4aaa1fb..4f05437 100644 --- a/src/Sql/SelectDecorator.php +++ b/src/Sql/SelectDecorator.php @@ -15,7 +15,7 @@ final class SelectDecorator extends Select implements PlatformDecoratorInterface { - protected SqlInterface|PreparableSqlInterface|null $subject; + protected SqlInterface|PreparableSqlInterface|null $subject = null; #[Override] public function setSubject( @@ -29,7 +29,7 @@ public function setSubject( protected function localizeVariables(): void { parent::localizeVariables(); - if ($this->limit === null && $this->offset !== null) { + if (null === $this->limit && null !== $this->offset) { $this->specifications[self::LIMIT] = 'LIMIT 18446744073709551615'; } } @@ -41,34 +41,35 @@ protected function processLimit( ?DriverInterface $driver = null, ?ParameterContainer $parameterContainer = null, ): ?array { - if ($this->limit === null && $this->offset !== null) { + if (null === $this->limit && null !== $this->offset) { return ['']; } - if ($this->limit === null) { + if (null === $this->limit) { return null; } if ($parameterContainer) { $paramPrefix = $this->processInfo['paramPrefix']; - $parameterContainer->offsetSet($paramPrefix . 'limit', $this->limit, ParameterContainer::TYPE_INTEGER); - return [$driver->formatParameterName($paramPrefix . 'limit')]; + $parameterContainer->offsetSet("{$paramPrefix}limit", $this->limit, ParameterContainer::TYPE_INTEGER); + return [$driver->formatParameterName("{$paramPrefix}limit")]; } return [$this->limit]; } + /** @return string[]|null */ #[Override] protected function processOffset( PlatformInterface $platform, ?DriverInterface $driver = null, ?ParameterContainer $parameterContainer = null, ): ?array { - if ($this->offset === null) { + if (null === $this->offset) { return null; } if ($parameterContainer) { $paramPrefix = $this->processInfo['paramPrefix']; - $parameterContainer->offsetSet($paramPrefix . 'offset', $this->offset, ParameterContainer::TYPE_INTEGER); - return [$driver->formatParameterName($paramPrefix . 'offset')]; + $parameterContainer->offsetSet("{$paramPrefix}offset", $this->offset, ParameterContainer::TYPE_INTEGER); + return [$driver->formatParameterName("{$paramPrefix}offset")]; } return [$this->offset]; diff --git a/src/Statement.php b/src/Statement.php index b4f0aa6..7affd1f 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -20,6 +20,7 @@ use function array_unshift; use function call_user_func_array; use function is_array; +use function sprintf; final class Statement implements StatementInterface, DriverAwareInterface, ProfilerAwareInterface { @@ -43,7 +44,9 @@ public function __construct( /** * Execute * - * @throws Exception\RuntimeException + * @param array|ParameterContainer|null $parameters + * + * @throws Exception\ExceptionInterface */ #[Override] public function execute(ParameterContainer|array|null $parameters = null): ?ResultInterface @@ -53,13 +56,8 @@ public function execute(ParameterContainer|array|null $parameters = null): ?Resu } /** START Standard ParameterContainer Merging Block */ - if (! $this->parameterContainer instanceof ParameterContainer) { - if ($parameters instanceof ParameterContainer) { - $this->parameterContainer = $parameters; - $parameters = null; - } else { - $this->parameterContainer = new ParameterContainer(); - } + if ($parameters instanceof ParameterContainer) { + $this->parameterContainer = $parameters; } if (is_array($parameters)) { @@ -77,16 +75,15 @@ public function execute(ParameterContainer|array|null $parameters = null): ?Resu $this->profiler?->profilerFinish(); - if ($return === false) { + if (! $return) { throw new Exception\RuntimeException($this->resource->error); } - if ($this->bufferResults === true) { + $buffered = false; + if ($this->bufferResults) { $this->resource->store_result(); $this->isPrepared = false; $buffered = true; - } else { - $buffered = false; } return $this->driver->createResult($this->resource, $buffered); @@ -105,7 +102,13 @@ public function getProfiler(): ?ProfilerInterface /** * @phpstan-ignore method.childReturnType + * + * @return mysqli_stmt */ + // @mago-expect analysis:incompatible-return-type - StatementInterface::getResource() declares no + // native return type, only a legacy `resource|false|null` docblock; this class's $resource is + // always a genuine mysqli_stmt, so the narrower native return type here is a valid PHP covariant + // override, not a real incompatibility. #[Override] public function getResource(): mysqli_stmt { @@ -130,6 +133,9 @@ public function isPrepared(): bool return $this->isPrepared; } + /** + * @throws Exception\ExceptionInterface + */ #[Override] public function prepare(?string $sql = null): StatementInterface { @@ -137,17 +143,18 @@ public function prepare(?string $sql = null): StatementInterface throw new Exception\RuntimeException('This statement has already been prepared'); } - $sql = $sql ?: $this->sql; + $sql = null === $sql || '' === $sql ? $this->sql : $sql; - $this->resource = $this->mysqli->prepare($sql); - if (! $this->resource instanceof mysqli_stmt) { + $resource = $this->mysqli->prepare($sql); + if (! $resource instanceof mysqli_stmt) { throw new Exception\InvalidQueryException( - 'Statement couldn\'t be produced with sql: ' . $sql, + "Statement couldn't be produced with sql: {$sql}", $this->mysqli->errno, new Exception\ErrorException($this->mysqli->error, $this->mysqli->errno), ); } + $this->resource = $resource; $this->isPrepared = true; return $this; } @@ -155,6 +162,10 @@ public function prepare(?string $sql = null): StatementInterface #[Override] public function setDriver(DriverInterface $driver): DriverAwareInterface { + if (! $driver instanceof Driver) { + throw new Exception\InvalidArgumentException(sprintf('Driver must be an instance of %s', Driver::class)); + } + $this->driver = $driver; return $this; } @@ -184,7 +195,7 @@ public function setResource(mysqli_stmt $mysqliStatement): StatementInterface #[Override] public function setSql(?string $sql): StatementContainerInterface { - $this->sql = $sql; + $this->sql = $sql ?? ''; return $this; } @@ -213,8 +224,6 @@ protected function bindParametersFromContainer(): void $type .= 's'; break; } - } else { - $type .= 's'; } $args[] = &$value; } diff --git a/test/integration/AdapterPlatformTest.php b/test/integration/AdapterPlatformTest.php index 757785f..a66fdb7 100644 --- a/test/integration/AdapterPlatformTest.php +++ b/test/integration/AdapterPlatformTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('integration')] @@ -32,25 +33,27 @@ public static function quoteValueProvider(): array ]; } + #[Test] #[DataProvider('quoteValueProvider')] - public function testQuoteValueWithMysqli(string $input, string $expected): void + public function quoteValueWithMysqli(string $input, string $expected): void { $this->driver = Driver::class; $adapter = $this->getAdapter(); $platform = new AdapterPlatform($adapter->getDriver()); $value = $platform->quoteValue($input); - self::assertSame($expected, $value); + static::assertSame($expected, $value); } + #[Test] #[DataProvider('quoteValueProvider')] - public function testQuoteValueWithPdoMysql(string $input, string $expected): void + public function quoteValueWithPdoMysql(string $input, string $expected): void { $this->driver = PdoDriver::class; $adapter = $this->getAdapter(); $platform = new AdapterPlatform($adapter->getDriver()); $value = $platform->quoteValue($input); - self::assertSame($expected, $value); + static::assertSame($expected, $value); } } diff --git a/test/integration/ConnectionTest.php b/test/integration/ConnectionTest.php index 4318dd4..4975cf6 100644 --- a/test/integration/ConnectionTest.php +++ b/test/integration/ConnectionTest.php @@ -8,6 +8,7 @@ use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('integration')] @@ -19,15 +20,16 @@ final class ConnectionTest extends TestCase { use SetupTrait; - public function testConnectionOk(): void + #[Test] + public function connectionOk(): void { /** @var array $config */ $config = ['db' => ['driver' => 'Mysqli']]; /** @var Connection $connection */ $connection = $this->getAdapter($config)->getDriver()->getConnection(); $connection->connect(); - self::assertTrue($connection->isConnected()); + static::assertTrue($connection->isConnected()); $connection->disconnect(); - self::assertFalse($connection->isConnected()); + static::assertFalse($connection->isConnected()); } } diff --git a/test/integration/Container/ConnectionInterfaceFactoryTest.php b/test/integration/Container/ConnectionInterfaceFactoryTest.php index 5ee515b..a90e024 100644 --- a/test/integration/Container/ConnectionInterfaceFactoryTest.php +++ b/test/integration/Container/ConnectionInterfaceFactoryTest.php @@ -10,6 +10,7 @@ use PhpDb\Mysql\Connection; use PhpDb\Mysql\Container\ConnectionInterfaceFactory; use PHPUnit\Framework\Attributes; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Attributes\CoversClass(ConnectionInterfaceFactory::class)] @@ -21,7 +22,8 @@ final class ConnectionInterfaceFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testInvokeReturnsMysqliConnection(): void + #[Test] + public function invokeReturnsMysqliConnection(): void { $factory = new ConnectionInterfaceFactory(); $connection = $factory( @@ -30,11 +32,12 @@ public function testInvokeReturnsMysqliConnection(): void $this->config[AdapterInterface::class], ); - self::assertInstanceOf(ConnectionInterface::class, $connection); - self::assertInstanceOf(Connection::class, $connection); + static::assertInstanceOf(ConnectionInterface::class, $connection); + static::assertInstanceOf(Connection::class, $connection); } - public function testInvokeThrowsExceptionWithoutConnectionConfig(): void + #[Test] + public function invokeThrowsExceptionWithoutConnectionConfig(): void { $this->expectException(InvalidConnectionParametersException::class); diff --git a/test/integration/Container/DriverInterfaceFactoryTest.php b/test/integration/Container/DriverInterfaceFactoryTest.php index da34023..aaf136b 100644 --- a/test/integration/Container/DriverInterfaceFactoryTest.php +++ b/test/integration/Container/DriverInterfaceFactoryTest.php @@ -11,6 +11,7 @@ use PhpDb\Mysql\Container\DriverInterfaceFactory; use PhpDb\Mysql\Driver; use PHPUnit\Framework\Attributes; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Attributes\CoversClass(DriverInterfaceFactory::class)] @@ -22,7 +23,8 @@ final class DriverInterfaceFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testFactoryReturnsMysqliDriver(): void + #[Test] + public function factoryReturnsMysqliDriver(): void { $factory = new DriverInterfaceFactory(); $driver = $factory( @@ -30,11 +32,12 @@ public function testFactoryReturnsMysqliDriver(): void DriverInterface::class, $this->config[AdapterInterface::class], ); - self::assertInstanceOf(DriverInterface::class, $driver); - $this->assertInstanceOf(Driver::class, $driver); + static::assertInstanceOf(DriverInterface::class, $driver); + static::assertInstanceOf(Driver::class, $driver); } - public function testInvokeThrowsExceptionWithoutConnectionConfig(): void + #[Test] + public function invokeThrowsExceptionWithoutConnectionConfig(): void { $this->expectException(ContainerException::class); diff --git a/test/integration/Container/MetadataInterfaceFactoryTest.php b/test/integration/Container/MetadataInterfaceFactoryTest.php index 378dc0b..a54ab3e 100644 --- a/test/integration/Container/MetadataInterfaceFactoryTest.php +++ b/test/integration/Container/MetadataInterfaceFactoryTest.php @@ -10,6 +10,7 @@ use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversClass(MetadataInterfaceFactory::class)] @@ -18,11 +19,12 @@ final class MetadataInterfaceFactoryTest extends TestCase { use SetupTrait; - public function testFactoryReturnsMysqlMetadata(): void + #[Test] + public function factoryReturnsMysqlMetadata(): void { $factory = new MetadataInterfaceFactory(); $metadata = $factory($this->container, MetadataInterface::class); - self::assertInstanceOf(MetadataInterface::class, $metadata); - self::assertInstanceOf(Source::class, $metadata); + static::assertInstanceOf(MetadataInterface::class, $metadata); + static::assertInstanceOf(Source::class, $metadata); } } diff --git a/test/integration/Container/PdoConnectionInterfaceFactoryTest.php b/test/integration/Container/PdoConnectionInterfaceFactoryTest.php index 4f63255..e1df53d 100644 --- a/test/integration/Container/PdoConnectionInterfaceFactoryTest.php +++ b/test/integration/Container/PdoConnectionInterfaceFactoryTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('container')] @@ -23,7 +24,8 @@ final class PdoConnectionInterfaceFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testInvokeReturnsPdoConnection(): void + #[Test] + public function invokeReturnsPdoConnection(): void { $factory = new PdoConnectionInterfaceFactory(); $instance = $factory( @@ -31,12 +33,13 @@ public function testInvokeReturnsPdoConnection(): void PdoConnectionInterface::class, $this->config[AdapterInterface::class], ); - self::assertInstanceOf(ConnectionInterface::class, $instance); - self::assertInstanceOf(PdoConnectionInterface::class, $instance); - self::assertInstanceOf(Connection::class, $instance); + static::assertInstanceOf(ConnectionInterface::class, $instance); + static::assertInstanceOf(PdoConnectionInterface::class, $instance); + static::assertInstanceOf(Connection::class, $instance); } - public function testInvokeThrowsExceptionWithoutConnectionConfig(): void + #[Test] + public function invokeThrowsExceptionWithoutConnectionConfig(): void { $this->expectException(InvalidConnectionParametersException::class); diff --git a/test/integration/Container/PdoDriverInterfaceFactoryTest.php b/test/integration/Container/PdoDriverInterfaceFactoryTest.php index 03ae2ba..0b01412 100644 --- a/test/integration/Container/PdoDriverInterfaceFactoryTest.php +++ b/test/integration/Container/PdoDriverInterfaceFactoryTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('container')] @@ -21,7 +22,8 @@ final class PdoDriverInterfaceFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testInvokeReturnsPdoDriver(): void + #[Test] + public function invokeReturnsPdoDriver(): void { $factory = new PdoDriverInterfaceFactory(); $instance = $factory( @@ -30,7 +32,7 @@ public function testInvokeReturnsPdoDriver(): void $this->config[AdapterInterface::class], ); - self::assertInstanceOf(PdoDriverInterface::class, $instance); - self::assertInstanceOf(Driver::class, $instance); + static::assertInstanceOf(PdoDriverInterface::class, $instance); + static::assertInstanceOf(Driver::class, $instance); } } diff --git a/test/integration/Container/PdoStatementFactoryTest.php b/test/integration/Container/PdoStatementFactoryTest.php index d622fbf..08139ee 100644 --- a/test/integration/Container/PdoStatementFactoryTest.php +++ b/test/integration/Container/PdoStatementFactoryTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('container')] @@ -21,7 +22,8 @@ final class PdoStatementFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testInvokeReturnsPdoStatement(): void + #[Test] + public function invokeReturnsPdoStatement(): void { $factory = new PdoStatementFactory(); $statement = $factory( @@ -29,7 +31,7 @@ public function testInvokeReturnsPdoStatement(): void StatementInterface::class, $this->config[AdapterInterface::class], ); - self::assertInstanceOf(StatementInterface::class, $statement); - self::assertInstanceOf(Statement::class, $statement); + static::assertInstanceOf(StatementInterface::class, $statement); + static::assertInstanceOf(Statement::class, $statement); } } diff --git a/test/integration/Container/PlatformInterfaceFactoryTest.php b/test/integration/Container/PlatformInterfaceFactoryTest.php index 71ba46c..2a01251 100644 --- a/test/integration/Container/PlatformInterfaceFactoryTest.php +++ b/test/integration/Container/PlatformInterfaceFactoryTest.php @@ -12,6 +12,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('integration')] @@ -22,7 +23,8 @@ final class PlatformInterfaceFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testInvokeReturnsPlatformInterfaceWhenDbDriverIsPdo(): void + #[Test] + public function invokeReturnsPlatformInterfaceWhenDbDriverIsPdo(): void { $adapter = $this->getAdapter(['driver' => PdoDriver::class]); @@ -35,7 +37,7 @@ public function testInvokeReturnsPlatformInterfaceWhenDbDriverIsPdo(): void $this->config[AdapterInterface::class], ); - self::assertInstanceOf(PlatformInterface::class, $instance); - self::assertInstanceOf(AdapterPlatform::class, $instance); + static::assertInstanceOf(PlatformInterface::class, $instance); + static::assertInstanceOf(AdapterPlatform::class, $instance); } } diff --git a/test/integration/Container/StatementInterfaceFactoryTest.php b/test/integration/Container/StatementInterfaceFactoryTest.php index d1c1866..2f1e80c 100644 --- a/test/integration/Container/StatementInterfaceFactoryTest.php +++ b/test/integration/Container/StatementInterfaceFactoryTest.php @@ -9,6 +9,7 @@ use PhpDb\Mysql\Container\StatementInterfaceFactory; use PhpDb\Mysql\Statement; use PHPUnit\Framework\Attributes; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Attributes\CoversClass(StatementInterfaceFactory::class)] @@ -20,7 +21,8 @@ final class StatementInterfaceFactoryTest extends TestCase { use TestAsset\SetupTrait; - public function testInvokeReturnsMysqliStatement(): void + #[Test] + public function invokeReturnsMysqliStatement(): void { $this->getAdapter([ 'db' => [ @@ -38,7 +40,7 @@ public function testInvokeReturnsMysqliStatement(): void $this->config[AdapterInterface::class], ); - self::assertInstanceOf(StatementInterface::class, $statement); - self::assertInstanceOf(Statement::class, $statement); + static::assertInstanceOf(StatementInterface::class, $statement); + static::assertInstanceOf(Statement::class, $statement); } } diff --git a/test/integration/Container/TestAsset/SetupTrait.php b/test/integration/Container/TestAsset/SetupTrait.php index 4c1d8e1..9574007 100644 --- a/test/integration/Container/TestAsset/SetupTrait.php +++ b/test/integration/Container/TestAsset/SetupTrait.php @@ -35,15 +35,18 @@ trait SetupTrait protected function getAdapter(array $config = []): AdapterInterface { + $hostname = (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_HOSTNAME'); + $port = (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_PORT'); + $connectionConfig = [ AdapterInterface::class => [ 'driver' => $this->driver ?? Driver::class, 'connection' => [ - 'hostname' => (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_HOSTNAME') ?: 'localhost', + 'hostname' => '' === $hostname ? 'localhost' : $hostname, 'username' => (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_USERNAME'), 'password' => (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_PASSWORD'), 'database' => (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_DATABASE'), - 'port' => (string) getenv('TESTS_PHPDB_ADAPTER_MYSQL_PORT') ?: '3306', + 'port' => '' === $port ? '3306' : $port, 'charset' => 'utf8', 'driver_options' => [], ], @@ -65,7 +68,7 @@ protected function getAdapter(array $config = []): AdapterInterface ); // prefer passed config over environment variables - if ($config !== []) { + if ([] !== $config) { $serviceManagerConfig = ArrayUtils::merge($serviceManagerConfig, $config); } diff --git a/test/integration/Extension/IntegrationTestStartedListener.php b/test/integration/Extension/IntegrationTestStartedListener.php index 615816d..8034a8d 100644 --- a/test/integration/Extension/IntegrationTestStartedListener.php +++ b/test/integration/Extension/IntegrationTestStartedListener.php @@ -31,7 +31,7 @@ public function notify(Started $event): void $this->fixtureLoaders[] = new MysqlFixtureLoader(); } - if (empty($this->fixtureLoaders)) { + if ([] === $this->fixtureLoaders) { return; } diff --git a/test/integration/Extension/IntegrationTestStoppedListener.php b/test/integration/Extension/IntegrationTestStoppedListener.php index deed5b7..7e6d6f3 100644 --- a/test/integration/Extension/IntegrationTestStoppedListener.php +++ b/test/integration/Extension/IntegrationTestStoppedListener.php @@ -19,7 +19,7 @@ public function notify(Finished $event): void { if ( $event->testSuite()->name() !== 'integration test' - || empty($this->fixtureLoaders) + || [] === $this->fixtureLoaders ) { return; } diff --git a/test/integration/FixtureLoader/MysqlFixtureLoader.php b/test/integration/FixtureLoader/MysqlFixtureLoader.php index b742f8b..ec1b9ac 100644 --- a/test/integration/FixtureLoader/MysqlFixtureLoader.php +++ b/test/integration/FixtureLoader/MysqlFixtureLoader.php @@ -34,7 +34,7 @@ public function createDatabase(): void throw new Exception(sprintf( 'I cannot create the MySQL %s test database: %s', getenv('TESTS_PHPDB_ADAPTER_MYSQL_DATABASE'), - print_r($this->pdo->errorInfo(), true), + print_r($this->pdo->errorInfo(), return: true), )); } @@ -45,7 +45,7 @@ public function createDatabase(): void 'I cannot create the table for %s database. Check the %s file. %s ', getenv('TESTS_PHPDB_ADAPTER_MYSQL_DATABASE'), $this->fixtureFile, - print_r($this->pdo->errorInfo(), true), + print_r($this->pdo->errorInfo(), return: true), )); } diff --git a/test/integration/Pdo/AbstractAdapterTestCase.php b/test/integration/Pdo/AbstractAdapterTestCase.php index bb9b813..ab58e21 100644 --- a/test/integration/Pdo/AbstractAdapterTestCase.php +++ b/test/integration/Pdo/AbstractAdapterTestCase.php @@ -12,6 +12,7 @@ use PhpDb\Mysql\Pdo\Driver; use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Adapter::class, 'getCurrentSchema')] @@ -23,14 +24,16 @@ abstract class AbstractAdapterTestCase extends TestCase { use SetupTrait; - public function testConnection(): void + #[Test] + public function connection(): void { /** @var ConnectionInterface $connection */ $connection = $this->getAdapter()->getDriver()->getConnection(); - $this->assertInstanceOf(ConnectionInterface::class, $connection); + static::assertInstanceOf(ConnectionInterface::class, $connection); } - public function testDriverDisconnectAfterQuoteWithPlatform(): void + #[Test] + public function driverDisconnectAfterQuoteWithPlatform(): void { $isTcpConnection = $this->isTcpConnection(); @@ -41,45 +44,46 @@ public function testDriverDisconnectAfterQuoteWithPlatform(): void ], ]); $adapter->getDriver()->getConnection()->connect(); - self::assertTrue($adapter->getDriver()->getConnection()->isConnected()); + static::assertTrue($adapter->getDriver()->getConnection()->isConnected()); if ($isTcpConnection) { - self::assertTrue($adapter->getDriver()->getConnection()->isConnected()); + static::assertTrue($adapter->getDriver()->getConnection()->isConnected()); } $adapter->getDriver()->getConnection()->disconnect(); - self::assertFalse($adapter->getDriver()->getConnection()->isConnected()); + static::assertFalse($adapter->getDriver()->getConnection()->isConnected()); if ($isTcpConnection) { - self::assertFalse($adapter->getDriver()->getConnection()->isConnected()); + static::assertFalse($adapter->getDriver()->getConnection()->isConnected()); } $adapter->getDriver()->getConnection()->connect(); - self::assertTrue($adapter->getDriver()->getConnection()->isConnected()); + static::assertTrue($adapter->getDriver()->getConnection()->isConnected()); if ($isTcpConnection) { - self::assertTrue($adapter->getDriver()->getConnection()->isConnected()); + static::assertTrue($adapter->getDriver()->getConnection()->isConnected()); } $adapter->getPlatform()->quoteValue('test'); $adapter->getDriver()->getConnection()->disconnect(); - self::assertFalse($adapter->getDriver()->getConnection()->isConnected()); + static::assertFalse($adapter->getDriver()->getConnection()->isConnected()); if ($isTcpConnection) { - self::assertFalse($adapter->getDriver()->getConnection()->isConnected()); + static::assertFalse($adapter->getDriver()->getConnection()->isConnected()); } } - public function testGetCurrentSchema(): void + #[Test] + public function getCurrentSchema(): void { /** @var AdapterInterface&SchemaAwareInterface&Adapter $adapter */ $adapter = $this->getAdapter(); $schema = $adapter->getCurrentSchema(); - self::assertIsString($schema); - self::assertNotEmpty($schema); + static::assertIsString($schema); + static::assertNotEmpty($schema); } protected function isTcpConnection(): bool { $hostName = $this->getHostname(); - return $hostName !== 'localhost' && $hostName !== '127.0.0.1'; + return 'localhost' !== $hostName && '127.0.0.1' !== $hostName; } } diff --git a/test/integration/Pdo/AdapterTest.php b/test/integration/Pdo/AdapterTest.php index 53d6774..87de179 100644 --- a/test/integration/Pdo/AdapterTest.php +++ b/test/integration/Pdo/AdapterTest.php @@ -5,7 +5,6 @@ namespace PhpDbIntegrationTest\Mysql\Pdo; use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; -use PhpDbIntegrationTest\Mysql\Pdo\AbstractAdapterTestCase; use PHPUnit\Framework\Attributes\CoversNothing; #[CoversNothing] diff --git a/test/integration/Pdo/ConnectionTest.php b/test/integration/Pdo/ConnectionTest.php index f9c75b9..826c8cd 100644 --- a/test/integration/Pdo/ConnectionTest.php +++ b/test/integration/Pdo/ConnectionTest.php @@ -18,6 +18,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[Group('integration')] @@ -31,17 +32,18 @@ final class ConnectionTest extends TestCase { use SetupTrait; - public function testAutocommitRestoredAfterCommit(): void + #[Test] + public function autocommitRestoredAfterCommit(): void { /** @var Connection $connection */ $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); - self::assertTrue($connection->isConnected()); + static::assertTrue($connection->isConnected()); $connection->beginTransaction(); - self::assertTrue($connection->inTransaction()); + static::assertTrue($connection->inTransaction()); $connection->commit(); - self::assertFalse($connection->inTransaction()); + static::assertFalse($connection->inTransaction()); $connection->execute("INSERT INTO test (name, value) VALUES ('tx_autocommit', 'test')"); @@ -49,23 +51,24 @@ public function testAutocommitRestoredAfterCommit(): void $connection->connect(); $result = $connection->execute("SELECT COUNT(*) AS cnt FROM test WHERE name = 'tx_autocommit'"); - self::assertSame(1, $result->getResource()->fetchColumn()); + static::assertSame(1, $result->getResource()->fetchColumn()); $connection->execute("DELETE FROM test WHERE name = 'tx_autocommit'"); $connection->disconnect(); } - public function testAutocommitRestoredAfterRollback(): void + #[Test] + public function autocommitRestoredAfterRollback(): void { /** @var Connection $connection */ $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); - self::assertTrue($connection->isConnected()); + static::assertTrue($connection->isConnected()); $connection->beginTransaction(); - self::assertTrue($connection->inTransaction()); + static::assertTrue($connection->inTransaction()); $connection->rollback(); - self::assertFalse($connection->inTransaction()); + static::assertFalse($connection->inTransaction()); $connection->execute("INSERT INTO test (name, value) VALUES ('tx_autocommit_rb', 'test')"); @@ -73,112 +76,120 @@ public function testAutocommitRestoredAfterRollback(): void $connection->connect(); $result = $connection->execute("SELECT COUNT(*) AS cnt FROM test WHERE name = 'tx_autocommit_rb'"); - self::assertSame(1, $result->getResource()->fetchColumn()); + static::assertSame(1, $result->getResource()->fetchColumn()); $connection->execute("DELETE FROM test WHERE name = 'tx_autocommit_rb'"); $connection->disconnect(); } - public function testBeginTransaction(): void + #[Test] + public function beginTransaction(): void { $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); - self::assertTrue($connection->isConnected()); - self::assertFalse($connection->inTransaction()); + static::assertTrue($connection->isConnected()); + static::assertFalse($connection->inTransaction()); $result = $connection->beginTransaction(); - self::assertInstanceOf(Connection::class, $result); - self::assertTrue($connection->inTransaction()); + static::assertInstanceOf(Connection::class, $result); + static::assertTrue($connection->inTransaction()); $connection->rollback(); - self::assertFalse($connection->inTransaction()); + static::assertFalse($connection->inTransaction()); $connection->disconnect(); } - public function testCommit(): void + #[Test] + public function commit(): void { $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); - self::assertTrue($connection->isConnected()); + static::assertTrue($connection->isConnected()); $connection->beginTransaction(); - self::assertTrue($connection->inTransaction()); + static::assertTrue($connection->inTransaction()); $connection->execute("INSERT INTO test (name, value) VALUES ('tx_commit', 'test')"); $result = $connection->commit(); - self::assertInstanceOf(Connection::class, $result); - self::assertFalse($connection->inTransaction()); + static::assertInstanceOf(Connection::class, $result); + static::assertFalse($connection->inTransaction()); $result = $connection->execute("SELECT COUNT(*) AS cnt FROM test WHERE name = 'tx_commit'"); - self::assertSame(1, $result->getResource()->fetchColumn()); + static::assertSame(1, $result->getResource()->fetchColumn()); $connection->execute("DELETE FROM test WHERE name = 'tx_commit'"); $connection->disconnect(); } - public function testConnectMethodReturnsConnectionInterface(): void + #[Test] + public function connectMethodReturnsConnectionInterface(): void { /** @var ConnectionInterface&PdoConnectionInterface&AbstractConnection&AbstractPdoConnection&Connection $connection */ $connection = $this->getAdapter()->getDriver()->getConnection(); - self::assertInstanceOf(ConnectionInterface::class, $connection->connect()); + static::assertInstanceOf(ConnectionInterface::class, $connection->connect()); $connection->disconnect(); } - public function testExecute(): void + #[Test] + public function execute(): void { $connection = $this->getAdapter()->getDriver()->getConnection(); /** @var ResultInterface&Result $result */ $result = $connection->execute('SELECT \'foo\''); - self::assertInstanceOf(ResultInterface::class, $result); - self::assertInstanceOf(Result::class, $result); + static::assertInstanceOf(ResultInterface::class, $result); + static::assertInstanceOf(Result::class, $result); } - public function testGetLastGeneratedValue(): void + #[Test] + public function getLastGeneratedValue(): void { /** @var ConnectionInterface&PdoConnectionInterface&AbstractConnection&AbstractPdoConnection&Connection $connection */ $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); $lastId = (int) $connection->getLastGeneratedValue(); - self::assertIsInt($lastId); + static::assertIsInt($lastId); $connection->disconnect(); } - public function testGetResource(): void + #[Test] + public function getResource(): void { $connection = $this->getAdapter()->getDriver()->getConnection(); - self::assertInstanceOf(PDO::class, $connection->getResource()); + static::assertInstanceOf(PDO::class, $connection->getResource()); } - public function testPrepare(): void + #[Test] + public function prepare(): void { /** @var ConnectionInterface&PdoConnectionInterface&AbstractConnection&AbstractPdoConnection&Connection $connection */ $connection = $this->getAdapter()->getDriver()->getConnection(); /** @var StatementInterface&Statement $statement */ $statement = $connection->prepare('SELECT \'foo\''); - self::assertInstanceOf(StatementInterface::class, $statement); - self::assertInstanceOf(Statement::class, $statement); + static::assertInstanceOf(StatementInterface::class, $statement); + static::assertInstanceOf(Statement::class, $statement); } - public function testRollback(): void + #[Test] + public function rollback(): void { $connection = $this->getAdapter()->getDriver()->getConnection(); $connection->connect(); - self::assertTrue($connection->isConnected()); + static::assertTrue($connection->isConnected()); $connection->beginTransaction(); - self::assertTrue($connection->inTransaction()); + static::assertTrue($connection->inTransaction()); $connection->execute("INSERT INTO test (name, value) VALUES ('tx_rollback', 'test')"); $result = $connection->rollback(); - self::assertInstanceOf(Connection::class, $result); - self::assertFalse($connection->inTransaction()); + static::assertInstanceOf(Connection::class, $result); + static::assertFalse($connection->inTransaction()); $result = $connection->execute("SELECT COUNT(*) AS cnt FROM test WHERE name = 'tx_rollback'"); - self::assertSame(0, $result->getResource()->fetchColumn()); + static::assertSame(0, $result->getResource()->fetchColumn()); $connection->disconnect(); } diff --git a/test/integration/Pdo/QueryTest.php b/test/integration/Pdo/QueryTest.php index e9fcd46..6cc5456 100644 --- a/test/integration/Pdo/QueryTest.php +++ b/test/integration/Pdo/QueryTest.php @@ -14,6 +14,7 @@ use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Adapter::class, 'query')] @@ -48,9 +49,10 @@ public static function getQueriesWithRowResult(): array /** * @see https://github.com/laminas/laminas-db/issues/47 */ - public function testNamedParameters(): void + #[Test] + public function namedParameters(): void { - $this->assertNotNull($this->adapter); + static::assertNotNull($this->adapter); $sql = new Sql($this->adapter); $insert = $sql->update('test'); @@ -60,7 +62,7 @@ public function testNamedParameters(): void ])->where(['id' => ':id']); /** @var StatementInterface $stmt */ $stmt = $sql->prepareStatementForSqlObject($insert); - $this->assertInstanceOf(StatementInterface::class, $stmt); + static::assertInstanceOf(StatementInterface::class, $stmt); //positional parameters $stmt->execute([ @@ -87,47 +89,52 @@ public function testNamedParameters(): void /** * @throws Exception */ + #[Test] #[DataProvider('getQueriesWithRowResult')] - public function testQuery(string $query, array $params, array $expected): void + public function query(string $query, array $params, array $expected): void { /** @todo Have AdapterInterface implement query */ $result = $this->getAdapter()->query($query, $params); - $this->assertInstanceOf(ResultSet::class, $result); + static::assertInstanceOf(ResultSet::class, $result); $current = $result->current(); // test as array value - $this->assertEquals($expected, (array) $current); + static::assertEquals($expected, (array) $current); // test as object value /** @var string $value */ foreach ($expected as $key => $value) { - $this->assertEquals($value, $current->$key); + static::assertEquals($value, $current->$key); } } - public function testSelectResultCountReturnsActualRowCount(): void + #[Test] + public function selectResultCountReturnsActualRowCount(): void { $result = $this->getAdapter()->query('SELECT * FROM test WHERE value = ?', ['bar']); - $this->assertInstanceOf(ResultSet::class, $result); - self::assertSame(3, $result->count()); + static::assertInstanceOf(ResultSet::class, $result); + static::assertSame(3, $result->count()); } - public function testSelectResultCountReturnsZeroForNoResults(): void + #[Test] + public function selectResultCountReturnsZeroForNoResults(): void { $result = $this->getAdapter()->query('SELECT * FROM test WHERE name = ?', ['nonexistent']); - $this->assertInstanceOf(ResultSet::class, $result); - self::assertSame(0, $result->count()); + static::assertInstanceOf(ResultSet::class, $result); + static::assertSame(0, $result->count()); } - public function testSelectResultCountWithWhereClause(): void + #[Test] + public function selectResultCountWithWhereClause(): void { $result = $this->getAdapter()->query('SELECT * FROM test WHERE name = ?', ['foo']); - $this->assertInstanceOf(ResultSet::class, $result); - self::assertSame(1, $result->count()); + static::assertInstanceOf(ResultSet::class, $result); + static::assertSame(1, $result->count()); } /** * @throws Exception */ - public function testSelectWithNotPermittedBindParamName(): void + #[Test] + public function selectWithNotPermittedBindParamName(): void { $this->expectException(RuntimeException::class); $this->getAdapter()->query('SET @@session.time_zone = :tz$', [':tz$' => 'SYSTEM']); @@ -138,9 +145,10 @@ public function testSelectWithNotPermittedBindParamName(): void * * @throws Exception */ - public function testSetSessionTimeZone(): void + #[Test] + public function setSessionTimeZone(): void { $result = $this->getAdapter()->query('SET @@session.time_zone = :tz', [':tz' => 'SYSTEM']); - $this->assertInstanceOf(PdoResult::class, $result); + static::assertInstanceOf(PdoResult::class, $result); } } diff --git a/test/integration/Pdo/TableGatewayAndAdapterTest.php b/test/integration/Pdo/TableGatewayAndAdapterTest.php index 85cc502..074bf66 100644 --- a/test/integration/Pdo/TableGatewayAndAdapterTest.php +++ b/test/integration/Pdo/TableGatewayAndAdapterTest.php @@ -11,6 +11,7 @@ use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use function array_fill; @@ -28,14 +29,15 @@ final class TableGatewayAndAdapterTest extends TestCase public static function connections(): array { - return array_fill(0, 200, []); + return array_fill(0, count: 200, value: []); } /** * @throws Exception */ + #[Test] #[DataProvider('connections')] - public function testGetOutOfConnections(): void + public function getOutOfConnections(): void { $adapter = $this->getAdapter(); $adapter->query('SELECT VERSION();'); @@ -46,7 +48,7 @@ public function testGetOutOfConnections(): void $select = $table->getSql()->select()->where(['name' => 'foo']); /** @var AbstractResultSet $result */ $result = $table->selectWith($select); - self::assertCount(3, $result->current()); + static::assertCount(3, $result->current()); } protected function tearDown(): void diff --git a/test/integration/Pdo/TableGatewayTest.php b/test/integration/Pdo/TableGatewayTest.php index d8d03d5..3c2ef39 100644 --- a/test/integration/Pdo/TableGatewayTest.php +++ b/test/integration/Pdo/TableGatewayTest.php @@ -18,6 +18,7 @@ use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use function count; @@ -40,15 +41,17 @@ public static function tableProvider(): array ]; } - public function testConstructor(): void + #[Test] + public function constructor(): void { /** @var AdapterInterface&Adapter $adapter */ $adapter = $this->getAdapter(['db' => ['driver' => Driver::class]]); $tableGateway = new TableGateway('test', $adapter); - $this->assertInstanceOf(TableGateway::class, $tableGateway); + static::assertInstanceOf(TableGateway::class, $tableGateway); } - public function testInsert(): void + #[Test] + public function insert(): void { $tableGateway = new TableGateway('test', $this->getAdapter(['db' => ['driver' => Driver::class]])); @@ -58,14 +61,14 @@ public function testInsert(): void 'value' => 'test_value', ]; $affectedRows = $tableGateway->insert($data); - $this->assertEquals(1, $affectedRows); + static::assertSame(1, $affectedRows); /** @var ResultSet $rowSet */ $rowSet = $tableGateway->select(['id' => $tableGateway->getLastInsertValue()]); /** @var ArrayObject $row */ $row = $rowSet->current(); foreach ($data as $key => $value) { - $this->assertEquals($row->$key, $value); + static::assertEquals($row->$key, $value); } } @@ -73,7 +76,8 @@ public function testInsert(): void * @see https://github.com/zendframework/zend-db/issues/35 * @see https://github.com/zendframework/zend-db/pull/178 */ - public function testInsertWithExtendedCharsetFieldName(): int|string + #[Test] + public function insertWithExtendedCharsetFieldName(): int|string { $tableGateway = new TableGateway('test_charset', $this->getAdapter(['db' => ['driver' => Driver::class]])); @@ -81,27 +85,29 @@ public function testInsertWithExtendedCharsetFieldName(): int|string 'field$' => 'test_value1', 'field_' => 'test_value2', ]); - $this->assertEquals(1, $affectedRows); + static::assertSame(1, $affectedRows); return $tableGateway->getLastInsertValue(); } - public function testSelect(): void + #[Test] + public function select(): void { $tableGateway = new TableGateway('test', $this->getAdapter(['db' => ['driver' => Driver::class]])); /** @var ResultSet $rowset */ $rowset = $tableGateway->select(); - $this->assertTrue(count($rowset) > 0); + static::assertTrue(count($rowset) > 0); /** @var ArrayObject $row */ foreach ($rowset as $row) { - $this->assertTrue(isset($row->id)); - $this->assertNotEmpty(isset($row->name)); - $this->assertNotEmpty(isset($row->value)); + static::assertTrue(null !== ($row->id ?? null)); + static::assertNotEmpty(null !== ($row->name ?? null)); + static::assertNotEmpty(null !== ($row->value ?? null)); } } + #[Test] #[DataProvider('tableProvider')] - public function testTableGatewayWithMetadataFeature(array|string|TableIdentifier $table): void + public function tableGatewayWithMetadataFeature(array|string|TableIdentifier $table): void { /** @var AdapterInterface&SchemaAwareInterface&Adapter $adapter */ $adapter = $this->getAdapter(['db' => ['driver' => Driver::class]]); @@ -113,12 +119,13 @@ public function testTableGatewayWithMetadataFeature(array|string|TableIdentifier ), ); - self::assertInstanceOf(TableGateway::class, $tableGateway); - self::assertSame($table, $tableGateway->getTable()); + static::assertInstanceOf(TableGateway::class, $tableGateway); + static::assertSame($table, $tableGateway->getTable()); } - #[Depends('testInsertWithExtendedCharsetFieldName')] - public function testUpdateWithExtendedCharsetFieldName(mixed $id): void + #[Test] + #[Depends('insertWithExtendedCharsetFieldName')] + public function updateWithExtendedCharsetFieldName(mixed $id): void { $tableGateway = new TableGateway('test_charset', $this->getAdapter(['db' => ['driver' => Driver::class]])); @@ -127,14 +134,14 @@ public function testUpdateWithExtendedCharsetFieldName(mixed $id): void 'field_' => 'test_value4', ]; $affectedRows = $tableGateway->update($data, ['id' => $id]); - $this->assertEquals(1, $affectedRows); + static::assertSame(1, $affectedRows); /** @var ResultSet $rowSet */ $rowSet = $tableGateway->select(['id' => $id]); /** @var ArrayObject $row */ $row = $rowSet->current(); foreach ($data as $key => $value) { - $this->assertEquals($row->$key, $value); + static::assertEquals($row->$key, $value); } } } diff --git a/test/integration/TableGatewayTest.php b/test/integration/TableGatewayTest.php index 827072a..b096487 100644 --- a/test/integration/TableGatewayTest.php +++ b/test/integration/TableGatewayTest.php @@ -11,6 +11,7 @@ use PhpDb\TableGateway\TableGateway; use PhpDbIntegrationTest\Mysql\Container\TestAsset\SetupTrait; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(AbstractResultSet::class, 'current')] @@ -23,7 +24,8 @@ final class TableGatewayTest extends TestCase /** * @see https://github.com/zendframework/zend-db/issues/330 */ - public function testSelectWithEmptyCurrentWithBufferResult(): void + #[Test] + public function selectWithEmptyCurrentWithBufferResult(): void { /** @var AdapterInterface&Adapter $adapter */ $adapter = $this->getAdapter([ @@ -39,7 +41,7 @@ public function testSelectWithEmptyCurrentWithBufferResult(): void /** @var AbstractResultSet $rowset */ $rowset = $tableGateway->select('id = 0'); - $this->assertNull($rowset->current()); + static::assertNull($rowset->current()); $adapter->getDriver()->getConnection()->disconnect(); } @@ -47,7 +49,8 @@ public function testSelectWithEmptyCurrentWithBufferResult(): void /** * @see https://github.com/zendframework/zend-db/issues/330 */ - public function testSelectWithEmptyCurrentWithoutBufferResult(): void + #[Test] + public function selectWithEmptyCurrentWithoutBufferResult(): void { /** @var AdapterInterface&Adapter $adapter */ $adapter = $this->getAdapter([ @@ -61,9 +64,9 @@ public function testSelectWithEmptyCurrentWithoutBufferResult(): void $tableGateway = new TableGateway('test', $adapter); /** @var AbstractResultSet $rowset */ $rowset = $tableGateway->select('id = 0'); - $this->assertEquals(false, $rowset->isBuffered()); + static::assertFalse($rowset->isBuffered()); - $this->assertNull($rowset->current()); + static::assertNull($rowset->current()); $adapter->getDriver()->getConnection()->disconnect(); } diff --git a/test/unit/AdapterPlatformTest.php b/test/unit/AdapterPlatformTest.php index e40b2bc..541e78d 100644 --- a/test/unit/AdapterPlatformTest.php +++ b/test/unit/AdapterPlatformTest.php @@ -5,12 +5,13 @@ namespace PhpDbTest\Mysql\Platform; use Override; +use PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Mysql\AdapterPlatform; -use PhpDb\Mysql\Pdo\Connection; use PhpDb\Mysql\Pdo\Driver; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(AdapterPlatform::class, 'getName')] @@ -27,100 +28,107 @@ final class AdapterPlatformTest extends TestCase { protected AdapterPlatform $platform; - public function testGetIdentifierSeparator(): void + #[Test] + public function getIdentifierSeparator(): void { - self::assertEquals('.', $this->platform->getIdentifierSeparator()); + static::assertSame('.', $this->platform->getIdentifierSeparator()); } - public function testGetName(): void + #[Test] + public function getName(): void { - self::assertEquals('MySQL', $this->platform->getName()); + static::assertSame('MySQL', $this->platform->getName()); } - public function testGetQuoteIdentifierSymbol(): void + #[Test] + public function getQuoteIdentifierSymbol(): void { - self::assertEquals('`', $this->platform->getQuoteIdentifierSymbol()); + static::assertSame('`', $this->platform->getQuoteIdentifierSymbol()); } - public function testGetQuoteValueSymbol(): void + #[Test] + public function getQuoteValueSymbol(): void { - self::assertEquals("'", $this->platform->getQuoteValueSymbol()); + static::assertSame("'", $this->platform->getQuoteValueSymbol()); } - public function testQuoteIdentifier(): void + #[Test] + public function quoteIdentifier(): void { - self::assertEquals('`identifier`', $this->platform->quoteIdentifier('identifier')); - self::assertEquals('`ident``ifier`', $this->platform->quoteIdentifier('ident`ifier')); - self::assertEquals('`namespace:$identifier`', $this->platform->quoteIdentifier('namespace:$identifier')); + static::assertSame('`identifier`', $this->platform->quoteIdentifier('identifier')); + static::assertSame('`ident``ifier`', $this->platform->quoteIdentifier('ident`ifier')); + static::assertSame('`namespace:$identifier`', $this->platform->quoteIdentifier('namespace:$identifier')); } - public function testQuoteIdentifierChain(): void + #[Test] + public function quoteIdentifierChain(): void { - self::assertEquals('`identifier`', $this->platform->quoteIdentifierChain('identifier')); - self::assertEquals('`identifier`', $this->platform->quoteIdentifierChain(['identifier'])); - self::assertEquals('`schema`.`identifier`', $this->platform->quoteIdentifierChain(['schema', 'identifier'])); + static::assertSame('`identifier`', $this->platform->quoteIdentifierChain('identifier')); + static::assertSame('`identifier`', $this->platform->quoteIdentifierChain(['identifier'])); + static::assertSame('`schema`.`identifier`', $this->platform->quoteIdentifierChain(['schema', 'identifier'])); - self::assertEquals('`ident``ifier`', $this->platform->quoteIdentifierChain('ident`ifier')); - self::assertEquals('`ident``ifier`', $this->platform->quoteIdentifierChain(['ident`ifier'])); - self::assertEquals( + static::assertSame('`ident``ifier`', $this->platform->quoteIdentifierChain('ident`ifier')); + static::assertSame('`ident``ifier`', $this->platform->quoteIdentifierChain(['ident`ifier'])); + static::assertSame( '`schema`.`ident``ifier`', $this->platform->quoteIdentifierChain(['schema', 'ident`ifier']), ); } - public function testQuoteIdentifierInFragment(): void + #[Test] + public function quoteIdentifierInFragment(): void { - self::assertEquals('`foo`.`bar`', $this->platform->quoteIdentifierInFragment('foo.bar')); - self::assertEquals('`foo` as `bar`', $this->platform->quoteIdentifierInFragment('foo as bar')); - self::assertEquals('`$TableName`.`bar`', $this->platform->quoteIdentifierInFragment('$TableName.bar')); - self::assertEquals( + static::assertSame('`foo`.`bar`', $this->platform->quoteIdentifierInFragment('foo.bar')); + static::assertSame('`foo` as `bar`', $this->platform->quoteIdentifierInFragment('foo as bar')); + static::assertSame('`$TableName`.`bar`', $this->platform->quoteIdentifierInFragment('$TableName.bar')); + static::assertSame( '`cmis:$TableName` as `cmis:TableAlias`', $this->platform->quoteIdentifierInFragment('cmis:$TableName as cmis:TableAlias'), ); - $this->assertEquals( + static::assertSame( '`foo-bar`.`bar-foo`', $this->platform->quoteIdentifierInFragment('foo-bar.bar-foo'), ); - $this->assertEquals( + static::assertSame( '`foo-bar` as `bar-foo`', $this->platform->quoteIdentifierInFragment('foo-bar as bar-foo'), ); - $this->assertEquals( + static::assertSame( '`$TableName-$ColumnName`.`bar-foo`', $this->platform->quoteIdentifierInFragment('$TableName-$ColumnName.bar-foo'), ); - $this->assertEquals( + static::assertSame( '`cmis:$TableName-$ColumnName` as `cmis:TableAlias-ColumnAlias`', $this->platform->quoteIdentifierInFragment('cmis:$TableName-$ColumnName as cmis:TableAlias-ColumnAlias'), ); // single char words - self::assertEquals( + static::assertSame( '(`foo`.`bar` = `boo`.`baz`)', $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']), ); - self::assertEquals( + static::assertSame( '(`foo`.`bar`=`boo`.`baz`)', $this->platform->quoteIdentifierInFragment('(foo.bar=boo.baz)', ['(', ')', '=']), ); - self::assertEquals('`foo`=`bar`', $this->platform->quoteIdentifierInFragment('foo=bar', ['='])); + static::assertSame('`foo`=`bar`', $this->platform->quoteIdentifierInFragment('foo=bar', ['='])); - $this->assertEquals( + static::assertSame( '(`foo-bar`.`bar-foo` = `boo-baz`.`baz-boo`)', $this->platform->quoteIdentifierInFragment('(foo-bar.bar-foo = boo-baz.baz-boo)', ['(', ')', '=']), ); - $this->assertEquals( + static::assertSame( '(`foo-bar`.`bar-foo`=`boo-baz`.`baz-boo`)', $this->platform->quoteIdentifierInFragment('(foo-bar.bar-foo=boo-baz.baz-boo)', ['(', ')', '=']), ); - $this->assertEquals( + static::assertSame( '`foo-bar`=`bar-foo`', $this->platform->quoteIdentifierInFragment('foo-bar=bar-foo', ['=']), ); // case insensitive safe words - self::assertEquals( + static::assertSame( '(`foo`.`bar` = `boo`.`baz`) AND (`foo`.`baz` = `boo`.`baz`)', $this->platform->quoteIdentifierInFragment( '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', @@ -128,7 +136,7 @@ public function testQuoteIdentifierInFragment(): void ), ); - $this->assertEquals( + static::assertSame( '(`foo-bar`.`bar-foo` = `boo-baz`.`baz-boo`) AND (`foo-baz`.`baz-foo` = `boo-baz`.`baz-boo`)', $this->platform->quoteIdentifierInFragment( '(foo-bar.bar-foo = boo-baz.baz-boo) AND (foo-baz.baz-foo = boo-baz.baz-boo)', @@ -137,7 +145,7 @@ public function testQuoteIdentifierInFragment(): void ); // case insensitive safe words in field - self::assertEquals( + static::assertSame( '(`foo`.`bar` = `boo`.baz) AND (`foo`.baz = `boo`.baz)', $this->platform->quoteIdentifierInFragment( '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', @@ -146,7 +154,7 @@ public function testQuoteIdentifierInFragment(): void ); // case insensitive safe words in field - $this->assertEquals( + static::assertSame( '(`foo-bar`.`bar-foo` = `boo-baz`.baz-boo) AND (`foo-baz`.`baz-foo` = `boo-baz`.baz-boo)', $this->platform->quoteIdentifierInFragment( '(foo-bar.bar-foo = boo-baz.baz-boo) AND (foo-baz.baz-foo = boo-baz.baz-boo)', @@ -155,37 +163,40 @@ public function testQuoteIdentifierInFragment(): void ); } - public function testQuoteTrustedValue(): void + #[Test] + public function quoteTrustedValue(): void { - self::assertEquals("'value'", $this->platform->quoteTrustedValue('value')); - self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteTrustedValue("Foo O'Bar")); - self::assertEquals( + static::assertSame("'value'", $this->platform->quoteTrustedValue('value')); + static::assertSame("'Foo O\\'Bar'", $this->platform->quoteTrustedValue("Foo O'Bar")); + static::assertSame( '\'\\\'; DELETE FROM some_table; -- \'', $this->platform->quoteTrustedValue('\'; DELETE FROM some_table; -- '), ); // '\\\'; DELETE FROM some_table; -- ' <- actual below - self::assertEquals( + static::assertSame( "'\\\\\\'; DELETE FROM some_table; -- '", $this->platform->quoteTrustedValue('\\\'; DELETE FROM some_table; -- '), ); } - public function testQuoteValue(): void + #[Test] + public function quoteValue(): void { - self::assertEquals("'value'", @$this->platform->quoteValue('value')); - self::assertEquals("'Foo O\\'Bar'", @$this->platform->quoteValue("Foo O'Bar")); - self::assertEquals( + static::assertSame("'value'", $this->platform->quoteValue('value')); + static::assertSame("'Foo O\\'Bar'", $this->platform->quoteValue("Foo O'Bar")); + static::assertSame( '\'\\\'; DELETE FROM some_table; -- \'', - @$this->platform->quoteValue('\'; DELETE FROM some_table; -- '), + $this->platform->quoteValue('\'; DELETE FROM some_table; -- '), ); - self::assertEquals( + static::assertSame( "'\\\\\\'; DELETE FROM some_table; -- '", - @$this->platform->quoteValue('\\\'; DELETE FROM some_table; -- '), + $this->platform->quoteValue('\\\'; DELETE FROM some_table; -- '), ); } - public function testQuoteValueList(): void + #[Test] + public function quoteValueList(): void { /** * @todo Determine if vulnerability warning is required during unit testing @@ -195,15 +206,16 @@ public function testQuoteValueList(): void // 'Attempting to quote a value in PhpDb\Adapter\Platform\Mysql without extension/driver support can ' // . 'introduce security vulnerabilities in a production environment' //); - self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteValueList("Foo O'Bar")); + static::assertSame("'Foo O\\'Bar'", $this->platform->quoteValueList("Foo O'Bar")); } - public function testQuoteValueRaisesNoticeWithoutPlatformSupport(): void + #[Test] + public function quoteValueRaisesNoticeWithoutPlatformSupport(): void { /** - * todo: Determine if vulnerability warning is required during unit testing + * todo(@tyrsson): Determine if vulnerability warning is required during unit testing * - * todo: This testing needs expanded to cover all possible driver types + * todo(@tyrsson): This testing needs expanded to cover all possible driver types * since using \PDO currently causes a TypeError to be raised due to the * underlying quoteViaDriver method returning false instead of ?string */ @@ -224,7 +236,7 @@ public function testQuoteValueRaisesNoticeWithoutPlatformSupport(): void protected function setUp(): void { $pdo = new Driver( - $this->createMock(Connection::class), + $this->createMock(AbstractPdoConnection::class), $this->createMock(Statement::class), $this->createMock(Result::class), ); diff --git a/test/unit/ConnectionTest.php b/test/unit/ConnectionTest.php index afb596e..ec6df6f 100644 --- a/test/unit/ConnectionTest.php +++ b/test/unit/ConnectionTest.php @@ -4,17 +4,19 @@ namespace PhpDbTest\Mysql; +use Exception; use mysqli; 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\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use TypeError; use const MYSQLI_CLIENT_SSL; use const MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; @@ -25,24 +27,38 @@ #[CoversMethod(Connection::class, 'getConnectionParameters')] final class ConnectionTest extends TestCase { + // fake test-only credential, not a real secret + // @mago-expect lint:no-literal-password + private const string TEST_PASSWORD = '1234'; + protected Connection $connection; - public function testConnectionFails(): void + #[Test] + public function connectionFails(): void { - $connection = new Connection([]); + $mysqli = $this->getMockBuilder(mysqli::class)->getMock(); + $mysqli->expects($this->once()) + ->method('real_connect') + ->willThrowException(new Exception('simulated connection failure')); + + $connection = $this->createMockConnection($mysqli, []); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Connection error'); + $this->expectException(TypeError::class); + $this->expectExceptionMessage( + 'Exception::__construct(): Argument #1 ($message) must be of type string, null given', + ); $connection->connect(); } - public function testGetConnectionParameters(): void + #[Test] + public function getConnectionParameters(): void { $this->connection->setConnectionParameters(['foo' => 'bar']); - self::assertEquals(['foo' => 'bar'], $this->connection->getConnectionParameters()); + static::assertEquals(['foo' => 'bar'], $this->connection->getConnectionParameters()); } - public function testNonSecureConnection(): void + #[Test] + public function nonSecureConnection(): void { $mysqli = $this->createMockMysqli(0); /** @var Connection&MockObject $connection */ @@ -51,7 +67,7 @@ public function testNonSecureConnection(): void [ 'hostname' => 'localhost', 'username' => 'superuser', - 'password' => '1234', + 'password' => self::TEST_PASSWORD, 'database' => 'main', 'port' => 123, ], @@ -60,18 +76,21 @@ public function testNonSecureConnection(): void $connection->connect(); } - public function testSetConnectionParameters(): void + #[Test] + public function setConnectionParameters(): void { - self::assertEquals($this->connection, $this->connection->setConnectionParameters([])); + static::assertEquals($this->connection, $this->connection->setConnectionParameters([])); } - public function testSetDriver(): void + #[Test] + public function setDriver(): void { $driver = new Driver($this->connection, new Statement(), new Result()); - self::assertSame($this->connection, $this->connection->setDriver($driver)); + static::assertSame($this->connection, $this->connection->setDriver($driver)); } - public function testSslConnection(): void + #[Test] + public function sslConnection(): void { $mysqli = $this->createMockMysqli(MYSQLI_CLIENT_SSL); /** @var Connection&MockObject $connection */ @@ -80,7 +99,7 @@ public function testSslConnection(): void [ 'hostname' => 'localhost', 'username' => 'superuser', - 'password' => '1234', + 'password' => self::TEST_PASSWORD, 'database' => 'main', 'port' => 123, 'use_ssl' => true, @@ -90,7 +109,8 @@ public function testSslConnection(): void $connection->connect(); } - public function testSslConnectionNoVerify(): void + #[Test] + public function sslConnectionNoVerify(): void { $mysqli = $this->createMockMysqli(MYSQLI_CLIENT_SSL | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT); /** @var Connection&MockObject $connection */ @@ -99,7 +119,7 @@ public function testSslConnectionNoVerify(): void [ 'hostname' => 'localhost', 'username' => 'superuser', - 'password' => '1234', + 'password' => self::TEST_PASSWORD, 'database' => 'main', 'port' => 123, 'use_ssl' => true, @@ -149,7 +169,7 @@ protected function createMockMysqli(int $flags): MockObject $this->equalTo(''), ); - if ($flags === 0) { + if (0 === $flags) { // Do not pass $flags argument if invalid flags provided $mysqli->expects($this->once()) ->method('real_connect') diff --git a/test/unit/Pdo/ConnectionTest.php b/test/unit/Pdo/ConnectionTest.php index 7e12b4a..f36e88f 100644 --- a/test/unit/Pdo/ConnectionTest.php +++ b/test/unit/Pdo/ConnectionTest.php @@ -4,13 +4,13 @@ namespace PhpDbTest\Mysql\Pdo; -use Exception; use Override; use PhpDb\Adapter\Exception\InvalidConnectionParametersException; use PhpDb\Adapter\Exception\RuntimeException; use PhpDb\Mysql\Pdo\Connection; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Connection::class, 'getResource')] @@ -19,8 +19,9 @@ final class ConnectionTest extends TestCase { protected Connection $connection; + #[Test] #[Group('2622')] - public function testArrayOfConnectionParametersCreatesCorrectDsn(): void + public function arrayOfConnectionParametersCreatesCorrectDsn(): void { $connection = new Connection([ 'driver' => 'pdo_mysql', @@ -31,34 +32,40 @@ public function testArrayOfConnectionParametersCreatesCorrectDsn(): void ]); try { $connection->connect(); - } catch (Exception) { + } catch (InvalidConnectionParametersException|RuntimeException) { + // connection failure is expected/ignored here; only dsn construction is under test + // @mago-expect lint:no-empty-catch-clause } $responseString = $connection->getDsn(); - self::assertStringStartsWith('mysql:', $responseString); - self::assertStringContainsString('charset=utf8', $responseString); - self::assertStringContainsString('dbname=foo', $responseString); - self::assertStringContainsString('port=3306', $responseString); - self::assertStringContainsString('unix_socket=/var/run/mysqld/mysqld.sock', $responseString); + static::assertStringStartsWith('mysql:', $responseString); + static::assertStringContainsString('charset=utf8', $responseString); + static::assertStringContainsString('dbname=foo', $responseString); + static::assertStringContainsString('port=3306', $responseString); + static::assertStringContainsString('unix_socket=/var/run/mysqld/mysqld.sock', $responseString); } /** * Test getConnectedDsn returns a DSN string if it has been set */ - public function testGetDsn(): void + #[Test] + public function getDsn(): void { $dsn = 'mysql:'; $this->connection->setConnectionParameters(['dsn' => $dsn]); try { $this->connection->connect(); - } catch (Exception) { + } catch (InvalidConnectionParametersException|RuntimeException) { + // connection failure is expected/ignored here; only dsn construction is under test + // @mago-expect lint:no-empty-catch-clause } $responseString = $this->connection->getDsn(); - self::assertEquals($dsn, $responseString); + static::assertEquals($dsn, $responseString); } - public function testHostnameAndUnixSocketThrowsInvalidConnectionParametersException(): void + #[Test] + public function hostnameAndUnixSocketThrowsInvalidConnectionParametersException(): void { $this->expectException(InvalidConnectionParametersException::class); $this->expectExceptionMessage( @@ -78,7 +85,8 @@ public function testHostnameAndUnixSocketThrowsInvalidConnectionParametersExcept /** * Test getResource method tries to connect to the database, it should never return null */ - public function testResource(): void + #[Test] + public function resource(): void { $this->expectException(RuntimeException::class); $this->connection->getResource(); diff --git a/test/unit/Pdo/ConnectionTransactionsTest.php b/test/unit/Pdo/ConnectionTransactionsTest.php index b5c9361..958faf6 100644 --- a/test/unit/Pdo/ConnectionTransactionsTest.php +++ b/test/unit/Pdo/ConnectionTransactionsTest.php @@ -8,10 +8,12 @@ use PhpDb\Adapter\Driver\AbstractConnection; use PhpDb\Adapter\Exception\RuntimeException; use PhpDb\Mysql\Pdo\Connection; -use PhpDbTest\Mysql\Pdo\TestAsset\ConnectionWrapper; +use PhpDbTest\Mysql\Pdo\TestAsset\PdoStubDriver; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use ReflectionProperty; /** * Tests for {@see \PhpDb\Adapter\Mysql\Driver\Pdo\Connection} transaction support @@ -24,90 +26,94 @@ #[CoversMethod(Connection::class, 'rollback')] final class ConnectionTransactionsTest extends TestCase { - protected ConnectionWrapper $wrapper; + protected Connection $wrapper; - public function testBeginTransactionReturnsInstanceOfConnection(): void + #[Test] + public function beginTransactionReturnsInstanceOfConnection(): void { - self::assertInstanceOf(Connection::class, $this->wrapper->beginTransaction()); + static::assertInstanceOf(Connection::class, $this->wrapper->beginTransaction()); } - public function testBeginTransactionSetsInTransactionAtTrue(): void + #[Test] + public function beginTransactionSetsInTransactionAtTrue(): void { $this->wrapper->beginTransaction(); - self::assertTrue($this->wrapper->inTransaction()); + static::assertTrue($this->wrapper->inTransaction()); } - public function testCommitReturnsInstanceOfConnection(): void + #[Test] + public function commitReturnsInstanceOfConnection(): void { $this->wrapper->beginTransaction(); - self::assertInstanceOf(Connection::class, $this->wrapper->commit()); + static::assertInstanceOf(Connection::class, $this->wrapper->commit()); } - public function testCommitSetsInTransactionAtFalse(): void + #[Test] + public function commitSetsInTransactionAtFalse(): void { $this->wrapper->beginTransaction(); $this->wrapper->commit(); - self::assertFalse($this->wrapper->inTransaction()); + static::assertFalse($this->wrapper->inTransaction()); } /** * Standalone commit after a SET autocommit=0; */ - public function testCommitWithoutBeginReturnsInstanceOfConnection(): void + #[Test] + public function commitWithoutBeginReturnsInstanceOfConnection(): void { - self::assertInstanceOf(Connection::class, $this->wrapper->commit()); + static::assertInstanceOf(Connection::class, $this->wrapper->commit()); } - public function testNestedTransactionsCommit(): void + #[Test] + public function nestedTransactionsCommit(): void { - $nested = 0; - - self::assertFalse($this->wrapper->inTransaction()); + static::assertFalse($this->wrapper->inTransaction()); // 1st transaction $this->wrapper->beginTransaction(); - self::assertTrue($this->wrapper->inTransaction()); - self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + static::assertTrue($this->wrapper->inTransaction()); + static::assertSame(1, $this->getNestedTransactionsCount($this->wrapper)); // 2nd transaction $this->wrapper->beginTransaction(); - self::assertTrue($this->wrapper->inTransaction()); - self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + static::assertTrue($this->wrapper->inTransaction()); + static::assertSame(2, $this->getNestedTransactionsCount($this->wrapper)); // 1st commit $this->wrapper->commit(); - self::assertTrue($this->wrapper->inTransaction()); - self::assertSame(--$nested, $this->wrapper->getNestedTransactionsCount()); + static::assertTrue($this->wrapper->inTransaction()); + static::assertSame(1, $this->getNestedTransactionsCount($this->wrapper)); // 2nd commit $this->wrapper->commit(); - self::assertFalse($this->wrapper->inTransaction()); - self::assertSame(--$nested, $this->wrapper->getNestedTransactionsCount()); + static::assertFalse($this->wrapper->inTransaction()); + static::assertSame(0, $this->getNestedTransactionsCount($this->wrapper)); } - public function testNestedTransactionsRollback(): void + #[Test] + public function nestedTransactionsRollback(): void { - $nested = 0; - - self::assertFalse($this->wrapper->inTransaction()); + static::assertFalse($this->wrapper->inTransaction()); // 1st transaction $this->wrapper->beginTransaction(); - self::assertTrue($this->wrapper->inTransaction()); - self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + static::assertTrue($this->wrapper->inTransaction()); + static::assertSame(1, $this->getNestedTransactionsCount($this->wrapper)); // 2nd transaction $this->wrapper->beginTransaction(); - self::assertTrue($this->wrapper->inTransaction()); - self::assertSame(++$nested, $this->wrapper->getNestedTransactionsCount()); + static::assertTrue($this->wrapper->inTransaction()); + static::assertSame(2, $this->getNestedTransactionsCount($this->wrapper)); // Rollback $this->wrapper->rollback(); - self::assertFalse($this->wrapper->inTransaction()); - self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); + static::assertFalse($this->wrapper->inTransaction()); + static::assertSame(0, $this->getNestedTransactionsCount($this->wrapper)); } - public function testRollbackDisconnectedThrowsException(): void + #[Test] + public function rollbackDisconnectedThrowsException(): void { $this->wrapper->disconnect(); @@ -116,20 +122,23 @@ public function testRollbackDisconnectedThrowsException(): void $this->wrapper->rollback(); } - public function testRollbackReturnsInstanceOfConnection(): void + #[Test] + public function rollbackReturnsInstanceOfConnection(): void { $this->wrapper->beginTransaction(); - self::assertInstanceOf(Connection::class, $this->wrapper->rollback()); + static::assertInstanceOf(Connection::class, $this->wrapper->rollback()); } - public function testRollbackSetsInTransactionAtFalse(): void + #[Test] + public function rollbackSetsInTransactionAtFalse(): void { $this->wrapper->beginTransaction(); $this->wrapper->rollback(); - self::assertFalse($this->wrapper->inTransaction()); + static::assertFalse($this->wrapper->inTransaction()); } - public function testRollbackWithoutBeginThrowsException(): void + #[Test] + public function rollbackWithoutBeginThrowsException(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Must call beginTransaction() before you can rollback'); @@ -139,15 +148,16 @@ public function testRollbackWithoutBeginThrowsException(): void /** * Standalone commit after a SET autocommit=0; */ - public function testStandaloneCommit(): void + #[Test] + public function standaloneCommit(): void { - self::assertFalse($this->wrapper->inTransaction()); - self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); + static::assertFalse($this->wrapper->inTransaction()); + static::assertSame(0, $this->getNestedTransactionsCount($this->wrapper)); $this->wrapper->commit(); - self::assertFalse($this->wrapper->inTransaction()); - self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); + static::assertFalse($this->wrapper->inTransaction()); + static::assertSame(0, $this->getNestedTransactionsCount($this->wrapper)); } /** @@ -156,6 +166,17 @@ public function testStandaloneCommit(): void #[Override] protected function setUp(): void { - $this->wrapper = new ConnectionWrapper(); + $this->wrapper = new Connection([]); + // bypass setResource(), which calls PDO::getAttribute() and would fail + // against the stub's uninitialized internal PDO state + (new ReflectionProperty($this->wrapper, 'resource'))->setValue( + $this->wrapper, + new PdoStubDriver('foo', 'bar', 'baz'), + ); + } + + private function getNestedTransactionsCount(Connection $connection): int + { + return (new ReflectionProperty($connection, 'nestedTransactionsCount'))->getValue($connection); } } diff --git a/test/unit/Pdo/DriverTest.php b/test/unit/Pdo/DriverTest.php index d36fe0b..8889a03 100644 --- a/test/unit/Pdo/DriverTest.php +++ b/test/unit/Pdo/DriverTest.php @@ -7,13 +7,14 @@ use Override; use PDOStatement; use PhpDb\Adapter\Driver\DriverInterface; +use PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Exception\RuntimeException; -use PhpDb\Mysql\Pdo\Connection; use PhpDb\Mysql\Pdo\Driver; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Driver::class, 'getResultPrototype')] @@ -51,42 +52,46 @@ public static function getParamsAndType(): array ]; } - public function testCreateResultPassesNullRowCount(): void + #[Test] + public function createResultPassesNullRowCount(): void { $pdoStatement = $this->getMockBuilder(PDOStatement::class)->getMock(); $pdoStatement->expects($this->once()) ->method('rowCount') ->willReturn(4); - $connection = $this->createMock(Connection::class); + $connection = $this->createMock(AbstractPdoConnection::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()); + static::assertInstanceOf(Result::class, $result); + static::assertSame(4, $result->count()); } + #[Test] #[DataProvider('getParamsAndType')] - public function testFormatParameterName(int|string $name, ?string $type, string $expected): void + public function formatParameterName(int|string $name, ?string $type, string $expected): void { $result = $this->pdo->formatParameterName($name, $type); - $this->assertEquals($expected, $result); + static::assertEquals($expected, $result); } + #[Test] #[DataProvider('getInvalidParamName')] - public function testFormatParameterNameWithInvalidCharacters(string $name): void + public function formatParameterNameWithInvalidCharacters(string $name): void { $this->expectException(RuntimeException::class); $this->pdo->formatParameterName($name); } - public function testGetResultPrototype(): void + #[Test] + public function getResultPrototype(): void { $resultPrototype = $this->pdo->getResultPrototype(); - self::assertInstanceOf(Result::class, $resultPrototype); + static::assertInstanceOf(Result::class, $resultPrototype); } /** @@ -96,7 +101,7 @@ public function testGetResultPrototype(): void #[Override] protected function setUp(): void { - $connection = $this->createMock(Connection::class); + $connection = $this->createMock(AbstractPdoConnection::class); $statement = $this->createMock(Statement::class); $result = $this->createMock(Result::class); $this->pdo = new Driver( diff --git a/test/unit/Pdo/ResultTest.php b/test/unit/Pdo/ResultTest.php index bd21809..ce1a43d 100644 --- a/test/unit/Pdo/ResultTest.php +++ b/test/unit/Pdo/ResultTest.php @@ -10,6 +10,7 @@ use PhpDb\Adapter\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use stdClass; @@ -21,19 +22,21 @@ #[Group('result-pdo')] final class ResultTest extends TestCase { - public function testCountWithClosureRowCountInvokesClosure(): void + #[Test] + public function countWithClosureRowCountInvokesClosure(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->never()) ->method('rowCount'); $result = new Result(); - $result->initialize($mock, null, fn() => 3); + $result->initialize($mock, null, static fn() => 3); - self::assertSame(3, $result->count()); + static::assertSame(3, $result->count()); } - public function testCountWithIntRowCountReturnsValueWithoutQueryingPdo(): void + #[Test] + public function countWithIntRowCountReturnsValueWithoutQueryingPdo(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->never()) @@ -42,10 +45,11 @@ public function testCountWithIntRowCountReturnsValueWithoutQueryingPdo(): void $result = new Result(); $result->initialize($mock, null, 7); - self::assertSame(7, $result->count()); + static::assertSame(7, $result->count()); } - public function testCountWithNullRowCountDelegatesToPdoStatement(): void + #[Test] + public function countWithNullRowCountDelegatesToPdoStatement(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->once()) @@ -55,10 +59,11 @@ public function testCountWithNullRowCountDelegatesToPdoStatement(): void $result = new Result(); $result->initialize($mock, null, null); - self::assertSame(4, $result->count()); + static::assertSame(4, $result->count()); } - public function testCountWithZeroRowCountReturnsZeroWithoutQueryingPdo(): void + #[Test] + public function countWithZeroRowCountReturnsZeroWithoutQueryingPdo(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->never()) @@ -67,44 +72,48 @@ public function testCountWithZeroRowCountReturnsZeroWithoutQueryingPdo(): void $result = new Result(); $result->initialize($mock, null, 0); - self::assertSame(0, $result->count()); + static::assertSame(0, $result->count()); } /** * Tests current method returns same data on consecutive calls. */ - public function testCurrent(): void + #[Test] + public function current(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->any()) ->method('fetch') - ->willReturnCallback(fn() => uniqid()); + // @mago-expect lint:prefer-first-class-callable + ->willReturnCallback(static fn() => uniqid()); $result = new Result(); $result->initialize($mock, null); - self::assertEquals($result->current(), $result->current()); + static::assertEquals($result->current(), $result->current()); } /** * Tests whether the fetch mode was set properly and */ - public function testFetchModeAnonymousObject(): void + #[Test] + public function fetchModeAnonymousObject(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->any()) ->method('fetch') - ->willReturnCallback(fn() => new stdClass()); + ->willReturnCallback(static fn() => new stdClass()); $result = new Result(); $result->initialize($mock, null); $result->setFetchMode(PDO::FETCH_OBJ); - self::assertEquals(5, $result->getFetchMode()); - self::assertInstanceOf('stdClass', $result->current()); + static::assertSame(5, $result->getFetchMode()); + static::assertInstanceOf('stdClass', $result->current()); } - public function testFetchModeException(): void + #[Test] + public function fetchModeException(): void { $result = new Result(); @@ -115,20 +124,22 @@ public function testFetchModeException(): void /** * Tests whether the fetch mode has a broader range */ - public function testFetchModeRange(): void + #[Test] + public function fetchModeRange(): void { $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); $mock->expects($this->any()) ->method('fetch') - ->willReturnCallback(fn() => new stdClass()); + ->willReturnCallback(static fn() => new stdClass()); $result = new Result(); $result->initialize($mock, null); $result->setFetchMode(PDO::FETCH_NAMED); - self::assertEquals(11, $result->getFetchMode()); - self::assertInstanceOf('stdClass', $result->current()); + static::assertSame(11, $result->getFetchMode()); + static::assertInstanceOf('stdClass', $result->current()); } - public function testMultipleRewind(): void + #[Test] + public function multipleRewind(): void { $data = [ ['test' => 1], @@ -137,10 +148,10 @@ public function testMultipleRewind(): void $position = 0; $mock = $this->getMockBuilder(PDOStatement::class)->getMock(); - assert($mock instanceof PDOStatement); // to suppress IDE type warnings + assert($mock instanceof PDOStatement, description: 'to suppress IDE type warnings'); $mock->expects($this->any()) ->method('fetch') - ->willReturnCallback(function () use ($data, &$position) { + ->willReturnCallback(static function () use ($data, &$position) { return $data[$position++]; }); $result = new Result(); @@ -149,13 +160,13 @@ public function testMultipleRewind(): void $result->rewind(); $result->rewind(); - $this->assertEquals(0, $result->key()); - $this->assertEquals(1, $position); - $this->assertEquals($data[0], $result->current()); + static::assertSame(0, $result->key()); + static::assertSame(1, $position); + static::assertEquals($data[0], $result->current()); $result->next(); - $this->assertEquals(1, $result->key()); - $this->assertEquals(2, $position); - $this->assertEquals($data[1], $result->current()); + static::assertSame(1, $result->key()); + static::assertSame(2, $position); + static::assertEquals($data[1], $result->current()); } } diff --git a/test/unit/Pdo/StatementIntegrationTest.php b/test/unit/Pdo/StatementIntegrationTest.php index 97dad99..f7ace3e 100644 --- a/test/unit/Pdo/StatementIntegrationTest.php +++ b/test/unit/Pdo/StatementIntegrationTest.php @@ -8,9 +8,10 @@ use PDO; use PDOStatement; use PhpDb\Adapter\Driver\Pdo\Statement; -use PhpDb\Mysql\Pdo\Driver as PdoDriver; -use PhpDbTest\Mysql\Pdo\TestAsset; +use PhpDb\Adapter\Driver\PdoDriverInterface; +use PhpDb\Adapter\Driver\ResultInterface; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -22,54 +23,58 @@ final class StatementIntegrationTest extends TestCase /** @var MockObject */ protected PDOStatement|MockObject $pdoStatementMock; - public function testStatementExecuteWillConvertPhpBoolToPdoBoolWhenBinding(): void + #[Test] + public function statementExecuteWillConvertPhpBoolToPdoBoolWhenBinding(): void { $this->pdoStatementMock ->expects($this->any()) ->method('bindParam') ->with( - $this->equalTo(':foo'), - $this->equalTo(false), - $this->equalTo(PDO::PARAM_BOOL), + static::equalTo(':foo'), + static::equalTo(false), + static::equalTo(PDO::PARAM_BOOL), ); $this->statement->execute(['foo' => false]); } - public function testStatementExecuteWillUsePdoIntForIntWhenBinding(): void + #[Test] + public function statementExecuteWillUsePdoIntForIntWhenBinding(): void { $this->pdoStatementMock ->expects($this->any()) ->method('bindParam') ->with( - $this->equalTo(':foo'), - $this->equalTo(123), - $this->equalTo(PDO::PARAM_INT), + static::equalTo(':foo'), + static::equalTo(123), + static::equalTo(PDO::PARAM_INT), ); $this->statement->execute(['foo' => 123]); } - public function testStatementExecuteWillUsePdoStrByDefaultWhenBinding(): void + #[Test] + public function statementExecuteWillUsePdoStrByDefaultWhenBinding(): void { $this->pdoStatementMock ->expects($this->any()) ->method('bindParam') ->with( - $this->equalTo(':foo'), - $this->equalTo('bar'), - $this->equalTo(PDO::PARAM_STR), + static::equalTo(':foo'), + static::equalTo('bar'), + static::equalTo(PDO::PARAM_STR), ); $this->statement->execute(['foo' => 'bar']); } - public function testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding(): void + #[Test] + public function statementExecuteWillUsePdoStrForStringIntegerWhenBinding(): void { $this->pdoStatementMock ->expects($this->any()) ->method('bindParam') ->with( - $this->equalTo(':foo'), - $this->equalTo('123'), - $this->equalTo(PDO::PARAM_STR), + static::equalTo(':foo'), + static::equalTo('123'), + static::equalTo(PDO::PARAM_STR), ); $this->statement->execute(['foo' => '123']); } @@ -81,18 +86,16 @@ public function testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding(): #[Override] protected function setUp(): void { - $driver = $this->getMockBuilder(PdoDriver::class) - ->onlyMethods(['createResult']) - ->disableOriginalConstructor() + $driver = $this->createMock(PdoDriverInterface::class); + $driver->method('createResult')->willReturn($this->createMock(ResultInterface::class)); + + $this->pdoStatementMock = $this->getMockBuilder(PDOStatement::class) + ->onlyMethods(['execute', 'bindParam']) ->getMock(); $this->statement = new Statement(); $this->statement->setDriver($driver); - $this->statement->initialize(new TestAsset\CtorlessPdo( - $this->pdoStatementMock = $this->getMockBuilder(PDOStatement::class) - ->onlyMethods(['execute', 'bindParam']) - ->getMock(), - )); + $this->statement->initialize(new TestAsset\CtorlessPdo($this->pdoStatementMock)); } /** diff --git a/test/unit/Pdo/StatementTest.php b/test/unit/Pdo/StatementTest.php index 3cb9740..f914a9f 100644 --- a/test/unit/Pdo/StatementTest.php +++ b/test/unit/Pdo/StatementTest.php @@ -6,14 +6,15 @@ use Override; use PDOStatement; +use PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection; 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; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Statement::class, 'setDriver')] @@ -30,7 +31,8 @@ final class StatementTest extends TestCase protected ?Driver $pdo; protected Statement $statement; - public function testExecute(): void + #[Test] + public function execute(): void { $mockPdoStatement = $this->createMock(PDOStatement::class); $pdo = new TestAsset\CtorlessPdo($mockPdoStatement); @@ -38,70 +40,78 @@ public function testExecute(): void $this->statement->prepare('SELECT 1'); $result = $this->statement->execute(); - self::assertInstanceOf(ResultInterface::class, $result); + static::assertInstanceOf(ResultInterface::class, $result); } /** * @todo Implement testGetParameterContainer(). */ - public function testGetParameterContainer(): void + #[Test] + public function getParameterContainer(): void { $container = new ParameterContainer(); $this->statement->setParameterContainer($container); - self::assertSame($container, $this->statement->getParameterContainer()); + static::assertSame($container, $this->statement->getParameterContainer()); } - public function testGetResource(): void + #[Test] + public function getResource(): void { $stmt = $this->createMock(PDOStatement::class); $this->statement->setResource($stmt); - self::assertSame($stmt, $this->statement->getResource()); + static::assertSame($stmt, $this->statement->getResource()); } - public function testGetSql(): void + #[Test] + public function getSql(): void { $this->statement->setSql('SELECT 1'); - self::assertEquals('SELECT 1', $this->statement->getSql()); + static::assertSame('SELECT 1', $this->statement->getSql()); } - public function testIsPrepared(): void + #[Test] + public function isPrepared(): void { - self::assertFalse($this->statement->isPrepared()); + static::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()); + static::assertTrue($this->statement->isPrepared()); } - public function testPrepare(): void + #[Test] + public function prepare(): void { $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); + static::assertInstanceOf(Statement::class, $result); } - public function testSetDriver(): void + #[Test] + public function setDriver(): void { - self::assertInstanceOf(PdoDriverInterface::class, $this->pdo); - self::assertEquals($this->statement, $this->statement->setDriver($this->pdo)); + static::assertInstanceOf(PdoDriverInterface::class, $this->pdo); + static::assertEquals($this->statement, $this->statement->setDriver($this->pdo)); } - public function testSetParameterContainer(): void + #[Test] + public function setParameterContainer(): void { - self::assertSame($this->statement, $this->statement->setParameterContainer(new ParameterContainer())); + static::assertSame($this->statement, $this->statement->setParameterContainer(new ParameterContainer())); } - public function testSetSql(): void + #[Test] + public function setSql(): void { $this->statement->setSql('SELECT 1'); - self::assertEquals('SELECT 1', $this->statement->getSql()); + static::assertSame('SELECT 1', $this->statement->getSql()); } /** @@ -113,7 +123,7 @@ protected function setUp(): void { $this->statement = new Statement(); $this->pdo = new Driver( - $this->createMock(Connection::class), + $this->createMock(AbstractPdoConnection::class), $this->statement, new Result(), ); diff --git a/test/unit/Pdo/TestAsset/ConnectionWrapper.php b/test/unit/Pdo/TestAsset/ConnectionWrapper.php deleted file mode 100644 index f89a1e9..0000000 --- a/test/unit/Pdo/TestAsset/ConnectionWrapper.php +++ /dev/null @@ -1,23 +0,0 @@ -resource = new PdoStubDriver('foo', 'bar', 'baz'); - } - - public function getNestedTransactionsCount(): int - { - return $this->nestedTransactionsCount; - } -} diff --git a/test/unit/Pdo/TestAsset/PdoStubDriver.php b/test/unit/Pdo/TestAsset/PdoStubDriver.php index ef1ab7b..35ee98f 100644 --- a/test/unit/Pdo/TestAsset/PdoStubDriver.php +++ b/test/unit/Pdo/TestAsset/PdoStubDriver.php @@ -5,6 +5,7 @@ namespace PhpDbTest\Mysql\Pdo\TestAsset; use PDO; +use SensitiveParameter; final class PdoStubDriver extends PDO { @@ -13,7 +14,7 @@ final class PdoStubDriver extends PDO * @param string $password * @phpstan-ignore constructor.unusedParameter, constructor.unusedParameter, constructor.unusedParameter */ - public function __construct(string $dsn, $user, $password) {} + public function __construct(string $dsn, $user, #[SensitiveParameter] $password) {} public function beginTransaction(): bool { diff --git a/test/unit/Sql/Ddl/AlterTableDecoratorTest.php b/test/unit/Sql/Ddl/AlterTableDecoratorTest.php index 94f54e6..b842518 100644 --- a/test/unit/Sql/Ddl/AlterTableDecoratorTest.php +++ b/test/unit/Sql/Ddl/AlterTableDecoratorTest.php @@ -4,15 +4,16 @@ namespace PhpDbTest\Mysql\Sql\Ddl; +use PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Mysql\AdapterPlatform; -use PhpDb\Mysql\Pdo\Connection; use PhpDb\Mysql\Pdo\Driver; use PhpDb\Mysql\Sql\Ddl\AlterTableDecorator; use PhpDb\Sql\Ddl\AlterTable; use PhpDb\Sql\Ddl\Column; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(AlterTableDecorator::class, 'processAddColumns')] @@ -22,7 +23,8 @@ final class AlterTableDecoratorTest extends TestCase { protected AdapterPlatform $platform; - public function testAddColumnAfter(): void + #[Test] + public function addColumnAfter(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -31,10 +33,11 @@ public function testAddColumnAfter(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('AFTER `id`', $sql); + static::assertStringContainsString('AFTER `id`', $sql); } - public function testAddColumnCharset(): void + #[Test] + public function addColumnCharset(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -43,10 +46,11 @@ public function testAddColumnCharset(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('CHARACTER SET utf8mb3', $sql); + static::assertStringContainsString('CHARACTER SET utf8mb3', $sql); } - public function testAddColumnCharsetAndCollate(): void + #[Test] + public function addColumnCharsetAndCollate(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -56,10 +60,11 @@ public function testAddColumnCharsetAndCollate(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci', $sql); + static::assertStringContainsString('CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci', $sql); } - public function testAddColumnCharsetBeforeNotNull(): void + #[Test] + public function addColumnCharsetBeforeNotNull(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -70,13 +75,14 @@ public function testAddColumnCharsetBeforeNotNull(): void $sql = $this->buildSql($alter); - self::assertMatchesRegularExpression( + static::assertMatchesRegularExpression( '/CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL/', $sql, ); } - public function testAddColumnCollate(): void + #[Test] + public function addColumnCollate(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -85,10 +91,11 @@ public function testAddColumnCollate(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('COLLATE utf8mb3_unicode_ci', $sql); + static::assertStringContainsString('COLLATE utf8mb3_unicode_ci', $sql); } - public function testAddColumnUnsigned(): void + #[Test] + public function addColumnUnsigned(): void { $alter = new AlterTable('test'); $col = new Column\Integer('id'); @@ -98,11 +105,12 @@ public function testAddColumnUnsigned(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('UNSIGNED', $sql); - self::assertStringContainsString('AUTO_INCREMENT', $sql); + static::assertStringContainsString('UNSIGNED', $sql); + static::assertStringContainsString('AUTO_INCREMENT', $sql); } - public function testChangeColumnCharset(): void + #[Test] + public function changeColumnCharset(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -111,10 +119,11 @@ public function testChangeColumnCharset(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('CHARACTER SET utf8mb3', $sql); + static::assertStringContainsString('CHARACTER SET utf8mb3', $sql); } - public function testChangeColumnCharsetAndCollate(): void + #[Test] + public function changeColumnCharsetAndCollate(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -125,13 +134,14 @@ public function testChangeColumnCharsetAndCollate(): void $sql = $this->buildSql($alter); - self::assertMatchesRegularExpression( + static::assertMatchesRegularExpression( '/CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL/', $sql, ); } - public function testChangeColumnCollate(): void + #[Test] + public function changeColumnCollate(): void { $alter = new AlterTable('test'); $col = new Column\Varchar('name', 255); @@ -140,13 +150,13 @@ public function testChangeColumnCollate(): void $sql = $this->buildSql($alter); - self::assertStringContainsString('COLLATE utf8mb3_unicode_ci', $sql); + static::assertStringContainsString('COLLATE utf8mb3_unicode_ci', $sql); } protected function setUp(): void { $driver = new Driver( - $this->createMock(Connection::class), + $this->createMock(AbstractPdoConnection::class), $this->createMock(Statement::class), $this->createMock(Result::class), ); diff --git a/test/unit/Sql/Ddl/CreateTableDecoratorTest.php b/test/unit/Sql/Ddl/CreateTableDecoratorTest.php index 148db3b..448f98d 100644 --- a/test/unit/Sql/Ddl/CreateTableDecoratorTest.php +++ b/test/unit/Sql/Ddl/CreateTableDecoratorTest.php @@ -4,16 +4,17 @@ namespace PhpDbTest\Mysql\Sql\Ddl; +use PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection; use PhpDb\Adapter\Driver\Pdo\Result; use PhpDb\Adapter\Driver\Pdo\Statement; use PhpDb\Mysql\AdapterPlatform; -use PhpDb\Mysql\Pdo\Connection; use PhpDb\Mysql\Pdo\Driver; use PhpDb\Mysql\Sql\Ddl\CreateTableDecorator; use PhpDb\Sql\Ddl\Column; use PhpDb\Sql\Ddl\Constraint; use PhpDb\Sql\Ddl\CreateTable; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(CreateTableDecorator::class, 'processColumns')] @@ -22,7 +23,8 @@ final class CreateTableDecoratorTest extends TestCase { protected AdapterPlatform $platform; - public function testCharsetAppearsAfterUnsigned(): void + #[Test] + public function charsetAppearsAfterUnsigned(): void { $table = new CreateTable('test'); $col = new Column\Integer('id'); @@ -32,10 +34,11 @@ public function testCharsetAppearsAfterUnsigned(): void $sql = $this->buildSql($table); - self::assertMatchesRegularExpression('/UNSIGNED CHARACTER SET utf8mb3/', $sql); + static::assertMatchesRegularExpression('/UNSIGNED CHARACTER SET utf8mb3/', $sql); } - public function testCharsetAppearsBeforeNotNull(): void + #[Test] + public function charsetAppearsBeforeNotNull(): void { $table = new CreateTable('test'); $col = new Column\Varchar('name', 255); @@ -46,13 +49,14 @@ public function testCharsetAppearsBeforeNotNull(): void $sql = $this->buildSql($table); - self::assertMatchesRegularExpression( + static::assertMatchesRegularExpression( '/CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL/', $sql, ); } - public function testColumnCharset(): void + #[Test] + public function columnCharset(): void { $table = new CreateTable('test'); $col = new Column\Varchar('name', 255); @@ -61,10 +65,11 @@ public function testColumnCharset(): void $sql = $this->buildSql($table); - self::assertStringContainsString('CHARACTER SET utf8mb3', $sql); + static::assertStringContainsString('CHARACTER SET utf8mb3', $sql); } - public function testColumnCharsetAndCollate(): void + #[Test] + public function columnCharsetAndCollate(): void { $table = new CreateTable('test'); $col = new Column\Varchar('name', 255); @@ -74,10 +79,11 @@ public function testColumnCharsetAndCollate(): void $sql = $this->buildSql($table); - self::assertStringContainsString('CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci', $sql); + static::assertStringContainsString('CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci', $sql); } - public function testColumnCollate(): void + #[Test] + public function columnCollate(): void { $table = new CreateTable('test'); $col = new Column\Varchar('name', 255); @@ -86,10 +92,11 @@ public function testColumnCollate(): void $sql = $this->buildSql($table); - self::assertStringContainsString('COLLATE utf8mb3_unicode_ci', $sql); + static::assertStringContainsString('COLLATE utf8mb3_unicode_ci', $sql); } - public function testCommentOption(): void + #[Test] + public function commentOption(): void { $table = new CreateTable('test'); $col = new Column\Varchar('name', 255); @@ -98,10 +105,11 @@ public function testCommentOption(): void $sql = $this->buildSql($table); - self::assertStringContainsString('COMMENT', $sql); + static::assertStringContainsString('COMMENT', $sql); } - public function testFullColumnDefinition(): void + #[Test] + public function fullColumnDefinition(): void { $table = new CreateTable('test'); @@ -120,12 +128,13 @@ public function testFullColumnDefinition(): void $sql = $this->buildSql($table); - self::assertStringContainsString('UNSIGNED', $sql); - self::assertStringContainsString('AUTO_INCREMENT', $sql); - self::assertStringContainsString('CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL', $sql); + static::assertStringContainsString('UNSIGNED', $sql); + static::assertStringContainsString('AUTO_INCREMENT', $sql); + static::assertStringContainsString('CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL', $sql); } - public function testUnsignedOption(): void + #[Test] + public function unsignedOption(): void { $table = new CreateTable('test'); $col = new Column\Integer('id'); @@ -135,14 +144,14 @@ public function testUnsignedOption(): void $sql = $this->buildSql($table); - self::assertStringContainsString('UNSIGNED', $sql); - self::assertStringContainsString('AUTO_INCREMENT', $sql); + static::assertStringContainsString('UNSIGNED', $sql); + static::assertStringContainsString('AUTO_INCREMENT', $sql); } protected function setUp(): void { $driver = new Driver( - $this->createMock(Connection::class), + $this->createMock(AbstractPdoConnection::class), $this->createMock(Statement::class), $this->createMock(Result::class), );