From 451aba93964b0abedda347a0aa68a506dc94f79f Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 10:20:11 +1000 Subject: [PATCH 1/6] Annotate Feature classes and types - Add @api to the Feature interfaces, both AbstractFeature bases, EventFeatureEventsInterface, EventFeature, GlobalAdapterFeature, MetadataFeature, SequenceFeature and TableGateway\Feature\FeatureSet - Mark RowGatewayFeature, MasterSlaveFeature and TableGatewayEvent final - Type the EventFeatureEventsInterface constants and both APPLY_HALT - Add array shape docblocks to the FeatureSets, AbstractFeature::$sharedData, RowGatewayFeature::$constructorArguments and TableGatewayEvent::$params - Declare TableGatewayEvent as an EventInterface implementation - Correct AbstractTableGateway's @property $table to the declared union and narrow TableGateway::__construct's $features docblock - Register the AbstractFeature gateway setters as class initializers - Drop the Feature entries from the analysis baselines --- analyzer-baseline.toml | 606 ------------------ lint-baseline.toml | 90 --- mago.toml | 2 + src/Feature/FeatureInterface.php | 3 + src/RowGateway/Feature/AbstractFeature.php | 4 + src/RowGateway/Feature/FeatureInterface.php | 3 + src/RowGateway/Feature/FeatureSet.php | 31 +- src/TableGateway/AbstractTableGateway.php | 2 +- src/TableGateway/Feature/AbstractFeature.php | 4 + src/TableGateway/Feature/EventFeature.php | 3 + .../EventFeature/TableGatewayEvent.php | 15 +- .../Feature/EventFeatureEventsInterface.php | 22 +- src/TableGateway/Feature/FeatureInterface.php | 3 + src/TableGateway/Feature/FeatureSet.php | 30 +- .../Feature/GlobalAdapterFeature.php | 3 + .../Feature/MasterSlaveFeature.php | 2 +- src/TableGateway/Feature/MetadataFeature.php | 3 + .../Feature/RowGatewayFeature.php | 3 +- src/TableGateway/Feature/SequenceFeature.php | 10 + src/TableGateway/TableGateway.php | 2 + 20 files changed, 128 insertions(+), 713 deletions(-) diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml index 26938f84..df84dfd8 100644 --- a/analyzer-baseline.toml +++ b/analyzer-baseline.toml @@ -1482,12 +1482,6 @@ code = "missing-api-or-internal" message = 'Interface `PhpDb\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' count = 1 -[[issues]] -file = "src/Feature/FeatureInterface.php" -code = "missing-api-or-internal" -message = 'Interface `PhpDb\Feature\FeatureInterface` is missing an `@api` or `@internal` annotation.' -count = 1 - [[issues]] file = "src/Metadata/Object/ConstraintKeyObject.php" code = "missing-constant-type" @@ -2082,102 +2076,6 @@ code = "class-must-be-final" message = 'Class `PhpDb\RowGateway\Exception\RuntimeException` should be declared `final`.' count = 1 -[[issues]] -file = "src/RowGateway/Feature/AbstractFeature.php" -code = "imprecise-type" -message = "Type `array` in property `$sharedData` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/AbstractFeature.php" -code = "missing-api-or-internal" -message = 'Abstract class `PhpDb\RowGateway\Feature\AbstractFeature` is missing an `@api` or `@internal` annotation.' -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureInterface.php" -code = "missing-api-or-internal" -message = 'Interface `PhpDb\RowGateway\Feature\FeatureInterface` is missing an `@api` or `@internal` annotation.' -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in parameter `$args` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in parameter `$arguments` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in parameter `$features` is imprecise, equivalent to `array`." -count = 2 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in property `$magicSpecifications` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "missing-constant-type" -message = "Class constant `APPLY_HALT` is missing a type hint." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #1 of `PhpDb\RowGateway\Feature\FeatureSet::addFeature`: expected `PhpDb\RowGateway\Feature\FeatureInterface`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "mixed-assignment" -message = "Assigning `mixed` type to a variable may lead to unexpected behavior." -count = 2 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "string-member-selector" -message = "This member selector uses a non-literal string type (`string`); its specific value cannot be statically determined." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "unused-parameter" -message = "Parameter `$arguments` is never used." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "unused-parameter" -message = "Parameter `$method` is never used." -count = 2 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "unused-parameter" -message = "Parameter `$property` is never used." -count = 4 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "unused-parameter" -message = "Parameter `$value` is never used." -count = 1 - -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "unused-property" -message = "Property `$magicSpecifications` is never used." -count = 1 - [[issues]] file = "src/RowGateway/RowGateway.php" code = "class-must-be-final" @@ -5640,510 +5538,6 @@ code = "class-must-be-final" message = 'Class `PhpDb\TableGateway\Exception\RuntimeException` should be declared `final`.' count = 1 -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "imprecise-type" -message = "Type `array` in property `$sharedData` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "missing-api-or-internal" -message = 'Abstract class `PhpDb\TableGateway\Feature\AbstractFeature` is missing an `@api` or `@internal` annotation.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "uninitialized-property" -message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\EventFeature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "uninitialized-property" -message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\MasterSlaveFeature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "uninitialized-property" -message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\MetadataFeature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "uninitialized-property" -message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\RowGatewayFeature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/AbstractFeature.php" -code = "uninitialized-property" -message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\SequenceFeature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\EventFeature` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "invalid-return-statement" -message = 'Invalid return type for function `PhpDb\TableGateway\Feature\EventFeature::getEvent`: expected `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent`, but found `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent|null`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "nullable-return-statement" -message = 'Function `PhpDb\TableGateway\Feature\EventFeature::getEvent` is declared to return `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent` but possibly returns a nullable value (inferred as `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent|null`).' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "possible-method-access-on-null" -message = "Attempting to call a method on `null`." -count = 19 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "possibly-null-argument" -message = 'Argument #1 of method `Laminas\EventManager\EventManagerInterface::triggerEvent` is possibly `null`, but parameter type `Laminas\EventManager\EventInterface|array|object>` does not accept it.' -count = 10 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "imprecise-type" -message = "Type `array` in property `$params` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "missing-template-parameter" -message = 'Too few template arguments for `Laminas\EventManager\EventInterface`: expected at least 2, but found 0.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "possibly-invalid-array-access" -message = "Cannot perform array access on value of type `object`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "property-type-coercion" -message = 'A value of a less specific type `null|object|string` is being assigned to property `$target` (PhpDb\TableGateway\AbstractTableGateway|null).' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "unused-template-parameter" -message = 'Template parameter `NewTParams` is never used in method `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent::setParams`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" -code = "unused-template-parameter" -message = 'Template parameter `NewTTarget` is never used in method `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent::setTarget`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-api-or-internal" -message = 'Interface `PhpDb\TableGateway\Feature\EventFeatureEventsInterface` is missing an `@api` or `@internal` annotation.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_POST_DELETE` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_POST_INITIALIZE` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_POST_INSERT` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_POST_SELECT` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_POST_UPDATE` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_PRE_DELETE` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_PRE_INITIALIZE` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_PRE_INSERT` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_PRE_SELECT` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" -code = "missing-constant-type" -message = "Class constant `EVENT_PRE_UPDATE` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureInterface.php" -code = "missing-api-or-internal" -message = 'Interface `PhpDb\TableGateway\Feature\FeatureInterface` is missing an `@api` or `@internal` annotation.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\FeatureSet` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in parameter `$args` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in parameter `$arguments` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in parameter `$features` is imprecise, equivalent to `array`." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "imprecise-type" -message = "Type `array` in property `$magicSpecifications` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "missing-constant-type" -message = "Class constant `APPLY_HALT` is missing a type hint." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #1 of `PhpDb\TableGateway\Feature\FeatureSet::addFeature`: expected `PhpDb\TableGateway\Feature\FeatureInterface`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "mixed-assignment" -message = "Assigning `mixed` type to a variable may lead to unexpected behavior." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "string-member-selector" -message = "This member selector uses a non-literal string type (`string`); its specific value cannot be statically determined." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "unused-parameter" -message = "Parameter `$property` is never used." -count = 4 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "unused-parameter" -message = "Parameter `$value` is never used." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\GlobalAdapterFeature` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "invalid-return-statement" -message = 'Invalid return type for function `PhpDb\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter`: expected `PhpDb\Adapter\AdapterInterface`, but found `PhpDb\Adapter\AdapterInterface|null`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "missing-constructor" -message = 'Class `PhpDb\TableGateway\Feature\GlobalAdapterFeature` has typed properties without default values but no constructor to initialize them.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "nullable-return-statement" -message = 'Function `PhpDb\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter` is declared to return `PhpDb\Adapter\AdapterInterface` but possibly returns a nullable value (inferred as `PhpDb\Adapter\AdapterInterface|null`).' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "possibly-undefined-string-array-index" -message = '''Possibly undefined array key `class-string('PhpDb\TableGateway\Feature\GlobalAdapterFeature')` accessed on `array`.''' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MasterSlaveFeature.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\MasterSlaveFeature` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MasterSlaveFeature.php" -code = "invalid-property-assignment-value" -message = 'Invalid type for property `$masterSql`: expected `PhpDb\Sql\Sql`, but got `PhpDb\Sql\Sql|null`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MasterSlaveFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #2 of `PhpDb\Sql\Sql::__construct`: expected `PhpDb\Sql\TableIdentifier|array|null|string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MasterSlaveFeature.php" -code = "possible-method-access-on-null" -message = "Attempting to call a method on `null`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MasterSlaveFeature.php" -code = "uninitialized-property" -message = 'Property `$masterSql` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\MasterSlaveFeature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\MetadataFeature` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "impossible-condition" -message = "Redundant ternary operator: condition is always falsy." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "impossible-condition" -message = "This condition (type `false`) will always evaluate to false." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "impossible-type-comparison" -message = "Impossible type assertion: `$t->table` of type `string` can never be `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\MetadataInterface::getColumnNames`: expected `string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\MetadataInterface::getConstraints`: expected `string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\MetadataInterface::getTable`: expected `string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #2 of `PhpDb\Metadata\MetadataInterface::getColumnNames`: expected `null|string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #2 of `PhpDb\Metadata\MetadataInterface::getConstraints`: expected `null|string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #2 of `PhpDb\Metadata\MetadataInterface::getTable`: expected `null|string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-array-assignment" -message = "Unsafe array assignment on type `mixed`." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "mixed-assignment" -message = "Assigning `mixed` type to a variable may lead to unexpected behavior." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "no-value" -message = "Argument #1 passed to function `current` has type `never`, meaning it cannot produce a value." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "possibly-undefined-int-array-index" -message = "Possibly undefined array key `int(0)` accessed on `non-empty-array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\RowGatewayFeature` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "imprecise-type" -message = "Type `array` in property `$constructorArguments` is imprecise, equivalent to `array`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "mixed-argument" -message = 'Invalid argument type for argument #1 of `PhpDb\RowGateway\RowGateway::__construct`: expected `array|null|string`, but found `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "mixed-array-access" -message = "Unsafe array access on type `nonnull`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "mixed-assignment" -message = "Assigning `mixed` type to a variable may lead to unexpected behavior." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "possible-method-access-on-null" -message = "Attempting to call a method on `null`." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "class-must-be-final" -message = 'Class `PhpDb\TableGateway\Feature\SequenceFeature` should be declared `final`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "invalid-array-access" -message = 'Cannot access array index on object `PhpDb\Sql\TableIdentifier` that does not implement `ArrayAccess`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "mixed-array-access" -message = "Unsafe array access on type `mixed`." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "mixed-assignment" -message = "Assigning `mixed` type to a variable may lead to unexpected behavior." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "mixed-property-type-coercion" -message = "A value with a less specific type `mixed` is being assigned to property `$sequenceValue` (int|null)." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "mixed-return-statement" -message = 'Could not infer a precise return type for function `PhpDb\TableGateway\Feature\SequenceFeature::lastSequenceId`. Saw type `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "mixed-return-statement" -message = 'Could not infer a precise return type for function `PhpDb\TableGateway\Feature\SequenceFeature::nextSequenceId`. Saw type `mixed`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "possible-method-access-on-null" -message = "Attempting to call a method on `null`." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "possibly-invalid-argument" -message = 'Possible argument type mismatch for argument #2 of `array_search`: expected `array`, but possibly received `PhpDb\Sql\TableIdentifier|array|string`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "unhandled-thrown-type" -message = 'Potentially unhandled exception `PhpDb\Exception\RuntimeException` in `PhpDb\TableGateway\Feature\SequenceFeature::preInsert`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "unused-parameter" -message = "Parameter `$result` is never used." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "unused-parameter" -message = "Parameter `$statement` is never used." -count = 1 - [[issues]] file = "src/TableGateway/TableGateway.php" code = "class-must-be-final" diff --git a/lint-baseline.toml b/lint-baseline.toml index d4aa363a..eadf4081 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -414,12 +414,6 @@ code = "yoda-conditions" message = "Use Yoda condition style for safer comparisons" count = 6 -[[issues]] -file = "src/RowGateway/Feature/FeatureSet.php" -code = "too-many-methods" -message = "Class has too many methods." -count = 1 - [[issues]] file = "src/RowGateway/RowGateway.php" code = "no-else-clause" @@ -972,90 +966,6 @@ code = "too-many-methods" message = "Class has too many methods." count = 1 -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "no-shorthand-ternary" -message = "Use of the shorthand ternary operator." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/EventFeature.php" -code = "too-many-methods" -message = "Class has too many methods." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "kan-defect" -message = "Class has a high kan defect score." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/FeatureSet.php" -code = "too-many-methods" -message = "Class has too many methods." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "no-isset" -message = "Use of the `isset` construct." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/MetadataFeature.php" -code = "no-else-clause" -message = "Avoid `else` clauses." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "no-else-clause" -message = "Avoid `else` clauses." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "no-else-clause" -message = "Avoid `elseif` clauses." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "no-isset" -message = "Use of the `isset` construct." -count = 2 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "no-redundant-use" -message = 'Redundant import: `MetadataFeature` is already in the current namespace `PhpDb\TableGateway\Feature`.' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/RowGatewayFeature.php" -code = "yoda-conditions" -message = "Use Yoda condition style for safer comparisons" -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "strict-behavior" -message = "Call to `array_search` must enforce strict comparison." -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "string-style" -message = "String concatenation can be replaced with interpolation." -count = 3 - -[[issues]] -file = "src/TableGateway/Feature/SequenceFeature.php" -code = "tagged-todo" -message = "TODO should be tagged with (@username) or (#issue)." -count = 1 - [[issues]] file = "src/TableGateway/TableGateway.php" code = "no-shorthand-ternary" diff --git a/mago.toml b/mago.toml index ae9cf5c7..5ef92248 100644 --- a/mago.toml +++ b/mago.toml @@ -15,4 +15,6 @@ baseline = "lint-baseline.toml" baseline = "analyzer-baseline.toml" class-initializers = [ "PhpDb\\ResultSet\\AbstractResultSet::initialize", + "PhpDb\\TableGateway\\Feature\\AbstractFeature::setTableGateway", + "PhpDb\\RowGateway\\Feature\\AbstractFeature::setRowGateway", ] diff --git a/src/Feature/FeatureInterface.php b/src/Feature/FeatureInterface.php index 241a9121..4fcf01da 100644 --- a/src/Feature/FeatureInterface.php +++ b/src/Feature/FeatureInterface.php @@ -4,6 +4,9 @@ namespace PhpDb\Feature; +/** + * @api + */ interface FeatureInterface { /** @return array */ diff --git a/src/RowGateway/Feature/AbstractFeature.php b/src/RowGateway/Feature/AbstractFeature.php index fbf92656..2e4539db 100644 --- a/src/RowGateway/Feature/AbstractFeature.php +++ b/src/RowGateway/Feature/AbstractFeature.php @@ -9,10 +9,14 @@ use PhpDb\RowGateway\Exception; use PhpDb\RowGateway\Exception\RuntimeException; +/** + * @api + */ abstract class AbstractFeature extends AbstractRowGateway implements FeatureInterface { protected AbstractRowGateway $rowGateway; + /** @var array */ protected array $sharedData = []; /** @return array */ diff --git a/src/RowGateway/Feature/FeatureInterface.php b/src/RowGateway/Feature/FeatureInterface.php index d6931334..637c7701 100644 --- a/src/RowGateway/Feature/FeatureInterface.php +++ b/src/RowGateway/Feature/FeatureInterface.php @@ -7,6 +7,9 @@ use PhpDb\Feature\FeatureInterface as BaseFeatureInterface; use PhpDb\RowGateway\AbstractRowGateway; +/** + * @api + */ interface FeatureInterface extends BaseFeatureInterface { public function setRowGateway(AbstractRowGateway $rowGateway): void; diff --git a/src/RowGateway/Feature/FeatureSet.php b/src/RowGateway/Feature/FeatureSet.php index 213e1b58..1e567d23 100644 --- a/src/RowGateway/Feature/FeatureSet.php +++ b/src/RowGateway/Feature/FeatureSet.php @@ -13,15 +13,17 @@ */ class FeatureSet { - final public const APPLY_HALT = 'halt'; + final public const string APPLY_HALT = 'halt'; protected ?AbstractRowGateway $rowGateway = null; /** @var FeatureInterface[] */ protected array $features = []; + /** @var array */ protected array $magicSpecifications = []; + /** @param FeatureInterface[] $features */ public function __construct(array $features = []) { if ([] !== $features) { @@ -38,6 +40,7 @@ public function addFeature(FeatureInterface $feature): static return $this; } + /** @param FeatureInterface[] $features */ public function addFeatures(array $features): static { foreach ($features as $feature) { @@ -46,6 +49,12 @@ public function addFeatures(array $features): static return $this; } + /** + * @param array $args + * + * @mago-expect analysis:string-member-selector + * @mago-expect analysis:mixed-assignment + */ public function apply(string $method, array $args): void { foreach ($this->features as $feature) { @@ -60,31 +69,51 @@ public function apply(string $method, array $args): void } } + /** + * @mago-expect analysis:unused-parameter + * + * @param array $arguments + */ public function callMagicCall(string $method, array $arguments): mixed { return null; } + /** + * @mago-expect analysis:unused-parameter + */ public function callMagicGet(string $property): mixed { return null; } + /** + * @mago-expect analysis:unused-parameter + */ public function callMagicSet(string $property, mixed $value): mixed { return null; } + /** + * @mago-expect analysis:unused-parameter + */ public function canCallMagicCall(string $method): bool { return false; } + /** + * @mago-expect analysis:unused-parameter + */ public function canCallMagicGet(string $property): false { return false; } + /** + * @mago-expect analysis:unused-parameter + */ public function canCallMagicSet(string $property): false { return false; diff --git a/src/TableGateway/AbstractTableGateway.php b/src/TableGateway/AbstractTableGateway.php index 808a1a72..6c0b7da7 100644 --- a/src/TableGateway/AbstractTableGateway.php +++ b/src/TableGateway/AbstractTableGateway.php @@ -31,7 +31,7 @@ /** * @property AdapterInterface $adapter * @property int $lastInsertValue - * @property string $table + * @property TableIdentifier|string|array|null $table */ abstract class AbstractTableGateway implements TableGatewayInterface { diff --git a/src/TableGateway/Feature/AbstractFeature.php b/src/TableGateway/Feature/AbstractFeature.php index 310dd89e..29de0696 100644 --- a/src/TableGateway/Feature/AbstractFeature.php +++ b/src/TableGateway/Feature/AbstractFeature.php @@ -7,10 +7,14 @@ use Override; use PhpDb\TableGateway\AbstractTableGateway; +/** + * @api + */ abstract class AbstractFeature extends AbstractTableGateway implements FeatureInterface { protected AbstractTableGateway $tableGateway; + /** @var array */ protected array $sharedData = []; /** @return array */ diff --git a/src/TableGateway/Feature/EventFeature.php b/src/TableGateway/Feature/EventFeature.php index cac06cd6..a8d095a1 100644 --- a/src/TableGateway/Feature/EventFeature.php +++ b/src/TableGateway/Feature/EventFeature.php @@ -19,6 +19,9 @@ use function get_class; +/** + * @api + */ class EventFeature extends AbstractFeature implements EventFeatureEventsInterface, EventsCapableInterface { protected EventManagerInterface $eventManager; diff --git a/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php b/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php index f76a374c..df747c1d 100644 --- a/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php +++ b/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php @@ -8,12 +8,16 @@ use Override; use PhpDb\TableGateway\AbstractTableGateway; -class TableGatewayEvent implements EventInterface +/** + * @implements EventInterface|object> + */ +final class TableGatewayEvent implements EventInterface { protected ?AbstractTableGateway $target = null; protected ?string $name = null; + /** @var array|object */ protected array|object $params = []; #[Override] @@ -77,6 +81,8 @@ public function setName($name): void * * @param string|int $name * @param mixed $value + * + * @mago-expect analysis:possibly-invalid-array-access */ #[Override] public function setParam($name, $value): void @@ -87,8 +93,10 @@ public function setParam($name, $value): void /** * Set event parameters * - * @param array|object $params + * @param array|object $params * @phpstan-ignore selfOut.type + * + * @mago-expect analysis:unused-template-parameter */ #[Override] public function setParams($params): void @@ -101,6 +109,9 @@ public function setParams($params): void * * @param object|string|null $target * @phpstan-ignore selfOut.type + * + * @mago-expect analysis:unused-template-parameter + * @mago-expect analysis:property-type-coercion */ #[Override] public function setTarget($target): void diff --git a/src/TableGateway/Feature/EventFeatureEventsInterface.php b/src/TableGateway/Feature/EventFeatureEventsInterface.php index 166f0950..26778c4b 100644 --- a/src/TableGateway/Feature/EventFeatureEventsInterface.php +++ b/src/TableGateway/Feature/EventFeatureEventsInterface.php @@ -11,21 +11,23 @@ * into a separate interface that EventFeature implements; the change keeps * backwards compatibility, while simultaneously removing the need to add * another hard dependency to the component. + * + * @api */ interface EventFeatureEventsInterface { - public const EVENT_PRE_INITIALIZE = 'preInitialize'; - public const EVENT_POST_INITIALIZE = 'postInitialize'; + public const string EVENT_PRE_INITIALIZE = 'preInitialize'; + public const string EVENT_POST_INITIALIZE = 'postInitialize'; - public const EVENT_PRE_SELECT = 'preSelect'; - public const EVENT_POST_SELECT = 'postSelect'; + public const string EVENT_PRE_SELECT = 'preSelect'; + public const string EVENT_POST_SELECT = 'postSelect'; - public const EVENT_PRE_INSERT = 'preInsert'; - public const EVENT_POST_INSERT = 'postInsert'; + public const string EVENT_PRE_INSERT = 'preInsert'; + public const string EVENT_POST_INSERT = 'postInsert'; - public const EVENT_PRE_DELETE = 'preDelete'; - public const EVENT_POST_DELETE = 'postDelete'; + public const string EVENT_PRE_DELETE = 'preDelete'; + public const string EVENT_POST_DELETE = 'postDelete'; - public const EVENT_PRE_UPDATE = 'preUpdate'; - public const EVENT_POST_UPDATE = 'postUpdate'; + public const string EVENT_PRE_UPDATE = 'preUpdate'; + public const string EVENT_POST_UPDATE = 'postUpdate'; } diff --git a/src/TableGateway/Feature/FeatureInterface.php b/src/TableGateway/Feature/FeatureInterface.php index 29507148..3959d918 100644 --- a/src/TableGateway/Feature/FeatureInterface.php +++ b/src/TableGateway/Feature/FeatureInterface.php @@ -7,6 +7,9 @@ use PhpDb\Feature\FeatureInterface as BaseFeatureInterface; use PhpDb\TableGateway\AbstractTableGateway; +/** + * @api + */ interface FeatureInterface extends BaseFeatureInterface { public function setTableGateway(AbstractTableGateway $tableGateway): void; diff --git a/src/TableGateway/Feature/FeatureSet.php b/src/TableGateway/Feature/FeatureSet.php index efef7c2d..01c5c8d8 100644 --- a/src/TableGateway/Feature/FeatureSet.php +++ b/src/TableGateway/Feature/FeatureSet.php @@ -9,17 +9,22 @@ use function method_exists; +/** + * @api + */ class FeatureSet { - public const APPLY_HALT = 'halt'; + public const string APPLY_HALT = 'halt'; protected ?AbstractTableGateway $tableGateway = null; /** @var FeatureInterface[] */ protected array $features = []; + /** @var array */ protected array $magicSpecifications = []; + /** @param FeatureInterface[] $features */ public function __construct(array $features = []) { if ([] !== $features) { @@ -36,6 +41,7 @@ public function addFeature(FeatureInterface $feature): static return $this; } + /** @param FeatureInterface[] $features */ public function addFeatures(array $features): static { foreach ($features as $feature) { @@ -44,6 +50,12 @@ public function addFeatures(array $features): static return $this; } + /** + * @param array $args + * + * @mago-expect analysis:string-member-selector + * @mago-expect analysis:mixed-assignment + */ public function apply(string $method, array $args): void { foreach ($this->features as $feature) { @@ -60,6 +72,10 @@ public function apply(string $method, array $args): void /** * Call method of on added feature as though it were a local method + * + * @param array $arguments + * + * @mago-expect analysis:string-member-selector */ public function callMagicCall(string $method, array $arguments): mixed { @@ -72,11 +88,17 @@ public function callMagicCall(string $method, array $arguments): mixed return null; } + /** + * @mago-expect analysis:unused-parameter + */ public function callMagicGet(string $property): mixed { return null; } + /** + * @mago-expect analysis:unused-parameter + */ public function callMagicSet(string $property, mixed $value): mixed { return null; @@ -97,11 +119,17 @@ public function canCallMagicCall(string $method): bool return false; } + /** + * @mago-expect analysis:unused-parameter + */ public function canCallMagicGet(string $property): bool { return false; } + /** + * @mago-expect analysis:unused-parameter + */ public function canCallMagicSet(string $property): bool { return false; diff --git a/src/TableGateway/Feature/GlobalAdapterFeature.php b/src/TableGateway/Feature/GlobalAdapterFeature.php index de5fadeb..16f86cf5 100644 --- a/src/TableGateway/Feature/GlobalAdapterFeature.php +++ b/src/TableGateway/Feature/GlobalAdapterFeature.php @@ -7,6 +7,9 @@ use PhpDb\Adapter\AdapterInterface; use PhpDb\TableGateway\Exception; +/** + * @api + */ class GlobalAdapterFeature extends AbstractFeature { /** @var AdapterInterface[] */ diff --git a/src/TableGateway/Feature/MasterSlaveFeature.php b/src/TableGateway/Feature/MasterSlaveFeature.php index 500e56e6..d08bde5a 100644 --- a/src/TableGateway/Feature/MasterSlaveFeature.php +++ b/src/TableGateway/Feature/MasterSlaveFeature.php @@ -7,7 +7,7 @@ use PhpDb\Adapter\AdapterInterface; use PhpDb\Sql\Sql; -class MasterSlaveFeature extends AbstractFeature +final class MasterSlaveFeature extends AbstractFeature { protected AdapterInterface $slaveAdapter; diff --git a/src/TableGateway/Feature/MetadataFeature.php b/src/TableGateway/Feature/MetadataFeature.php index ac4c2d69..5cf102f0 100644 --- a/src/TableGateway/Feature/MetadataFeature.php +++ b/src/TableGateway/Feature/MetadataFeature.php @@ -13,6 +13,9 @@ use function current; use function is_array; +/** + * @api + */ class MetadataFeature extends AbstractFeature { /** diff --git a/src/TableGateway/Feature/RowGatewayFeature.php b/src/TableGateway/Feature/RowGatewayFeature.php index 7a224145..9fd81ded 100644 --- a/src/TableGateway/Feature/RowGatewayFeature.php +++ b/src/TableGateway/Feature/RowGatewayFeature.php @@ -12,8 +12,9 @@ use function is_string; -class RowGatewayFeature extends AbstractFeature +final class RowGatewayFeature extends AbstractFeature { + /** @var array */ protected array $constructorArguments = []; public function __construct(mixed ...$constructorArguments) diff --git a/src/TableGateway/Feature/SequenceFeature.php b/src/TableGateway/Feature/SequenceFeature.php index 4cbe0091..2a50f392 100644 --- a/src/TableGateway/Feature/SequenceFeature.php +++ b/src/TableGateway/Feature/SequenceFeature.php @@ -11,6 +11,9 @@ use function array_search; +/** + * @api + */ class SequenceFeature extends AbstractFeature { protected string $primaryKeyField; @@ -29,6 +32,8 @@ public function __construct(string $primaryKeyField, string $sequenceName) * Return the most recent value from the specified sequence in the database. * * @throws RuntimeException + * + * @mago-expect analysis:mixed-assignment */ public function lastSequenceId(): int { @@ -56,6 +61,8 @@ public function lastSequenceId(): int * Generate a new value from the specified sequence in the database, and return it. * * @throws RuntimeException + * + * @mago-expect analysis:mixed-assignment */ public function nextSequenceId(): ?int { @@ -78,6 +85,9 @@ public function nextSequenceId(): ?int return $sequence['nextval']; } + /** + * @mago-expect analysis:unused-parameter + */ public function postInsert(StatementInterface $statement, ResultInterface $result): void { if (null !== $this->sequenceValue) { diff --git a/src/TableGateway/TableGateway.php b/src/TableGateway/TableGateway.php index c26604be..d2024437 100644 --- a/src/TableGateway/TableGateway.php +++ b/src/TableGateway/TableGateway.php @@ -15,6 +15,8 @@ class TableGateway extends AbstractTableGateway { /** + * @param Feature\FeatureSet|Feature\FeatureInterface|Feature\FeatureInterface[]|null $features + * * @throws Exception\InvalidArgumentException */ public function __construct( From a004aa8c5a2a928b8f5762bfa61b19088241a944 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 10:20:33 +1000 Subject: [PATCH 2/6] Guard nullable state in the TableGateway features - Make EventFeature::$event non-nullable and assign it explicitly - Resolve GlobalAdapterFeature::getStaticAdapter through a coalesce chain and throw when no adapter is registered - Make MasterSlaveFeature::$masterSql nullable and throw when the gateway has no Sql instance or postInitialize has not run - Throw in SequenceFeature when a statement yields no result, when the sequence returns no usable value and when an insert exposes no arrays - Interpolate the SequenceFeature sequence statements and search insert columns strictly - Resolve the MetadataFeature table through getTable(), throw when it is not a named table, and take the primary key with reset() - Extract RowGatewayFeature::primaryKeyFromMetadata(), flatten the prototype branches and throw when the gateway has no named table - Replace the deprecated setArrayObjectPrototype() calls with setRowPrototype() --- src/TableGateway/Feature/EventFeature.php | 6 +- .../Feature/GlobalAdapterFeature.php | 12 +-- .../Feature/MasterSlaveFeature.php | 24 ++++- src/TableGateway/Feature/MetadataFeature.php | 42 +++++--- .../Feature/RowGatewayFeature.php | 102 ++++++++++++------ src/TableGateway/Feature/SequenceFeature.php | 51 ++++++--- 6 files changed, 163 insertions(+), 74 deletions(-) diff --git a/src/TableGateway/Feature/EventFeature.php b/src/TableGateway/Feature/EventFeature.php index a8d095a1..b153d9e0 100644 --- a/src/TableGateway/Feature/EventFeature.php +++ b/src/TableGateway/Feature/EventFeature.php @@ -26,7 +26,7 @@ class EventFeature extends AbstractFeature implements EventFeatureEventsInterfac { protected EventManagerInterface $eventManager; - protected ?EventFeature\TableGatewayEvent $event; + protected EventFeature\TableGatewayEvent $event; public function __construct( ?EventManagerInterface $eventManager = null, @@ -40,7 +40,9 @@ public function __construct( TableGateway::class, ]); - $this->event = $tableGatewayEvent ?: new EventFeature\TableGatewayEvent(); + $this->event = $tableGatewayEvent instanceof EventFeature\TableGatewayEvent + ? $tableGatewayEvent + : new EventFeature\TableGatewayEvent(); } /** diff --git a/src/TableGateway/Feature/GlobalAdapterFeature.php b/src/TableGateway/Feature/GlobalAdapterFeature.php index 16f86cf5..2e2f8eb0 100644 --- a/src/TableGateway/Feature/GlobalAdapterFeature.php +++ b/src/TableGateway/Feature/GlobalAdapterFeature.php @@ -24,17 +24,13 @@ public static function getStaticAdapter(): AdapterInterface { $class = static::class; - // class specific adapter - if (isset(static::$staticAdapters[$class])) { - return static::$staticAdapters[$class]; - } + $adapter = static::$staticAdapters[$class] ?? static::$staticAdapters[self::class] ?? null; - // default adapter - if (isset(static::$staticAdapters[self::class])) { - return static::$staticAdapters[self::class]; + if (! $adapter instanceof AdapterInterface) { + throw new Exception\RuntimeException('No database adapter was found in the static registry.'); } - throw new Exception\RuntimeException('No database adapter was found in the static registry.'); + return $adapter; } /** diff --git a/src/TableGateway/Feature/MasterSlaveFeature.php b/src/TableGateway/Feature/MasterSlaveFeature.php index d08bde5a..e07eac52 100644 --- a/src/TableGateway/Feature/MasterSlaveFeature.php +++ b/src/TableGateway/Feature/MasterSlaveFeature.php @@ -6,12 +6,13 @@ use PhpDb\Adapter\AdapterInterface; use PhpDb\Sql\Sql; +use PhpDb\TableGateway\Exception; final class MasterSlaveFeature extends AbstractFeature { protected AdapterInterface $slaveAdapter; - protected Sql $masterSql; + protected ?Sql $masterSql = null; protected ?Sql $slaveSql = null; @@ -35,14 +36,23 @@ public function getSlaveSql(): ?Sql /** * after initialization, retrieve the original adapter as "master" + * + * @throws Exception\RuntimeException */ public function postInitialize(): void { - $this->masterSql = $this->tableGateway->sql; + $masterSql = $this->tableGateway->sql; + if (! $masterSql instanceof Sql) { + throw new Exception\RuntimeException( + 'The table gateway must be initialized with a Sql instance before this feature is applied.', + ); + } + + $this->masterSql = $masterSql; if (null === $this->slaveSql) { $this->slaveSql = new Sql( $this->slaveAdapter, - $this->tableGateway->sql->getTable(), + $masterSql->getTable(), ); } } @@ -50,9 +60,17 @@ public function postInitialize(): void /** * postSelect() * Ensure to return to the master adapter + * + * @throws Exception\RuntimeException */ public function postSelect(): void { + if (! $this->masterSql instanceof Sql) { + throw new Exception\RuntimeException( + 'The master Sql instance is not available; postInitialize() has not been run.', + ); + } + $this->tableGateway->sql = $this->masterSql; } diff --git a/src/TableGateway/Feature/MetadataFeature.php b/src/TableGateway/Feature/MetadataFeature.php index 5cf102f0..2bc5423b 100644 --- a/src/TableGateway/Feature/MetadataFeature.php +++ b/src/TableGateway/Feature/MetadataFeature.php @@ -12,6 +12,8 @@ use function count; use function current; use function is_array; +use function is_string; +use function reset; /** * @api @@ -30,28 +32,43 @@ public function __construct( ]; } + /** + * @throws Exception\RuntimeException + */ public function postInitialize(): void { // localize variable for brevity $t = $this->tableGateway; $m = $this->metadata; - $tableGatewayTable = is_array($t->table) ? current($t->table) : $t->table; + $tableGatewayTable = $t->getTable(); + if (is_array($tableGatewayTable)) { + $tableGatewayTable = current($tableGatewayTable); + } - if ($tableGatewayTable instanceof TableIdentifier) { - $table = $tableGatewayTable->getTable(); - $schema = $tableGatewayTable->getSchema(); - } else { - $table = $tableGatewayTable; - $schema = null; + if (! $tableGatewayTable instanceof TableIdentifier && ! is_string($tableGatewayTable)) { + throw new Exception\RuntimeException( + 'The table gateway must reference a named table before metadata can be resolved.', + ); } + $table = $tableGatewayTable instanceof TableIdentifier + ? $tableGatewayTable->getTable() + : $tableGatewayTable; + + $schema = $tableGatewayTable instanceof TableIdentifier + ? $tableGatewayTable->getSchema() + : null; + // get column named $columns = $m->getColumnNames($table, $schema); $t->columns = $columns; // set locally - $this->sharedData['metadata']['columns'] = $columns; + $metadata = $this->sharedData['metadata'] ?? []; + $metadata = is_array($metadata) ? $metadata : []; + $metadata['columns'] = $columns; + $this->sharedData['metadata'] = $metadata; // process primary key only if table is a table; there are no PK constraints on views if (! $m->getTable($table, $schema) instanceof TableObject) { @@ -74,12 +91,9 @@ public function postInitialize(): void } $pkcColumns = $pkc->getColumns(); - if (count($pkcColumns) === 1) { - $primaryKey = $pkcColumns[0]; - } else { - $primaryKey = $pkcColumns; - } + $primaryKey = 1 === count($pkcColumns) ? reset($pkcColumns) : $pkcColumns; - $this->sharedData['metadata']['primaryKey'] = $primaryKey; + $metadata['primaryKey'] = $primaryKey; + $this->sharedData['metadata'] = $metadata; } } diff --git a/src/TableGateway/Feature/RowGatewayFeature.php b/src/TableGateway/Feature/RowGatewayFeature.php index 9fd81ded..3d62f19a 100644 --- a/src/TableGateway/Feature/RowGatewayFeature.php +++ b/src/TableGateway/Feature/RowGatewayFeature.php @@ -7,9 +7,10 @@ use PhpDb\ResultSet\RowPrototypeResultSet; use PhpDb\RowGateway\RowGateway; use PhpDb\RowGateway\RowGatewayInterface; +use PhpDb\Sql\TableIdentifier; use PhpDb\TableGateway\Exception; -use PhpDb\TableGateway\Feature\MetadataFeature; +use function is_array; use function is_string; final class RowGatewayFeature extends AbstractFeature @@ -22,14 +23,13 @@ public function __construct(mixed ...$constructorArguments) $this->constructorArguments = $constructorArguments; } + /** + * @throws Exception\RuntimeException + */ public function postInitialize(): void { - $args = $this->constructorArguments; - - /** @var RowPrototypeResultSet $resultSetPrototype */ $resultSetPrototype = $this->tableGateway->resultSetPrototype; - - if (! $this->tableGateway->resultSetPrototype instanceof RowPrototypeResultSet) { + if (! $resultSetPrototype instanceof RowPrototypeResultSet) { throw new Exception\RuntimeException( 'This feature ' . self::class @@ -38,37 +38,69 @@ public function postInitialize(): void ); } - if (isset($args[0])) { - if (is_string($args[0])) { - $primaryKey = $args[0]; - $rowGatewayPrototype = new RowGateway( - $primaryKey, - $this->tableGateway->table, - $this->tableGateway->adapter, - ); - $resultSetPrototype->setRowPrototype($rowGatewayPrototype); - } elseif ($args[0] instanceof RowGatewayInterface) { - $rowGatewayPrototype = $args[0]; - $resultSetPrototype->setRowPrototype($rowGatewayPrototype); - } - } else { - // get from metadata feature - $metadata = $this->tableGateway->featureSet->getFeatureByClassName( - MetadataFeature::class, + $firstArgument = $this->constructorArguments[0] ?? null; + + if ($firstArgument instanceof RowGatewayInterface) { + $resultSetPrototype->setRowPrototype($firstArgument); + return; + } + + if (null !== $firstArgument && ! is_string($firstArgument)) { + return; + } + + $primaryKey = $firstArgument ?? $this->primaryKeyFromMetadata(); + + $table = $this->tableGateway->table; + if (! is_string($table) && ! $table instanceof TableIdentifier) { + throw new Exception\RuntimeException( + 'The table gateway must reference a named table before a RowGateway prototype can be created.', + ); + } + + $resultSetPrototype->setRowPrototype(new RowGateway( + $primaryKey, + $table, + $this->tableGateway->adapter, + )); + } + + /** + * @return string|array + * + * @throws Exception\RuntimeException + * + * @mago-expect analysis:mixed-assignment + */ + private function primaryKeyFromMetadata(): string|array + { + $featureSet = $this->tableGateway->featureSet; + $metadata = $featureSet instanceof FeatureSet + ? $featureSet->getFeatureByClassName(MetadataFeature::class) + : null; + + $metadataData = $metadata instanceof MetadataFeature + ? $metadata->sharedData['metadata'] ?? null + : null; + + if (null === $metadataData) { + throw new Exception\RuntimeException( + 'No information was provided to the RowGatewayFeature and/or no MetadataFeature could be consulted ' + . 'to find the primary key necessary for RowGateway object creation.', ); - if ($metadata === null || ! isset($metadata->sharedData['metadata'])) { - throw new Exception\RuntimeException( - 'No information was provided to the RowGatewayFeature and/or no MetadataFeature could be consulted ' - . 'to find the primary key necessary for RowGateway object creation.', - ); - } - $primaryKey = $metadata->sharedData['metadata']['primaryKey']; - $rowGatewayPrototype = new RowGateway( - $primaryKey, - $this->tableGateway->table, - $this->tableGateway->adapter, + } + + if (! is_array($metadataData)) { + throw new Exception\RuntimeException('The MetadataFeature did not expose its metadata as an array.'); + } + + $primaryKey = $metadataData['primaryKey'] ?? null; + if (! is_string($primaryKey) && ! is_array($primaryKey)) { + throw new Exception\RuntimeException( + 'The MetadataFeature did not expose a usable primary key for RowGateway object creation.', ); - $resultSetPrototype->setRowPrototype($rowGatewayPrototype); } + + return $primaryKey; } } diff --git a/src/TableGateway/Feature/SequenceFeature.php b/src/TableGateway/Feature/SequenceFeature.php index 2a50f392..2e1bccd6 100644 --- a/src/TableGateway/Feature/SequenceFeature.php +++ b/src/TableGateway/Feature/SequenceFeature.php @@ -10,6 +10,8 @@ use PhpDb\Sql\Insert; use function array_search; +use function is_array; +use function is_int; /** * @api @@ -42,18 +44,25 @@ public function lastSequenceId(): int // todo: Remove string usage $sql = match ($platformName) { - 'Oracle' => 'SELECT ' - . $platform->quoteIdentifier($this->sequenceName) - . '.CURRVAL as "currval" FROM dual', + 'Oracle' => "SELECT {$platform->quoteIdentifier($this->sequenceName)}.CURRVAL as \"currval\" FROM dual", 'PostgreSQL' => 'SELECT LAST_INSERT_ROWID() as "currval"', default => throw new RuntimeException('Unsupported platform for retrieving last sequence id'), }; $statement = $this->tableGateway->adapter->createStatement(); $statement->prepare($sql); - $result = $statement->execute(); + $result = $statement->execute(); + if (! $result instanceof ResultInterface) { + throw new RuntimeException('The sequence statement did not produce a result.'); + } + $sequence = $result->current(); unset($statement, $result); + + if (! is_array($sequence) || ! is_int($sequence['currval'] ?? null)) { + throw new RuntimeException('The sequence did not return a current value.'); + } + return $sequence['currval']; } @@ -70,19 +79,28 @@ public function nextSequenceId(): ?int $platformName = $platform->getName(); $sql = match ($platformName) { - 'Oracle' => 'SELECT ' - . $platform->quoteIdentifier($this->sequenceName) - . '.NEXTVAL as "nextval" FROM dual', - 'PostgreSQL' => 'SELECT NEXTVAL(\'"' . $this->sequenceName . '"\')', + 'Oracle' => "SELECT {$platform->quoteIdentifier($this->sequenceName)}.NEXTVAL as \"nextval\" FROM dual", + 'PostgreSQL' => "SELECT NEXTVAL('\"{$this->sequenceName}\"')", default => throw new RuntimeException('Unsupported platform for retrieving next sequence id'), }; $statement = $this->tableGateway->adapter->createStatement(); $statement->prepare($sql); - $result = $statement->execute(); + $result = $statement->execute(); + if (! $result instanceof ResultInterface) { + throw new RuntimeException('The sequence statement did not produce a result.'); + } + $sequence = $result->current(); unset($statement, $result); - return $sequence['nextval']; + + if (! is_array($sequence)) { + throw new RuntimeException('The sequence did not return a next value.'); + } + + $nextValue = $sequence['nextval'] ?? null; + + return is_int($nextValue) ? $nextValue : null; } /** @@ -95,13 +113,22 @@ public function postInsert(StatementInterface $statement, ResultInterface $resul } } + /** + * @throws RuntimeException + */ public function preInsert(Insert $insert): Insert { $columns = $insert->getRawState('columns'); $values = $insert->getRawState('values'); - $key = array_search($this->primaryKeyField, $columns); + + if (! is_array($columns) || ! is_array($values)) { + throw new RuntimeException('The insert does not expose columns and values as arrays.'); + } + + $key = array_search($this->primaryKeyField, $columns, strict: true); if (false !== $key) { - $this->sequenceValue = $values[$key] ?? null; + $sequenceValue = $values[$key] ?? null; + $this->sequenceValue = is_int($sequenceValue) ? $sequenceValue : null; return $insert; } From 494851ab0e042881530de52e06ee76234b9108d3 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 10:32:31 +1000 Subject: [PATCH 3/6] Modernise the Feature tests - Convert the Feature test methods to #[Test] attributes - Use static:: assertions and strict comparisons throughout - Hoist mock assignments out of the EventFeature call arguments - Assert the MetadataFeature shared data by key rather than isset() - Rename the TestTableGatewayFeature dispatch target to recordCall() - Drop the Feature test entries from the lint baseline --- lint-baseline.toml | 744 ------------------ .../Feature/AbstractFeatureTest.php | 23 +- .../RowGateway/Feature/FeatureSetTest.php | 92 ++- .../Feature/AbstractFeatureTest.php | 21 +- .../EventFeature/TableGatewayEventTest.php | 51 +- .../TableGateway/Feature/EventFeatureTest.php | 191 +++-- .../TableGateway/Feature/FeatureSetTest.php | 111 +-- .../Feature/GlobalAdapterFeatureTest.php | 34 +- .../Feature/MasterSlaveFeatureTest.php | 29 +- .../Feature/MetadataFeatureTest.php | 56 +- .../Feature/RowGatewayFeatureTest.php | 40 +- .../Feature/SequenceFeatureTest.php | 57 +- .../TestAsset/TestTableGatewayFeature.php | 2 +- 13 files changed, 415 insertions(+), 1036 deletions(-) diff --git a/lint-baseline.toml b/lint-baseline.toml index eadf4081..c15e94b7 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -3570,144 +3570,6 @@ code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." count = 21 -[[issues]] -file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 5 - -[[issues]] -file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetMagicMethodSpecificationsReturnsEmptyArray`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetNameReturnsClassName`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsRuntimeException`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetRowGateway`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 20 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureCallsSetRowGatewayWhenRowGatewayIsSet`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeature`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatures`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyCallsMethodOnFeatures`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyHaltsWhenFeatureReturnsHalt`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplySkipsFeatureWithoutMethod`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicCallReturnsNull`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicGetReturnsNull`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicSetReturnsNull`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsFalse`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicGetReturnsFalse`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicSetReturnsFalse`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithEmptyArray`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFeatures`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureByClassNameReturnsFeature`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureByClassNameReturnsNullWhenNotFound`." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetRowGateway`." -count = 1 - [[issues]] file = "test/unit/RowGateway/RowGatewayTest.php" code = "assertion-style" @@ -8532,612 +8394,6 @@ code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." count = 16 -[[issues]] -file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 4 - -[[issues]] -file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetMagicMethodSpecificationsReturnsEmptyArray`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetNameReturnsClassName`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeDoesNothing`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableGateway`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 13 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetParamWithDefault`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPropagationIsStoppedAlwaysReturnsFalse`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNameAndGetName`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParamAndGetParam`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParamsAndGetParams`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParamsWithObject`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTargetAndGetTarget`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testStopPropagation`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" -code = "strict-assertions" -message = "Use strict assertions in PHPUnit tests." -count = 4 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 49 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "no-assign-in-argument" -message = "Avoid assignments in function call arguments." -count = 13 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithDefaults`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetEventManager`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetEvent`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostDelete`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitialize`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInsert`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostSelect`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostUpdate`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreDelete`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInitializeAddsIdentifiersForCustomTableGatewayClass`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInitialize`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsert`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreSelect`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/EventFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreUpdate`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 22 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureThatFeatureDoesNotHaveTableGatewayButFeatureSetHas`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureThatFeatureHasTableGatewayButFeatureSetDoesNotHave`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeaturesReturnsFluentInterface`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyCallsAllFeaturesWhenNoHalt`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyCallsMethodOnFeatures`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyHaltsWhenFeatureReturnsHalt`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyPassesArgumentsToFeatures`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplySkipsFeatureWithoutMethod`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicCallReturnsNullWhenNoFeatureHasMethod`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicCallSucceedsForValidMethodOfAddedFeature`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicGetReturnsNull`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicSetReturnsNull`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsFalseForAddedMethodOfAddedFeature`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsFalseWhenNoFeaturesHaveBeenAdded`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsTrueForAddedMethodOfAddedFeature`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicGetReturnsFalse`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicSetReturnsFalse`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFeatures`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureByClassNameReturnsNullWhenNotFound`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableGateway`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/FeatureSetTest.php" -code = "strict-assertions" -message = "Use strict assertions in PHPUnit tests." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 6 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetStaticAdapterThrowsExceptionWhenNoAdapterSet`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInitializeSetsAdapterOnTableGateway`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStaticAdapter`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSubclassCanSetAndGetOwnAdapter`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSubclassFallsBackToDefaultAdapterWhenNoSpecificAdapterSet`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testSubclassThrowsExceptionWhenNoAdaptersSet`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 5 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSlaveSql`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSlaveAdapter`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithProvidedSlaveSql`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitialize`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostSelect`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreSelect`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 12 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "no-isset" -message = "Use of the `isset` construct." -count = 2 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorSetsInitialSharedData`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetadata`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeSkipsPrimaryKeyCheckIfNotTable`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionWhenNoPrimaryKeyFound`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithArrayTable`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithTableIdentifier`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 6 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorStoresArguments`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorStoresRowGatewayInstance`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNoArguments`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionForNonResultSet`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithMetadataFeature`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithRowGatewayInstance`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithStringPrimaryKey`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" -code = "yoda-conditions" -message = "Use Yoda condition style for safer comparisons" -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "assertion-style" -message = "Inconsistent assertions style." -count = 13 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testLastSequenceIdThrowsExceptionForUnsupportedPlatform`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testLastSequenceId`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testNextSequenceIdThrowsExceptionForUnsupportedPlatform`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testNextSequenceId`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInsertSetsLastInsertValue`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertGeneratesSequenceWhenPrimaryKeyNotInValues`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertReturnsEarlyWhenNextSequenceIdReturnsNull`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertWhenPrimaryKeyAlreadyInValues`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertWithPrimaryKeyColumnButNullValue`." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" -code = "strict-assertions" -message = "Use strict assertions in PHPUnit tests." -count = 5 - -[[issues]] -file = "test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testMethod`." -count = 1 - [[issues]] file = "test/unit/TableGateway/TableGatewayTest.php" code = "assertion-style" diff --git a/test/unit/RowGateway/Feature/AbstractFeatureTest.php b/test/unit/RowGateway/Feature/AbstractFeatureTest.php index ef16e46d..ef552b06 100644 --- a/test/unit/RowGateway/Feature/AbstractFeatureTest.php +++ b/test/unit/RowGateway/Feature/AbstractFeatureTest.php @@ -7,6 +7,7 @@ use PhpDb\RowGateway\AbstractRowGateway; use PhpDb\RowGateway\Exception\RuntimeException; use PhpDb\RowGateway\Feature\AbstractFeature; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionProperty; @@ -15,26 +16,29 @@ class AbstractFeatureTest extends TestCase { private AbstractFeature&MockObject $feature; - public function testGetMagicMethodSpecificationsReturnsEmptyArray(): void + #[Test] + public function getMagicMethodSpecificationsReturnsEmptyArray(): void { $result = $this->feature->getMagicMethodSpecifications(); /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertIsArray($result); - self::assertEmpty($result); + static::assertIsArray($result); + static::assertEmpty($result); } - public function testGetNameReturnsClassName(): void + #[Test] + public function getNameReturnsClassName(): void { $name = $this->feature->getName(); // The mock class name will contain the class name /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertIsString($name); - self::assertNotEmpty($name); + static::assertIsString($name); + static::assertNotEmpty($name); } - public function testInitializeThrowsRuntimeException(): void + #[Test] + public function initializeThrowsRuntimeException(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('This method is not intended to be called on this object.'); @@ -42,7 +46,8 @@ public function testInitializeThrowsRuntimeException(): void $this->feature->initialize(); } - public function testSetRowGateway(): void + #[Test] + public function setRowGateway(): void { /** @var AbstractRowGateway&MockObject $rowGateway */ $rowGateway = $this->getMockBuilder(AbstractRowGateway::class) @@ -55,7 +60,7 @@ public function testSetRowGateway(): void $reflection = new ReflectionProperty(AbstractFeature::class, 'rowGateway'); $value = $reflection->getValue($this->feature); - self::assertSame($rowGateway, $value); + static::assertSame($rowGateway, $value); } protected function setUp(): void diff --git a/test/unit/RowGateway/Feature/FeatureSetTest.php b/test/unit/RowGateway/Feature/FeatureSetTest.php index cc7bc384..f4c17459 100644 --- a/test/unit/RowGateway/Feature/FeatureSetTest.php +++ b/test/unit/RowGateway/Feature/FeatureSetTest.php @@ -8,23 +8,26 @@ use PhpDb\RowGateway\Feature\AbstractFeature; use PhpDb\RowGateway\Feature\FeatureSet; use PhpDbTest\RowGateway\Feature\TestAsset\TestRowGatewayFeature; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class FeatureSetTest extends TestCase { - public function testAddFeature(): void + #[Test] + public function addFeature(): void { $feature = $this->createMock(AbstractFeature::class); $featureSet = new FeatureSet(); $result = $featureSet->addFeature($feature); - self::assertSame($featureSet, $result); - self::assertSame($feature, $featureSet->getFeatureByClassName(AbstractFeature::class)); + static::assertSame($featureSet, $result); + static::assertSame($feature, $featureSet->getFeatureByClassName(AbstractFeature::class)); } - public function testAddFeatureCallsSetRowGatewayWhenRowGatewayIsSet(): void + #[Test] + public function addFeatureCallsSetRowGatewayWhenRowGatewayIsSet(): void { /** @var AbstractRowGateway&MockObject $rowGateway */ $rowGateway = $this->getMockBuilder(AbstractRowGateway::class) @@ -41,7 +44,8 @@ public function testAddFeatureCallsSetRowGatewayWhenRowGatewayIsSet(): void $featureSet->addFeature($feature); } - public function testAddFeatures(): void + #[Test] + public function addFeatures(): void { $feature1 = $this->createMock(AbstractFeature::class); $feature2 = $this->createMock(AbstractFeature::class); @@ -49,22 +53,24 @@ public function testAddFeatures(): void $featureSet = new FeatureSet(); $result = $featureSet->addFeatures([$feature1, $feature2]); - self::assertSame($featureSet, $result); - self::assertSame($feature1, $featureSet->getFeatureByClassName(AbstractFeature::class)); + static::assertSame($featureSet, $result); + static::assertSame($feature1, $featureSet->getFeatureByClassName(AbstractFeature::class)); } - public function testApplyCallsMethodOnFeatures(): void + #[Test] + public function applyCallsMethodOnFeatures(): void { $feature = new TestRowGatewayFeature(); $featureSet = new FeatureSet([$feature]); $featureSet->apply('preInitialize', ['arg1', 'arg2']); - self::assertTrue($feature->called); - self::assertEquals(['arg1', 'arg2'], $feature->receivedArgs); + static::assertTrue($feature->called); + static::assertEquals(['arg1', 'arg2'], $feature->receivedArgs); } - public function testApplyHaltsWhenFeatureReturnsHalt(): void + #[Test] + public function applyHaltsWhenFeatureReturnsHalt(): void { $feature1 = new TestRowGatewayFeature(); $feature1->returnValue = FeatureSet::APPLY_HALT; @@ -74,11 +80,12 @@ public function testApplyHaltsWhenFeatureReturnsHalt(): void $featureSet = new FeatureSet([$feature1, $feature2]); $featureSet->apply('preInitialize', []); - self::assertTrue($feature1->called); - self::assertFalse($feature2->called); + static::assertTrue($feature1->called); + static::assertFalse($feature2->called); } - public function testApplySkipsFeatureWithoutMethod(): void + #[Test] + public function applySkipsFeatureWithoutMethod(): void { $feature = $this->createMock(AbstractFeature::class); @@ -86,80 +93,91 @@ public function testApplySkipsFeatureWithoutMethod(): void $featureSet->apply('nonExistentMethod', []); /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); + static::assertTrue(true); } - public function testCallMagicCallReturnsNull(): void + #[Test] + public function callMagicCallReturnsNull(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicCall('method', [])); + static::assertNull($featureSet->callMagicCall('method', [])); } - public function testCallMagicGetReturnsNull(): void + #[Test] + public function callMagicGetReturnsNull(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicGet('property')); + static::assertNull($featureSet->callMagicGet('property')); } - public function testCallMagicSetReturnsNull(): void + #[Test] + public function callMagicSetReturnsNull(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicSet('property', 'value')); + static::assertNull($featureSet->callMagicSet('property', 'value')); } - public function testCanCallMagicCallReturnsFalse(): void + #[Test] + public function canCallMagicCallReturnsFalse(): void { $featureSet = new FeatureSet(); - self::assertFalse($featureSet->canCallMagicCall('method')); + static::assertFalse($featureSet->canCallMagicCall('method')); } - public function testCanCallMagicGetReturnsFalse(): void + #[Test] + public function canCallMagicGetReturnsFalse(): void { $featureSet = new FeatureSet(); /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($featureSet->canCallMagicGet('property')); + static::assertFalse($featureSet->canCallMagicGet('property')); } - public function testCanCallMagicSetReturnsFalse(): void + #[Test] + public function canCallMagicSetReturnsFalse(): void { $featureSet = new FeatureSet(); /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($featureSet->canCallMagicSet('property')); + static::assertFalse($featureSet->canCallMagicSet('property')); } - public function testConstructorWithEmptyArray(): void + #[Test] + public function constructorWithEmptyArray(): void { $featureSet = new FeatureSet(); - self::assertInstanceOf(FeatureSet::class, $featureSet); + static::assertInstanceOf(FeatureSet::class, $featureSet); } - public function testConstructorWithFeatures(): void + #[Test] + public function constructorWithFeatures(): void { $feature = $this->createMock(AbstractFeature::class); $featureSet = new FeatureSet([$feature]); - self::assertInstanceOf(FeatureSet::class, $featureSet); + static::assertInstanceOf(FeatureSet::class, $featureSet); } - public function testGetFeatureByClassNameReturnsFeature(): void + #[Test] + public function getFeatureByClassNameReturnsFeature(): void { $feature = $this->createMock(AbstractFeature::class); $featureSet = new FeatureSet([$feature]); $result = $featureSet->getFeatureByClassName(AbstractFeature::class); - self::assertSame($feature, $result); + static::assertSame($feature, $result); } - public function testGetFeatureByClassNameReturnsNullWhenNotFound(): void + #[Test] + public function getFeatureByClassNameReturnsNullWhenNotFound(): void { $featureSet = new FeatureSet(); $result = $featureSet->getFeatureByClassName(AbstractFeature::class); - self::assertNull($result); + static::assertNull($result); } - public function testSetRowGateway(): void + #[Test] + public function setRowGateway(): void { /** @var AbstractRowGateway&MockObject $rowGateway */ $rowGateway = $this->getMockBuilder(AbstractRowGateway::class) @@ -174,6 +192,6 @@ public function testSetRowGateway(): void $featureSet = new FeatureSet([$feature]); $result = $featureSet->setRowGateway($rowGateway); - self::assertSame($featureSet, $result); + static::assertSame($featureSet, $result); } } diff --git a/test/unit/TableGateway/Feature/AbstractFeatureTest.php b/test/unit/TableGateway/Feature/AbstractFeatureTest.php index fe896a97..b028d18d 100644 --- a/test/unit/TableGateway/Feature/AbstractFeatureTest.php +++ b/test/unit/TableGateway/Feature/AbstractFeatureTest.php @@ -6,6 +6,7 @@ use PhpDb\TableGateway\AbstractTableGateway; use PhpDb\TableGateway\Feature\AbstractFeature; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionProperty; @@ -14,30 +15,34 @@ class AbstractFeatureTest extends TestCase { private AbstractFeature&MockObject $feature; - public function testGetMagicMethodSpecificationsReturnsEmptyArray(): void + #[Test] + public function getMagicMethodSpecificationsReturnsEmptyArray(): void { $result = $this->feature->getMagicMethodSpecifications(); - self::assertEmpty($result); + static::assertEmpty($result); } - public function testGetNameReturnsClassName(): void + #[Test] + public function getNameReturnsClassName(): void { $name = $this->feature->getName(); - self::assertNotEmpty($name); + static::assertNotEmpty($name); } - public function testInitializeDoesNothing(): void + #[Test] + public function initializeDoesNothing(): void { // initialize() is a no-op, just verify it doesn't throw $this->feature->initialize(); /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); + static::assertTrue(true); } - public function testSetTableGateway(): void + #[Test] + public function setTableGateway(): void { /** @var AbstractTableGateway&MockObject $tableGateway */ $tableGateway = $this->getMockBuilder(AbstractTableGateway::class) @@ -49,7 +54,7 @@ public function testSetTableGateway(): void $reflection = new ReflectionProperty(AbstractFeature::class, 'tableGateway'); $value = $reflection->getValue($this->feature); - self::assertSame($tableGateway, $value); + static::assertSame($tableGateway, $value); } protected function setUp(): void diff --git a/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php b/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php index 62d93496..7b87f52a 100644 --- a/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php +++ b/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php @@ -6,6 +6,7 @@ use PhpDb\TableGateway\AbstractTableGateway; use PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use stdClass; @@ -14,65 +15,72 @@ class TableGatewayEventTest extends TestCase { private TableGatewayEvent $event; - public function testGetParamWithDefault(): void + #[Test] + public function getParamWithDefault(): void { $result = $this->event->getParam('nonExistent', 'defaultValue'); - self::assertEquals('defaultValue', $result); + static::assertSame('defaultValue', $result); } - public function testPropagationIsStoppedAlwaysReturnsFalse(): void + #[Test] + public function propagationIsStoppedAlwaysReturnsFalse(): void { /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($this->event->propagationIsStopped()); + static::assertFalse($this->event->propagationIsStopped()); $this->event->stopPropagation(true); // Still returns false as per implementation /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($this->event->propagationIsStopped()); + static::assertFalse($this->event->propagationIsStopped()); } - public function testSetNameAndGetName(): void + #[Test] + public function setNameAndGetName(): void { - self::assertNull($this->event->getName()); + static::assertNull($this->event->getName()); $this->event->setName('test.event'); - self::assertEquals('test.event', $this->event->getName()); + static::assertSame('test.event', $this->event->getName()); } - public function testSetParamAndGetParam(): void + #[Test] + public function setParamAndGetParam(): void { - self::assertNull($this->event->getParam('unknown')); - self::assertEquals('default', $this->event->getParam('unknown', 'default')); + static::assertNull($this->event->getParam('unknown')); + static::assertSame('default', $this->event->getParam('unknown', 'default')); $this->event->setParam('myParam', 'myValue'); - self::assertEquals('myValue', $this->event->getParam('myParam')); + static::assertSame('myValue', $this->event->getParam('myParam')); } - public function testSetParamsAndGetParams(): void + #[Test] + public function setParamsAndGetParams(): void { - self::assertEquals([], $this->event->getParams()); + static::assertEquals([], $this->event->getParams()); $params = ['key1' => 'value1', 'key2' => 'value2']; $this->event->setParams($params); - self::assertEquals($params, $this->event->getParams()); + static::assertEquals($params, $this->event->getParams()); } - public function testSetParamsWithObject(): void + #[Test] + public function setParamsWithObject(): void { $params = new stdClass(); $params->key = 'value'; $this->event->setParams($params); - self::assertSame($params, $this->event->getParams()); + static::assertSame($params, $this->event->getParams()); } - public function testSetTargetAndGetTarget(): void + #[Test] + public function setTargetAndGetTarget(): void { /** @var AbstractTableGateway&MockObject $tableGateway */ $tableGateway = $this->getMockBuilder(AbstractTableGateway::class) @@ -81,17 +89,18 @@ public function testSetTargetAndGetTarget(): void $this->event->setTarget($tableGateway); - self::assertSame($tableGateway, $this->event->getTarget()); + static::assertSame($tableGateway, $this->event->getTarget()); } - public function testStopPropagation(): void + #[Test] + public function stopPropagation(): void { // stopPropagation should do nothing, just ensure it doesn't throw $this->event->stopPropagation(true); $this->event->stopPropagation(false); /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($this->event->propagationIsStopped()); + static::assertFalse($this->event->propagationIsStopped()); } protected function setUp(): void diff --git a/test/unit/TableGateway/Feature/EventFeatureTest.php b/test/unit/TableGateway/Feature/EventFeatureTest.php index d47ded98..105a39c2 100644 --- a/test/unit/TableGateway/Feature/EventFeatureTest.php +++ b/test/unit/TableGateway/Feature/EventFeatureTest.php @@ -18,6 +18,7 @@ use PhpDb\TableGateway\Feature\EventFeatureEventsInterface; use PhpDb\TableGateway\TableGateway; use PhpDbTest\TableGateway\Feature\TestAsset\TestTableGateway; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -32,25 +33,29 @@ final class EventFeatureTest extends TestCase protected TableGateway&MockObject $tableGateway; - public function testConstructorWithDefaults(): void + #[Test] + public function constructorWithDefaults(): void { $feature = new EventFeature(); - self::assertInstanceOf(EventManagerInterface::class, $feature->getEventManager()); - self::assertInstanceOf(EventFeature\TableGatewayEvent::class, $feature->getEvent()); + static::assertInstanceOf(EventManagerInterface::class, $feature->getEventManager()); + static::assertInstanceOf(EventFeature\TableGatewayEvent::class, $feature->getEvent()); } - public function testGetEvent(): void + #[Test] + public function getEvent(): void { - self::assertSame($this->event, $this->feature->getEvent()); + static::assertSame($this->event, $this->feature->getEvent()); } - public function testGetEventManager(): void + #[Test] + public function getEventManager(): void { - self::assertSame($this->eventManager, $this->feature->getEventManager()); + static::assertSame($this->eventManager, $this->feature->getEventManager()); } - public function testPostDelete(): void + #[Test] + public function postDelete(): void { $closureHasRun = false; @@ -64,18 +69,19 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->postDelete( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock(), - ); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_DELETE, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(); + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + + $this->feature->postDelete($stmt, $result); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_POST_DELETE, $event->getName()); + static::assertSame($stmt, $event->getParam('statement')); + static::assertSame($result, $event->getParam('result')); } - public function testPostInitialize(): void + #[Test] + public function postInitialize(): void { $closureHasRun = false; @@ -90,12 +96,13 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even ); $this->feature->postInitialize(); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_INITIALIZE, $event->getName()); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_POST_INITIALIZE, $event->getName()); } - public function testPostInsert(): void + #[Test] + public function postInsert(): void { $closureHasRun = false; @@ -109,18 +116,19 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->postInsert( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock(), - ); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_INSERT, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(); + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + + $this->feature->postInsert($stmt, $result); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_POST_INSERT, $event->getName()); + static::assertSame($stmt, $event->getParam('statement')); + static::assertSame($result, $event->getParam('result')); } - public function testPostSelect(): void + #[Test] + public function postSelect(): void { $closureHasRun = false; @@ -134,20 +142,21 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->postSelect( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock(), - $resultset = $this->getMockBuilder(ResultSet::class)->getMock(), - ); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_SELECT, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); - self::assertSame($resultset, $event->getParam('result_set')); + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(); + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + $resultset = $this->getMockBuilder(ResultSet::class)->getMock(); + + $this->feature->postSelect($stmt, $result, $resultset); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_POST_SELECT, $event->getName()); + static::assertSame($stmt, $event->getParam('statement')); + static::assertSame($result, $event->getParam('result')); + static::assertSame($resultset, $event->getParam('result_set')); } - public function testPostUpdate(): void + #[Test] + public function postUpdate(): void { $closureHasRun = false; @@ -161,18 +170,19 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->postUpdate( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock(), - ); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_UPDATE, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(); + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + + $this->feature->postUpdate($stmt, $result); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_POST_UPDATE, $event->getName()); + static::assertSame($stmt, $event->getParam('statement')); + static::assertSame($result, $event->getParam('result')); } - public function testPreDelete(): void + #[Test] + public function preDelete(): void { $closureHasRun = false; @@ -186,14 +196,17 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->preDelete($delete = $this->getMockBuilder(Delete::class)->getMock()); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_DELETE, $event->getName()); - self::assertSame($delete, $event->getParam('delete')); + $delete = $this->getMockBuilder(Delete::class)->getMock(); + + $this->feature->preDelete($delete); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_PRE_DELETE, $event->getName()); + static::assertSame($delete, $event->getParam('delete')); } - public function testPreInitialize(): void + #[Test] + public function preInitialize(): void { $closureHasRun = false; @@ -208,15 +221,16 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even ); $this->feature->preInitialize(); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, $event->getName()); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, $event->getName()); } /** * @throws Exception */ - public function testPreInitializeAddsIdentifiersForCustomTableGatewayClass(): void + #[Test] + public function preInitializeAddsIdentifiersForCustomTableGatewayClass(): void { // Create a custom subclass of TableGateway (using anonymous class) $customTableGateway = new TestTableGateway(); @@ -232,11 +246,12 @@ public function testPreInitializeAddsIdentifiersForCustomTableGatewayClass(): vo $identifiers = $eventManager->getIdentifiers(); // Should contain both TableGateway::class and the anonymous class name - self::assertContains(TableGateway::class, $identifiers); - self::assertContains($customTableGateway::class, $identifiers); + static::assertContains(TableGateway::class, $identifiers); + static::assertContains($customTableGateway::class, $identifiers); } - public function testPreInsert(): void + #[Test] + public function preInsert(): void { $closureHasRun = false; @@ -250,14 +265,17 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->preInsert($insert = $this->getMockBuilder(Insert::class)->getMock()); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INSERT, $event->getName()); - self::assertSame($insert, $event->getParam('insert')); + $insert = $this->getMockBuilder(Insert::class)->getMock(); + + $this->feature->preInsert($insert); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INSERT, $event->getName()); + static::assertSame($insert, $event->getParam('insert')); } - public function testPreSelect(): void + #[Test] + public function preSelect(): void { $closureHasRun = false; @@ -271,14 +289,17 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->preSelect($select = $this->getMockBuilder(Select::class)->getMock()); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_SELECT, $event->getName()); - self::assertSame($select, $event->getParam('select')); + $select = $this->getMockBuilder(Select::class)->getMock(); + + $this->feature->preSelect($select); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_PRE_SELECT, $event->getName()); + static::assertSame($select, $event->getParam('select')); } - public function testPreUpdate(): void + #[Test] + public function preUpdate(): void { $closureHasRun = false; @@ -292,11 +313,13 @@ static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$even }, ); - $this->feature->preUpdate($update = $this->getMockBuilder(Update::class)->getMock()); - self::assertTrue($closureHasRun); - self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_UPDATE, $event->getName()); - self::assertSame($update, $event->getParam('update')); + $update = $this->getMockBuilder(Update::class)->getMock(); + + $this->feature->preUpdate($update); + static::assertTrue($closureHasRun); + static::assertInstanceOf(TableGateway::class, $event->getTarget()); + static::assertEquals(EventFeatureEventsInterface::EVENT_PRE_UPDATE, $event->getName()); + static::assertSame($update, $event->getParam('update')); } /** diff --git a/test/unit/TableGateway/Feature/FeatureSetTest.php b/test/unit/TableGateway/Feature/FeatureSetTest.php index ac2fd43a..45cd93c6 100644 --- a/test/unit/TableGateway/Feature/FeatureSetTest.php +++ b/test/unit/TableGateway/Feature/FeatureSetTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; @@ -39,7 +40,8 @@ #[CoversMethod(FeatureSet::class, 'callMagicCall')] class FeatureSetTest extends TestCase { - public function testAddFeaturesReturnsFluentInterface(): void + #[Test] + public function addFeaturesReturnsFluentInterface(): void { $feature1 = new SequenceFeature('id', 'seq1'); $feature2 = new SequenceFeature('id', 'seq2'); @@ -47,15 +49,16 @@ public function testAddFeaturesReturnsFluentInterface(): void $featureSet = new FeatureSet(); $result = $featureSet->addFeatures([$feature1, $feature2]); - self::assertSame($featureSet, $result); + static::assertSame($featureSet, $result); } /** * @cover FeatureSet::addFeature * @throws Exception */ + #[Test] #[Group('Laminas-4993')] - public function testAddFeatureThatFeatureDoesNotHaveTableGatewayButFeatureSetHas(): void + public function addFeatureThatFeatureDoesNotHaveTableGatewayButFeatureSetHas(): void { $mockMasterAdapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); @@ -80,15 +83,16 @@ public function testAddFeatureThatFeatureDoesNotHaveTableGatewayButFeatureSetHas $featureSet = new FeatureSet(); $featureSet->setTableGateway($tableGatewayMock); - self::assertInstanceOf(FeatureSet::class, $featureSet->addFeature($feature)); + static::assertInstanceOf(FeatureSet::class, $featureSet->addFeature($feature)); } /** * @cover FeatureSet::addFeature * @throws Exception */ + #[Test] #[Group('Laminas-4993')] - public function testAddFeatureThatFeatureHasTableGatewayButFeatureSetDoesNotHave(): void + public function addFeatureThatFeatureHasTableGatewayButFeatureSetDoesNotHave(): void { $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -105,22 +109,24 @@ public function testAddFeatureThatFeatureHasTableGatewayButFeatureSetDoesNotHave $feature->setTableGateway($tableGatewayMock); $featureSet = new FeatureSet(); - self::assertInstanceOf(FeatureSet::class, $featureSet->addFeature($feature)); + static::assertInstanceOf(FeatureSet::class, $featureSet->addFeature($feature)); } - public function testApplyCallsAllFeaturesWhenNoHalt(): void + #[Test] + public function applyCallsAllFeaturesWhenNoHalt(): void { $feature1 = new TestTableGatewayFeature(); $feature2 = new TestTableGatewayFeature(); $featureSet = new FeatureSet([$feature1, $feature2]); - $featureSet->apply('testMethod', []); + $featureSet->apply('recordCall', []); - self::assertTrue($feature1->called); - self::assertTrue($feature2->called); + static::assertTrue($feature1->called); + static::assertTrue($feature2->called); } - public function testApplyCallsMethodOnFeatures(): void + #[Test] + public function applyCallsMethodOnFeatures(): void { $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class) ->disableOriginalConstructor() @@ -136,10 +142,11 @@ public function testApplyCallsMethodOnFeatures(): void $featureSet->apply('preSelect', []); /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); + static::assertTrue(true); } - public function testApplyHaltsWhenFeatureReturnsHalt(): void + #[Test] + public function applyHaltsWhenFeatureReturnsHalt(): void { $feature1 = new TestTableGatewayFeature(); $feature1->returnValue = FeatureSet::APPLY_HALT; @@ -147,23 +154,25 @@ public function testApplyHaltsWhenFeatureReturnsHalt(): void $feature2 = new TestTableGatewayFeature(); $featureSet = new FeatureSet([$feature1, $feature2]); - $featureSet->apply('testMethod', []); + $featureSet->apply('recordCall', []); - self::assertTrue($feature1->called); - self::assertFalse($feature2->called); + static::assertTrue($feature1->called); + static::assertFalse($feature2->called); } - public function testApplyPassesArgumentsToFeatures(): void + #[Test] + public function applyPassesArgumentsToFeatures(): void { $feature = new TestTableGatewayFeature(); $featureSet = new FeatureSet([$feature]); - $featureSet->apply('testMethod', ['test value']); + $featureSet->apply('recordCall', ['test value']); - self::assertEquals(['test value'], $feature->receivedArgs); + static::assertEquals(['test value'], $feature->receivedArgs); } - public function testApplySkipsFeatureWithoutMethod(): void + #[Test] + public function applySkipsFeatureWithoutMethod(): void { $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet([$feature]); @@ -171,17 +180,19 @@ public function testApplySkipsFeatureWithoutMethod(): void $featureSet->apply('nonExistentMethod', []); /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); + static::assertTrue(true); } - public function testCallMagicCallReturnsNullWhenNoFeatureHasMethod(): void + #[Test] + public function callMagicCallReturnsNullWhenNoFeatureHasMethod(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicCall('nonExistentMethod', [])); + static::assertNull($featureSet->callMagicCall('nonExistentMethod', [])); } - public function testCallMagicCallSucceedsForValidMethodOfAddedFeature(): void + #[Test] + public function callMagicCallSucceedsForValidMethodOfAddedFeature(): void { $feature = new TestTableGatewayFeature(); @@ -190,87 +201,97 @@ public function testCallMagicCallSucceedsForValidMethodOfAddedFeature(): void $result = $featureSet->callMagicCall('customMethod', ['test_value']); - self::assertEquals('result: test_value', $result); + static::assertSame('result: test_value', $result); } - public function testCallMagicGetReturnsNull(): void + #[Test] + public function callMagicGetReturnsNull(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicGet('property')); + static::assertNull($featureSet->callMagicGet('property')); } - public function testCallMagicSetReturnsNull(): void + #[Test] + public function callMagicSetReturnsNull(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicSet('property', 'value')); + static::assertNull($featureSet->callMagicSet('property', 'value')); } - public function testCanCallMagicCallReturnsFalseForAddedMethodOfAddedFeature(): void + #[Test] + public function canCallMagicCallReturnsFalseForAddedMethodOfAddedFeature(): void { $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet(); $featureSet->addFeature($feature); - self::assertFalse( + static::assertFalse( $featureSet->canCallMagicCall('postInitialize'), 'Should have been able to call postInitialize from the MetaData Feature', ); } - public function testCanCallMagicCallReturnsFalseWhenNoFeaturesHaveBeenAdded(): void + #[Test] + public function canCallMagicCallReturnsFalseWhenNoFeaturesHaveBeenAdded(): void { $featureSet = new FeatureSet(); - self::assertFalse( + static::assertFalse( $featureSet->canCallMagicCall('lastSequenceId'), ); } - public function testCanCallMagicCallReturnsTrueForAddedMethodOfAddedFeature(): void + #[Test] + public function canCallMagicCallReturnsTrueForAddedMethodOfAddedFeature(): void { $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet(); $featureSet->addFeature($feature); - self::assertTrue( + static::assertTrue( $featureSet->canCallMagicCall('lastSequenceId'), 'Should have been able to call lastSequenceId from the Sequence Feature', ); } - public function testCanCallMagicGetReturnsFalse(): void + #[Test] + public function canCallMagicGetReturnsFalse(): void { $featureSet = new FeatureSet(); - self::assertFalse($featureSet->canCallMagicGet('property')); + static::assertFalse($featureSet->canCallMagicGet('property')); } - public function testCanCallMagicSetReturnsFalse(): void + #[Test] + public function canCallMagicSetReturnsFalse(): void { $featureSet = new FeatureSet(); - self::assertFalse($featureSet->canCallMagicSet('property')); + static::assertFalse($featureSet->canCallMagicSet('property')); } - public function testConstructorWithFeatures(): void + #[Test] + public function constructorWithFeatures(): void { $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet([$feature]); - self::assertSame($feature, $featureSet->getFeatureByClassName(SequenceFeature::class)); + static::assertSame($feature, $featureSet->getFeatureByClassName(SequenceFeature::class)); } - public function testGetFeatureByClassNameReturnsNullWhenNotFound(): void + #[Test] + public function getFeatureByClassNameReturnsNullWhenNotFound(): void { $featureSet = new FeatureSet(); $result = $featureSet->getFeatureByClassName(SequenceFeature::class); - self::assertNull($result); + static::assertNull($result); } - public function testSetTableGateway(): void + #[Test] + public function setTableGateway(): void { $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class) ->disableOriginalConstructor() @@ -281,6 +302,6 @@ public function testSetTableGateway(): void $result = $featureSet->setTableGateway($tableGatewayMock); - self::assertSame($featureSet, $result); + static::assertSame($featureSet, $result); } } diff --git a/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php b/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php index de9b5306..c3ff8252 100644 --- a/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php +++ b/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php @@ -9,13 +9,15 @@ use PhpDb\TableGateway\Exception\RuntimeException; use PhpDb\TableGateway\Feature\GlobalAdapterFeature; use PhpDbTest\TableGateway\Feature\TestAsset\TestGlobalAdapterFeatureSubclass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionProperty; class GlobalAdapterFeatureTest extends TestCase { - public function testGetStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet(): void + #[Test] + public function getStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet(): void { $adapter = $this->createMock(AdapterInterface::class); @@ -25,10 +27,11 @@ public function testGetStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet // Get adapter should return the default adapter $result = GlobalAdapterFeature::getStaticAdapter(); - self::assertSame($adapter, $result); + static::assertSame($adapter, $result); } - public function testGetStaticAdapterThrowsExceptionWhenNoAdapterSet(): void + #[Test] + public function getStaticAdapterThrowsExceptionWhenNoAdapterSet(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('No database adapter was found in the static registry.'); @@ -36,7 +39,8 @@ public function testGetStaticAdapterThrowsExceptionWhenNoAdapterSet(): void GlobalAdapterFeature::getStaticAdapter(); } - public function testPreInitializeSetsAdapterOnTableGateway(): void + #[Test] + public function preInitializeSetsAdapterOnTableGateway(): void { $adapter = $this->createMock(AdapterInterface::class); GlobalAdapterFeature::setStaticAdapter($adapter); @@ -55,20 +59,22 @@ public function testPreInitializeSetsAdapterOnTableGateway(): void $reflection = new ReflectionProperty(AbstractTableGateway::class, 'adapter'); $result = $reflection->getValue($tableGatewayMock); - self::assertSame($adapter, $result); + static::assertSame($adapter, $result); } - public function testSetStaticAdapter(): void + #[Test] + public function setStaticAdapter(): void { $adapter = $this->createMock(AdapterInterface::class); GlobalAdapterFeature::setStaticAdapter($adapter); $result = GlobalAdapterFeature::getStaticAdapter(); - self::assertSame($adapter, $result); + static::assertSame($adapter, $result); } - public function testSubclassCanSetAndGetOwnAdapter(): void + #[Test] + public function subclassCanSetAndGetOwnAdapter(): void { $baseAdapter = $this->createMock(AdapterInterface::class); $subclassAdapter = $this->createMock(AdapterInterface::class); @@ -80,13 +86,14 @@ public function testSubclassCanSetAndGetOwnAdapter(): void TestGlobalAdapterFeatureSubclass::setStaticAdapter($subclassAdapter); // Base class should return base adapter - self::assertSame($baseAdapter, GlobalAdapterFeature::getStaticAdapter()); + static::assertSame($baseAdapter, GlobalAdapterFeature::getStaticAdapter()); // Subclass should return its own adapter - self::assertSame($subclassAdapter, TestGlobalAdapterFeatureSubclass::getStaticAdapter()); + static::assertSame($subclassAdapter, TestGlobalAdapterFeatureSubclass::getStaticAdapter()); } - public function testSubclassFallsBackToDefaultAdapterWhenNoSpecificAdapterSet(): void + #[Test] + public function subclassFallsBackToDefaultAdapterWhenNoSpecificAdapterSet(): void { $defaultAdapter = $this->createMock(AdapterInterface::class); @@ -96,10 +103,11 @@ public function testSubclassFallsBackToDefaultAdapterWhenNoSpecificAdapterSet(): // Subclass should fall back to default adapter $result = TestGlobalAdapterFeatureSubclass::getStaticAdapter(); - self::assertSame($defaultAdapter, $result); + static::assertSame($defaultAdapter, $result); } - public function testSubclassThrowsExceptionWhenNoAdaptersSet(): void + #[Test] + public function subclassThrowsExceptionWhenNoAdaptersSet(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('No database adapter was found in the static registry.'); diff --git a/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php b/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php index efc845a9..ad331e87 100644 --- a/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php +++ b/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php @@ -13,6 +13,7 @@ use PhpDb\Sql\Sql; use PhpDb\TableGateway\Feature\MasterSlaveFeature; use PhpDb\TableGateway\TableGateway; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -28,36 +29,40 @@ final class MasterSlaveFeatureTest extends TestCase /** * @throws Exception */ - public function testConstructorWithSlaveSql(): void + #[Test] + public function constructorWithSlaveSql(): void { $slaveSql = new Sql($this->mockSlaveAdapter, 'foo'); $feature = new MasterSlaveFeature($this->mockSlaveAdapter, $slaveSql); - self::assertSame($slaveSql, $feature->getSlaveSql()); + static::assertSame($slaveSql, $feature->getSlaveSql()); } - public function testGetSlaveAdapter(): void + #[Test] + public function getSlaveAdapter(): void { - self::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveAdapter()); + static::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveAdapter()); } /** * @throws Exception */ - public function testPostInitialize(): void + #[Test] + public function postInitialize(): void { $this->getMockBuilder(TableGateway::class) ->setConstructorArgs(['foo', $this->mockMasterAdapter, $this->feature]) ->onlyMethods([]) ->getMock(); // postInitialize is run - self::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveSql()->getAdapter()); + static::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveSql()->getAdapter()); } /** * @throws Exception */ - public function testPostInitializeWithProvidedSlaveSql(): void + #[Test] + public function postInitializeWithProvidedSlaveSql(): void { $slaveSql = new Sql($this->mockSlaveAdapter, 'foo'); $feature = new MasterSlaveFeature($this->mockSlaveAdapter, $slaveSql); @@ -68,13 +73,14 @@ public function testPostInitializeWithProvidedSlaveSql(): void ->getMock(); // The provided slaveSql should be used instead of creating a new one - self::assertSame($slaveSql, $feature->getSlaveSql()); + static::assertSame($slaveSql, $feature->getSlaveSql()); } /** * @throws Exception */ - public function testPostSelect(): void + #[Test] + public function postSelect(): void { $table = $this->getMockBuilder(TableGateway::class) ->setConstructorArgs(['foo', $this->mockMasterAdapter, $this->feature]) @@ -98,13 +104,14 @@ public function testPostSelect(): void $table->select('foo = bar'); // test that the sql object is restored - self::assertSame($masterSql, $table->getSql()); + static::assertSame($masterSql, $table->getSql()); } /** * @throws Exception */ - public function testPreSelect(): void + #[Test] + public function preSelect(): void { $this->expectNotToPerformAssertions(); diff --git a/test/unit/TableGateway/Feature/MetadataFeatureTest.php b/test/unit/TableGateway/Feature/MetadataFeatureTest.php index 71443c45..04a3ec1b 100644 --- a/test/unit/TableGateway/Feature/MetadataFeatureTest.php +++ b/test/unit/TableGateway/Feature/MetadataFeatureTest.php @@ -14,6 +14,7 @@ use PhpDb\TableGateway\Feature\MetadataFeature; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -23,7 +24,8 @@ #[RequiresPhp('<= 8.6')] class MetadataFeatureTest extends TestCase { - public function testConstructorSetsInitialSharedData(): void + #[Test] + public function constructorSetsInitialSharedData(): void { $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); $feature = new MetadataFeature($metadataMock); @@ -31,17 +33,18 @@ public function testConstructorSetsInitialSharedData(): void $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); $sharedData = $r->getValue($feature); - self::assertIsArray($sharedData); - self::assertArrayHasKey('metadata', $sharedData); - self::assertNull($sharedData['metadata']['primaryKey']); - self::assertEquals([], $sharedData['metadata']['columns']); + static::assertIsArray($sharedData); + static::assertArrayHasKey('metadata', $sharedData); + static::assertNull($sharedData['metadata']['primaryKey']); + static::assertEquals([], $sharedData['metadata']['columns']); } /** * @throws Exception * @throws \Exception */ - public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetadata(): void + #[Test] + public function postInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetadata(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -69,12 +72,11 @@ public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetada $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); $sharedData = $r->getValue($feature); - self::assertIsArray($sharedData); - self::assertTrue( - isset($sharedData['metadata']['primaryKey']), - 'Shared data must have metadata entry for primary key', - ); - self::assertEquals(['composite', 'id'], $sharedData['metadata']['primaryKey']); + static::assertIsArray($sharedData); + static::assertArrayHasKey('metadata', $sharedData); + static::assertIsArray($sharedData['metadata']); + static::assertArrayHasKey('primaryKey', $sharedData['metadata']); + static::assertEquals(['composite', 'id'], $sharedData['metadata']['primaryKey']); } /** @@ -85,7 +87,8 @@ public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetada * @throws Exception * @throws \Exception */ - public function testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata(): void + #[Test] + public function postInitializeRecordsPrimaryKeyColumnToSharedMetadata(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -113,19 +116,19 @@ public function testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata(): voi $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); $sharedData = $r->getValue($feature); - self::assertIsArray($sharedData); - self::assertTrue( - isset($sharedData['metadata']['primaryKey']), - 'Shared data must have metadata entry for primary key', - ); - self::assertSame('id', $sharedData['metadata']['primaryKey']); + static::assertIsArray($sharedData); + static::assertArrayHasKey('metadata', $sharedData); + static::assertIsArray($sharedData['metadata']); + static::assertArrayHasKey('primaryKey', $sharedData['metadata']); + static::assertSame('id', $sharedData['metadata']['primaryKey']); } /** * @throws Exception * @throws \Exception */ - public function testPostInitializeSkipsPrimaryKeyCheckIfNotTable(): void + #[Test] + public function postInitializeSkipsPrimaryKeyCheckIfNotTable(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -151,7 +154,8 @@ public function testPostInitializeSkipsPrimaryKeyCheckIfNotTable(): void * @throws Exception * @throws \Exception */ - public function testPostInitializeThrowsExceptionWhenNoPrimaryKeyFound(): void + #[Test] + public function postInitializeThrowsExceptionWhenNoPrimaryKeyFound(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -182,7 +186,8 @@ public function testPostInitializeThrowsExceptionWhenNoPrimaryKeyFound(): void * @throws Exception * @throws \Exception */ - public function testPostInitializeWithArrayTable(): void + #[Test] + public function postInitializeWithArrayTable(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -213,14 +218,15 @@ public function testPostInitializeWithArrayTable(): void $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); $sharedData = $r->getValue($feature); - self::assertSame('id', $sharedData['metadata']['primaryKey']); + static::assertSame('id', $sharedData['metadata']['primaryKey']); } /** * @throws Exception * @throws \Exception */ - public function testPostInitializeWithTableIdentifier(): void + #[Test] + public function postInitializeWithTableIdentifier(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -256,6 +262,6 @@ public function testPostInitializeWithTableIdentifier(): void $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); $sharedData = $r->getValue($feature); - self::assertSame('id', $sharedData['metadata']['primaryKey']); + static::assertSame('id', $sharedData['metadata']['primaryKey']); } } diff --git a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php index d7f690a1..087e5960 100644 --- a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php +++ b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php @@ -13,13 +13,15 @@ use PhpDb\TableGateway\Feature\FeatureSet; use PhpDb\TableGateway\Feature\MetadataFeature; use PhpDb\TableGateway\Feature\RowGatewayFeature; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionProperty; class RowGatewayFeatureTest extends TestCase { - public function testConstructorStoresArguments(): void + #[Test] + public function constructorStoresArguments(): void { $feature = new RowGatewayFeature('id'); @@ -27,10 +29,11 @@ public function testConstructorStoresArguments(): void $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); $args = $property->getValue($feature); - self::assertEquals(['id'], $args); + static::assertEquals(['id'], $args); } - public function testConstructorStoresRowGatewayInstance(): void + #[Test] + public function constructorStoresRowGatewayInstance(): void { /** @var RowGatewayInterface&MockObject $rowGateway */ $rowGateway = $this->createMock(RowGatewayInterface::class); @@ -41,10 +44,11 @@ public function testConstructorStoresRowGatewayInstance(): void $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); $args = $property->getValue($feature); - self::assertSame($rowGateway, $args[0]); + static::assertSame($rowGateway, $args[0]); } - public function testConstructorWithNoArguments(): void + #[Test] + public function constructorWithNoArguments(): void { $feature = new RowGatewayFeature(); @@ -52,10 +56,11 @@ public function testConstructorWithNoArguments(): void $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); $args = $property->getValue($feature); - self::assertEquals([], $args); + static::assertEquals([], $args); } - public function testPostInitializeThrowsExceptionForNonResultSet(): void + #[Test] + public function postInitializeThrowsExceptionForNonResultSet(): void { $resultSet = $this->createMock(ResultSetInterface::class); $tableGateway = $this->createTableGatewayMock($resultSet); @@ -69,7 +74,8 @@ public function testPostInitializeThrowsExceptionForNonResultSet(): void $feature->postInitialize(); } - public function testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): void + #[Test] + public function postInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): void { $resultSet = $this->createInitialResultSet(); @@ -99,7 +105,8 @@ public function testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): $feature->postInitialize(); } - public function testPostInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey(): void + #[Test] + public function postInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey(): void { $resultSet = $this->createInitialResultSet(); @@ -120,7 +127,8 @@ public function testPostInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey() $feature->postInitialize(); } - public function testPostInitializeWithMetadataFeature(): void + #[Test] + public function postInitializeWithMetadataFeature(): void { $resultSet = $this->createInitialResultSet(); @@ -149,10 +157,11 @@ public function testPostInitializeWithMetadataFeature(): void $feature->postInitialize(); $prototype = $resultSet->getRowPrototype(); - self::assertInstanceOf(RowGatewayInterface::class, $prototype); + static::assertInstanceOf(RowGatewayInterface::class, $prototype); } - public function testPostInitializeWithRowGatewayInstance(): void + #[Test] + public function postInitializeWithRowGatewayInstance(): void { $resultSet = $this->createInitialResultSet(); @@ -166,10 +175,11 @@ public function testPostInitializeWithRowGatewayInstance(): void $feature->postInitialize(); - self::assertSame($rowGateway, $resultSet->getRowPrototype()); + static::assertSame($rowGateway, $resultSet->getRowPrototype()); } - public function testPostInitializeWithStringPrimaryKey(): void + #[Test] + public function postInitializeWithStringPrimaryKey(): void { $resultSet = $this->createInitialResultSet(); $tableGateway = $this->createTableGatewayMock($resultSet); @@ -180,7 +190,7 @@ public function testPostInitializeWithStringPrimaryKey(): void $feature->postInitialize(); $prototype = $resultSet->getRowPrototype(); - self::assertInstanceOf(RowGatewayInterface::class, $prototype); + static::assertInstanceOf(RowGatewayInterface::class, $prototype); } /** diff --git a/test/unit/TableGateway/Feature/SequenceFeatureTest.php b/test/unit/TableGateway/Feature/SequenceFeatureTest.php index b90262fd..0f03a2b0 100644 --- a/test/unit/TableGateway/Feature/SequenceFeatureTest.php +++ b/test/unit/TableGateway/Feature/SequenceFeatureTest.php @@ -15,6 +15,7 @@ use PhpDb\TableGateway\Feature\SequenceFeature; use PhpDb\TableGateway\TableGateway; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -50,18 +51,20 @@ public static function nextSequenceIdProvider(): array ]; } + #[Test] #[DataProvider('lastSequenceIdProvider')] - public function testLastSequenceId(string $platformName): void + public function lastSequenceId(string $platformName): void { $tableGateway = $this->createTableGatewayWithPlatform($platformName, 55); $this->feature->setTableGateway($tableGateway); $result = $this->feature->lastSequenceId(); - self::assertEquals(55, $result); + static::assertSame(55, $result); } - public function testLastSequenceIdThrowsExceptionForUnsupportedPlatform(): void + #[Test] + public function lastSequenceIdThrowsExceptionForUnsupportedPlatform(): void { $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); $this->feature->setTableGateway($tableGateway); @@ -75,8 +78,9 @@ public function testLastSequenceIdThrowsExceptionForUnsupportedPlatform(): void /** * @throws Exception */ + #[Test] #[DataProvider('nextSequenceIdProvider')] - public function testNextSequenceId(string $platformName, string $statementSql): void + public function nextSequenceId(string $platformName, string $statementSql): void { $platform = $this->createMock(PlatformInterface::class); $platform->expects($this->any()) @@ -114,7 +118,8 @@ public function testNextSequenceId(string $platformName, string $statementSql): $this->feature->nextSequenceId(); } - public function testNextSequenceIdThrowsExceptionForUnsupportedPlatform(): void + #[Test] + public function nextSequenceIdThrowsExceptionForUnsupportedPlatform(): void { $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); $this->feature->setTableGateway($tableGateway); @@ -125,7 +130,8 @@ public function testNextSequenceIdThrowsExceptionForUnsupportedPlatform(): void $this->feature->nextSequenceId(); } - public function testPostInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull(): void + #[Test] + public function postInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); $this->feature->setTableGateway($tableGateway); @@ -138,10 +144,11 @@ public function testPostInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull() $this->feature->postInsert($statement, $result); - self::assertEquals(999, $lastInsertValueProp->getValue($tableGateway)); + static::assertSame(999, $lastInsertValueProp->getValue($tableGateway)); } - public function testPostInsertSetsLastInsertValue(): void + #[Test] + public function postInsertSetsLastInsertValue(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL', 123); $this->feature->setTableGateway($tableGateway); @@ -156,10 +163,11 @@ public function testPostInsertSetsLastInsertValue(): void $this->feature->postInsert($statement, $result); - self::assertEquals(123, $tableGateway->lastInsertValue); + static::assertSame(123, $tableGateway->lastInsertValue); } - public function testPreInsertGeneratesSequenceWhenPrimaryKeyNotInValues(): void + #[Test] + public function preInsertGeneratesSequenceWhenPrimaryKeyNotInValues(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL', 99); $this->feature->setTableGateway($tableGateway); @@ -170,16 +178,17 @@ public function testPreInsertGeneratesSequenceWhenPrimaryKeyNotInValues(): void $result = $this->feature->preInsert($insert); - self::assertSame($insert, $result); + static::assertSame($insert, $result); $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertEquals(99, $sequenceValueProp->getValue($this->feature)); + static::assertSame(99, $sequenceValueProp->getValue($this->feature)); $rawState = $insert->getRawState(); - self::assertContains('id', $rawState['columns']); + static::assertContains('id', $rawState['columns']); } - public function testPreInsertReturnsEarlyWhenNextSequenceIdReturnsNull(): void + #[Test] + public function preInsertReturnsEarlyWhenNextSequenceIdReturnsNull(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); @@ -200,16 +209,17 @@ public function testPreInsertReturnsEarlyWhenNextSequenceIdReturnsNull(): void $result = $feature->preInsert($insert); - self::assertSame($insert, $result); + static::assertSame($insert, $result); $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertNull($sequenceValueProp->getValue($feature)); + static::assertNull($sequenceValueProp->getValue($feature)); $rawState = $insert->getRawState(); - self::assertNotContains('id', $rawState['columns']); + static::assertNotContains('id', $rawState['columns']); } - public function testPreInsertWhenPrimaryKeyAlreadyInValues(): void + #[Test] + public function preInsertWhenPrimaryKeyAlreadyInValues(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); $this->feature->setTableGateway($tableGateway); @@ -220,13 +230,14 @@ public function testPreInsertWhenPrimaryKeyAlreadyInValues(): void $result = $this->feature->preInsert($insert); - self::assertSame($insert, $result); + static::assertSame($insert, $result); $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertEquals(42, $sequenceValueProp->getValue($this->feature)); + static::assertSame(42, $sequenceValueProp->getValue($this->feature)); } - public function testPreInsertWithPrimaryKeyColumnButNullValue(): void + #[Test] + public function preInsertWithPrimaryKeyColumnButNullValue(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); $this->feature->setTableGateway($tableGateway); @@ -237,10 +248,10 @@ public function testPreInsertWithPrimaryKeyColumnButNullValue(): void $result = $this->feature->preInsert($insert); - self::assertSame($insert, $result); + static::assertSame($insert, $result); $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertNull($sequenceValueProp->getValue($this->feature)); + static::assertNull($sequenceValueProp->getValue($this->feature)); } #[Override] diff --git a/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php b/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php index d297ef04..df8926ae 100644 --- a/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php +++ b/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php @@ -31,7 +31,7 @@ public function getMagicMethodSpecifications(): array return $this->magicMethodSpecs; } - public function testMethod(mixed ...$args): mixed + public function recordCall(mixed ...$args): mixed { $this->called = true; $this->receivedArgs = $args; From a36290d1faf9e4a5a7e7be88f97e0a99cd967c38 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 10:39:39 +1000 Subject: [PATCH 4/6] Cover the Feature guard clauses - Test the MasterSlaveFeature Sql guards in postInitialize and postSelect - Test the SequenceFeature result, sequence value and raw state guards - Test the MetadataFeature named table guard and non-primary-key constraints - Test the RowGatewayFeature table, feature set and primary key guards - Test getFeatureByClassName skipping features of another class --- .../RowGateway/Feature/FeatureSetTest.php | 12 ++ .../TableGateway/Feature/FeatureSetTest.php | 11 ++ .../Feature/MasterSlaveFeatureTest.php | 32 +++++ .../Feature/MetadataFeatureTest.php | 66 +++++++++++ .../Feature/RowGatewayFeatureTest.php | 111 ++++++++++++++++++ .../Feature/SequenceFeatureTest.php | 109 +++++++++++++++++ 6 files changed, 341 insertions(+) diff --git a/test/unit/RowGateway/Feature/FeatureSetTest.php b/test/unit/RowGateway/Feature/FeatureSetTest.php index f4c17459..4387224d 100644 --- a/test/unit/RowGateway/Feature/FeatureSetTest.php +++ b/test/unit/RowGateway/Feature/FeatureSetTest.php @@ -6,6 +6,7 @@ use PhpDb\RowGateway\AbstractRowGateway; use PhpDb\RowGateway\Feature\AbstractFeature; +use PhpDb\RowGateway\Feature\FeatureInterface; use PhpDb\RowGateway\Feature\FeatureSet; use PhpDbTest\RowGateway\Feature\TestAsset\TestRowGatewayFeature; use PHPUnit\Framework\Attributes\Test; @@ -176,6 +177,17 @@ public function getFeatureByClassNameReturnsNullWhenNotFound(): void static::assertNull($result); } + #[Test] + public function getFeatureByClassNameSkipsFeaturesOfAnotherClass(): void + { + $other = $this->createMock(FeatureInterface::class); + $wanted = $this->createMock(AbstractFeature::class); + + $featureSet = new FeatureSet([$other, $wanted]); + + static::assertSame($wanted, $featureSet->getFeatureByClassName(AbstractFeature::class)); + } + #[Test] public function setRowGateway(): void { diff --git a/test/unit/TableGateway/Feature/FeatureSetTest.php b/test/unit/TableGateway/Feature/FeatureSetTest.php index 45cd93c6..a4cd6c4d 100644 --- a/test/unit/TableGateway/Feature/FeatureSetTest.php +++ b/test/unit/TableGateway/Feature/FeatureSetTest.php @@ -290,6 +290,17 @@ public function getFeatureByClassNameReturnsNullWhenNotFound(): void static::assertNull($result); } + #[Test] + public function getFeatureByClassNameSkipsFeaturesOfAnotherClass(): void + { + $other = new TestTableGatewayFeature(); + $wanted = new SequenceFeature('id', 'table_sequence'); + + $featureSet = new FeatureSet([$other, $wanted]); + + static::assertSame($wanted, $featureSet->getFeatureByClassName(SequenceFeature::class)); + } + #[Test] public function setTableGateway(): void { diff --git a/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php b/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php index ad331e87..3bf2103a 100644 --- a/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php +++ b/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php @@ -11,6 +11,8 @@ use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Adapter\Platform\Sql92; use PhpDb\Sql\Sql; +use PhpDb\TableGateway\AbstractTableGateway; +use PhpDb\TableGateway\Exception\RuntimeException; use PhpDb\TableGateway\Feature\MasterSlaveFeature; use PhpDb\TableGateway\TableGateway; use PHPUnit\Framework\Attributes\Test; @@ -58,6 +60,25 @@ public function postInitialize(): void static::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveSql()->getAdapter()); } + /** + * @throws Exception + */ + #[Test] + public function postInitializeThrowsWhenTableGatewayHasNoSql(): void + { + $tableGateway = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); + + $feature = new MasterSlaveFeature($this->mockSlaveAdapter); + $feature->setTableGateway($tableGateway); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'The table gateway must be initialized with a Sql instance before this feature is applied.', + ); + + $feature->postInitialize(); + } + /** * @throws Exception */ @@ -107,6 +128,17 @@ public function postSelect(): void static::assertSame($masterSql, $table->getSql()); } + #[Test] + public function postSelectThrowsWhenPostInitializeHasNotRun(): void + { + $feature = new MasterSlaveFeature($this->mockSlaveAdapter); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The master Sql instance is not available; postInitialize() has not been run.'); + + $feature->postSelect(); + } + /** * @throws Exception */ diff --git a/test/unit/TableGateway/Feature/MetadataFeatureTest.php b/test/unit/TableGateway/Feature/MetadataFeatureTest.php index 04a3ec1b..01ecad78 100644 --- a/test/unit/TableGateway/Feature/MetadataFeatureTest.php +++ b/test/unit/TableGateway/Feature/MetadataFeatureTest.php @@ -123,6 +123,49 @@ public function postInitializeRecordsPrimaryKeyColumnToSharedMetadata(): void static::assertSame('id', $sharedData['metadata']['primaryKey']); } + /** + * @throws Exception + * @throws \Exception + */ + #[Test] + public function postInitializeSkipsConstraintsThatAreNotPrimaryKeys(): void + { + /** @var AbstractTableGateway&MockObject $tableGatewayMock */ + $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); + + $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); + $tableProperty->setValue($tableGatewayMock, 'foo'); + + $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); + $metadataMock->expects($this->any())->method('getColumnNames')->willReturn(['id', 'name']); + $metadataMock->expects($this->any()) + ->method('getTable') + ->willReturn(new TableObject('foo')); + + $unique = new ConstraintObject('name_unique', 'foo'); + $unique->setColumns(['name']); + $unique->setType('UNIQUE'); + + $primary = new ConstraintObject('id_pk', 'foo'); + $primary->setColumns(['id']); + $primary->setType('PRIMARY KEY'); + + $metadataMock->expects($this->any())->method('getConstraints')->willReturn([$unique, $primary]); + + $feature = new MetadataFeature($metadataMock); + $feature->setTableGateway($tableGatewayMock); + $feature->postInitialize(); + + $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); + $sharedData = $r->getValue($feature); + + static::assertIsArray($sharedData); + static::assertArrayHasKey('metadata', $sharedData); + static::assertIsArray($sharedData['metadata']); + static::assertArrayHasKey('primaryKey', $sharedData['metadata']); + static::assertSame('id', $sharedData['metadata']['primaryKey']); + } + /** * @throws Exception * @throws \Exception @@ -182,6 +225,29 @@ public function postInitializeThrowsExceptionWhenNoPrimaryKeyFound(): void $feature->postInitialize(); } + /** + * @throws Exception + */ + #[Test] + public function postInitializeThrowsWhenTableIsNotNamed(): void + { + $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); + $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); + + $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); + $tableProperty->setValue($tableGatewayMock, [42]); + + $feature = new MetadataFeature($metadataMock); + $feature->setTableGateway($tableGatewayMock); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'The table gateway must reference a named table before metadata can be resolved.', + ); + + $feature->postInitialize(); + } + /** * @throws Exception * @throws \Exception diff --git a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php index 087e5960..a7e3cc42 100644 --- a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php +++ b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php @@ -59,6 +59,21 @@ public function constructorWithNoArguments(): void static::assertEquals([], $args); } + #[Test] + public function postInitializeIgnoresAnArgumentThatIsNeitherStringNorRowGateway(): void + { + $resultSet = new ResultSet(); + $tableGateway = $this->createTableGatewayMock($resultSet); + $original = $resultSet->getRowPrototype(); + + $feature = new RowGatewayFeature(42); + $feature->setTableGateway($tableGateway); + + $feature->postInitialize(); + + static::assertSame($original, $resultSet->getRowPrototype()); + } + #[Test] public function postInitializeThrowsExceptionForNonResultSet(): void { @@ -127,6 +142,102 @@ public function postInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey(): vo $feature->postInitialize(); } + #[Test] + public function postInitializeThrowsWhenMetadataIsNotAnArray(): void + { + $resultSet = new ResultSet(); + + $metadataFeature = $this->getMockBuilder(MetadataFeature::class) + ->disableOriginalConstructor() + ->getMock(); + + $sharedDataProperty = new ReflectionProperty(MetadataFeature::class, 'sharedData'); + $sharedDataProperty->setValue($metadataFeature, ['metadata' => 'not-an-array']); + + $featureSet = $this->createMock(FeatureSet::class); + $featureSet->expects($this->once()) + ->method('getFeatureByClassName') + ->with(MetadataFeature::class) + ->willReturn($metadataFeature); + + $feature = new RowGatewayFeature(); + $feature->setTableGateway($this->createTableGatewayMock($resultSet, $featureSet)); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The MetadataFeature did not expose its metadata as an array.'); + + $feature->postInitialize(); + } + + #[Test] + public function postInitializeThrowsWhenMetadataPrimaryKeyIsUnusable(): void + { + $resultSet = new ResultSet(); + + $metadataFeature = $this->getMockBuilder(MetadataFeature::class) + ->disableOriginalConstructor() + ->getMock(); + + $sharedDataProperty = new ReflectionProperty(MetadataFeature::class, 'sharedData'); + $sharedDataProperty->setValue($metadataFeature, [ + 'metadata' => ['primaryKey' => 42], + ]); + + $featureSet = $this->createMock(FeatureSet::class); + $featureSet->expects($this->once()) + ->method('getFeatureByClassName') + ->with(MetadataFeature::class) + ->willReturn($metadataFeature); + + $feature = new RowGatewayFeature(); + $feature->setTableGateway($this->createTableGatewayMock($resultSet, $featureSet)); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'The MetadataFeature did not expose a usable primary key for RowGateway object creation.', + ); + + $feature->postInitialize(); + } + + #[Test] + public function postInitializeThrowsWhenTableGatewayHasNoFeatureSet(): void + { + $resultSet = new ResultSet(); + $tableGateway = $this->createTableGatewayMock($resultSet); + + $featureSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'featureSet'); + $featureSetProperty->setValue($tableGateway, null); + + $feature = new RowGatewayFeature(); + $feature->setTableGateway($tableGateway); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('No information was provided to the RowGatewayFeature'); + + $feature->postInitialize(); + } + + #[Test] + public function postInitializeThrowsWhenTableIsNotNamed(): void + { + $resultSet = new ResultSet(); + $tableGateway = $this->createTableGatewayMock($resultSet); + + $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); + $tableProperty->setValue($tableGateway, [42]); + + $feature = new RowGatewayFeature('id'); + $feature->setTableGateway($tableGateway); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'The table gateway must reference a named table before a RowGateway prototype can be created.', + ); + + $feature->postInitialize(); + } + #[Test] public function postInitializeWithMetadataFeature(): void { diff --git a/test/unit/TableGateway/Feature/SequenceFeatureTest.php b/test/unit/TableGateway/Feature/SequenceFeatureTest.php index 0f03a2b0..538b1b84 100644 --- a/test/unit/TableGateway/Feature/SequenceFeatureTest.php +++ b/test/unit/TableGateway/Feature/SequenceFeatureTest.php @@ -75,6 +75,28 @@ public function lastSequenceIdThrowsExceptionForUnsupportedPlatform(): void $this->feature->lastSequenceId(); } + #[Test] + public function lastSequenceIdThrowsWhenSequenceHasNoCurrentValue(): void + { + $this->feature->setTableGateway($this->createTableGatewayReturning('Oracle', [])); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The sequence did not return a current value.'); + + $this->feature->lastSequenceId(); + } + + #[Test] + public function lastSequenceIdThrowsWhenStatementProducesNoResult(): void + { + $this->feature->setTableGateway($this->createTableGatewayReturning('Oracle', null)); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The sequence statement did not produce a result.'); + + $this->feature->lastSequenceId(); + } + /** * @throws Exception */ @@ -118,6 +140,14 @@ public function nextSequenceId(string $platformName, string $statementSql): void $this->feature->nextSequenceId(); } + #[Test] + public function nextSequenceIdReturnsNullWhenSequenceValueIsNotAnInteger(): void + { + $this->feature->setTableGateway($this->createTableGatewayReturning('Oracle', ['nextval' => 'abc'])); + + static::assertNull($this->feature->nextSequenceId()); + } + #[Test] public function nextSequenceIdThrowsExceptionForUnsupportedPlatform(): void { @@ -130,6 +160,28 @@ public function nextSequenceIdThrowsExceptionForUnsupportedPlatform(): void $this->feature->nextSequenceId(); } + #[Test] + public function nextSequenceIdThrowsWhenSequenceReturnsNoRow(): void + { + $this->feature->setTableGateway($this->createTableGatewayReturning('Oracle', 'not-an-array')); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The sequence did not return a next value.'); + + $this->feature->nextSequenceId(); + } + + #[Test] + public function nextSequenceIdThrowsWhenStatementProducesNoResult(): void + { + $this->feature->setTableGateway($this->createTableGatewayReturning('Oracle', null)); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The sequence statement did not produce a result.'); + + $this->feature->nextSequenceId(); + } + #[Test] public function postInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull(): void { @@ -218,6 +270,20 @@ public function preInsertReturnsEarlyWhenNextSequenceIdReturnsNull(): void static::assertNotContains('id', $rawState['columns']); } + #[Test] + public function preInsertThrowsWhenInsertDoesNotExposeArrays(): void + { + $insert = $this->createMock(Insert::class); + $insert->expects($this->any()) + ->method('getRawState') + ->willReturn('not-an-array'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The insert does not expose columns and values as arrays.'); + + $this->feature->preInsert($insert); + } + #[Test] public function preInsertWhenPrimaryKeyAlreadyInValues(): void { @@ -260,6 +326,49 @@ protected function setUp(): void $this->feature = new SequenceFeature($this->primaryKeyField, self::$sequenceName); } + private function createTableGatewayReturning( + string $platformName, + mixed $current, + ): AbstractTableGateway&MockObject { + $platform = $this->createMock(PlatformInterface::class); + $platform->expects($this->any()) + ->method('getName') + ->willReturn($platformName); + $platform->expects($this->any()) + ->method('quoteIdentifier') + ->willReturnCallback(static fn($name) => $name); + + $result = null; + if (null !== $current) { + $result = $this->createMock(ResultInterface::class); + $result->expects($this->any()) + ->method('current') + ->willReturn($current); + } + + $statement = $this->createMock(StatementInterface::class); + $statement->expects($this->any()) + ->method('execute') + ->willReturn($result); + + $adapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods(['getPlatform', 'createStatement']) + ->disableOriginalConstructor() + ->getMock(); + $adapter->expects($this->any()) + ->method('getPlatform') + ->willReturn($platform); + $adapter->expects($this->any()) + ->method('createStatement') + ->willReturn($statement); + + /** @var AbstractTableGateway&MockObject $tableGateway */ + return $this->getMockBuilder(TableGateway::class) + ->setConstructorArgs(['table', $adapter]) + ->onlyMethods([]) + ->getMock(); + } + private function createTableGatewayWithPlatform( string $platformName, int $sequenceValue = 2, From 82a7e1dee89c65be02ae69f70a889268bb9ac84b Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 11:57:53 +1000 Subject: [PATCH 5/6] Adapt the Feature layer to the row prototype result set - Point the RowGatewayFeature tests at the RowPrototypeResultSet helper - Restore the AbstractTableGateway and Feature class size baseline entries dropped when the baselines were regenerated upstream --- analyzer-baseline.toml | 37 +++++++++++++------ lint-baseline.toml | 24 ++++++++++++ .../Feature/RowGatewayFeatureTest.php | 12 +++--- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml index df84dfd8..90717a3d 100644 --- a/analyzer-baseline.toml +++ b/analyzer-baseline.toml @@ -5292,12 +5292,6 @@ code = "imprecise-type" message = "Type `array` in return type of `getTable` is imprecise, equivalent to `array`." count = 1 -[[issues]] -file = "src/TableGateway/AbstractTableGateway.php" -code = "invalid-array-access" -message = 'Cannot access array index on object `PhpDb\Sql\TableIdentifier` that does not implement `ArrayAccess`.' -count = 2 - [[issues]] file = "src/TableGateway/AbstractTableGateway.php" code = "invalid-clone" @@ -5502,12 +5496,6 @@ code = "possibly-invalid-clone" message = 'Expression of type `PhpDb\Sql\Sql|null` might not be a cloneable object.' count = 1 -[[issues]] -file = "src/TableGateway/AbstractTableGateway.php" -code = "possibly-undefined-string-array-index" -message = "Possibly undefined array key `string('table')` accessed on `array`." -count = 2 - [[issues]] file = "src/TableGateway/AbstractTableGateway.php" code = "redundant-comparison" @@ -5579,3 +5567,28 @@ file = "src/TableGateway/TableGatewayInterface.php" code = "missing-api-or-internal" message = 'Interface `PhpDb\TableGateway\TableGatewayInterface` is missing an `@api` or `@internal` annotation.' count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\AbstractTableGateway::getTable` is declared to return `PhpDb\Sql\TableIdentifier|array|string` but possibly returns a nullable value (inferred as `PhpDb\Sql\TableIdentifier|array|null|string`).' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\AbstractTableGateway::getTable`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `PhpDb\Sql\TableIdentifier|array|null|string`.' +count = 1 + + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-array-access" +message = 'Cannot access array index on object `PhpDb\Sql\TableIdentifier` that does not implement `ArrayAccess`.' +count = 3 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('table')` accessed on `array`." +count = 3 diff --git a/lint-baseline.toml b/lint-baseline.toml index c15e94b7..ff32f48d 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -8483,3 +8483,27 @@ file = "test/unit/TableGateway/TableGatewayTest.php" code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." count = 2 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "too-many-methods" +message = 'Class has too many methods.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "kan-defect" +message = 'Class has a high kan defect score.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "too-many-methods" +message = 'Class has too many methods.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "too-many-methods" +message = 'Class has too many methods.' +count = 1 diff --git a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php index a7e3cc42..3bbc8234 100644 --- a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php +++ b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php @@ -62,7 +62,7 @@ public function constructorWithNoArguments(): void #[Test] public function postInitializeIgnoresAnArgumentThatIsNeitherStringNorRowGateway(): void { - $resultSet = new ResultSet(); + $resultSet = $this->createInitialResultSet(); $tableGateway = $this->createTableGatewayMock($resultSet); $original = $resultSet->getRowPrototype(); @@ -145,7 +145,7 @@ public function postInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey(): vo #[Test] public function postInitializeThrowsWhenMetadataIsNotAnArray(): void { - $resultSet = new ResultSet(); + $resultSet = $this->createInitialResultSet(); $metadataFeature = $this->getMockBuilder(MetadataFeature::class) ->disableOriginalConstructor() @@ -172,7 +172,7 @@ public function postInitializeThrowsWhenMetadataIsNotAnArray(): void #[Test] public function postInitializeThrowsWhenMetadataPrimaryKeyIsUnusable(): void { - $resultSet = new ResultSet(); + $resultSet = $this->createInitialResultSet(); $metadataFeature = $this->getMockBuilder(MetadataFeature::class) ->disableOriginalConstructor() @@ -203,7 +203,7 @@ public function postInitializeThrowsWhenMetadataPrimaryKeyIsUnusable(): void #[Test] public function postInitializeThrowsWhenTableGatewayHasNoFeatureSet(): void { - $resultSet = new ResultSet(); + $resultSet = $this->createInitialResultSet(); $tableGateway = $this->createTableGatewayMock($resultSet); $featureSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'featureSet'); @@ -221,7 +221,7 @@ public function postInitializeThrowsWhenTableGatewayHasNoFeatureSet(): void #[Test] public function postInitializeThrowsWhenTableIsNotNamed(): void { - $resultSet = new ResultSet(); + $resultSet = $this->createInitialResultSet(); $tableGateway = $this->createTableGatewayMock($resultSet); $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); @@ -332,7 +332,7 @@ private function createTableGatewayMock( $resultSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'resultSetPrototype'); $resultSetProperty->setValue($tableGateway, $resultSetPrototype); - if ($featureSet !== null) { + if (null !== $featureSet) { $featureSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'featureSet'); $featureSetProperty->setValue($tableGateway, $featureSet); } From d6f702c7f0bfa3be401ce04271ce1daa0a7bde9f Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 12:07:10 +1000 Subject: [PATCH 6/6] Bring the Feature layer under the lint thresholds - Resolve the RowGatewayFeature metadata feature through a nullsafe call - Tag the SequenceFeature platform TODO --- src/TableGateway/Feature/RowGatewayFeature.php | 5 +---- src/TableGateway/Feature/SequenceFeature.php | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/TableGateway/Feature/RowGatewayFeature.php b/src/TableGateway/Feature/RowGatewayFeature.php index 3d62f19a..11351d6d 100644 --- a/src/TableGateway/Feature/RowGatewayFeature.php +++ b/src/TableGateway/Feature/RowGatewayFeature.php @@ -74,10 +74,7 @@ public function postInitialize(): void */ private function primaryKeyFromMetadata(): string|array { - $featureSet = $this->tableGateway->featureSet; - $metadata = $featureSet instanceof FeatureSet - ? $featureSet->getFeatureByClassName(MetadataFeature::class) - : null; + $metadata = $this->tableGateway->featureSet?->getFeatureByClassName(MetadataFeature::class); $metadataData = $metadata instanceof MetadataFeature ? $metadata->sharedData['metadata'] ?? null diff --git a/src/TableGateway/Feature/SequenceFeature.php b/src/TableGateway/Feature/SequenceFeature.php index 2e1bccd6..df4d4e91 100644 --- a/src/TableGateway/Feature/SequenceFeature.php +++ b/src/TableGateway/Feature/SequenceFeature.php @@ -42,7 +42,7 @@ public function lastSequenceId(): int $platform = $this->tableGateway->adapter->getPlatform(); $platformName = $platform->getName(); - // todo: Remove string usage + // TODO(@simon-mundy): Remove string usage $sql = match ($platformName) { 'Oracle' => "SELECT {$platform->quoteIdentifier($this->sequenceName)}.CURRVAL as \"currval\" FROM dual", 'PostgreSQL' => 'SELECT LAST_INSERT_ROWID() as "currval"',