From 61047e757a65f12746e29b6b47cbc5ce01035ac2 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 28 Sep 2026 16:30:34 +1000 Subject: [PATCH] Route the TableGateway and RowGateway throw sites through named constructors All 36 throw sites in src/TableGateway and src/RowGateway now call a named constructor. Message text is byte-identical apart from the magic-access messages noted below, verified template by template against the originals. - Add 19 templates and factories to TableGateway\Exception\RuntimeException, 4 to its InvalidArgumentException, 5 to RowGateway\Exception\RuntimeException and 2 to its InvalidArgumentException - Cover PrimaryReplicaFeature, added by #187. Its first guard reuses MISSING_SQL_INSTANCE; its second reports the primary rather than the master, so MISSING_MASTER_SQL becomes MISSING_PRIMARY_SQL. MasterSlaveFeature no longer throws, since #187 left it delegating to the replacement - Collapse the four "table name of the provided X object" copies onto one template taking the statement name - Report the concrete class in the magic access messages: AbstractTableGateway interpolated self::class, so a TableGateway named its abstract parent. The three messages now carry static::class - Point SequenceFeature at TableGateway\Exception\RuntimeException; it was throwing the root PhpDb\Exception\RuntimeException from inside the component - Rename the test__* methods to testMagicGet, testMagicSet and testMagicCall forms and drop the codingStandardsIgnore pairs they needed - Cover every factory with a template test, and derive the message assertions from the templates rather than restating them - Re-attribute the baselined mixed-operand concatenation entry in AbstractRowGateway to the factory that now receives the argument, exclude src/TableGateway/Exception/ from too-many-methods, and baseline the halstead reading AbstractTableGateway shifts Signed-off-by: Simon Mundy --- analyzer-baseline.toml | 4 +- lint-baseline.toml | 149 ++------ mago.toml | 7 + src/RowGateway/AbstractRowGateway.php | 12 +- .../Exception/InvalidArgumentException.php | 21 +- src/RowGateway/Exception/RuntimeException.php | 44 ++- src/RowGateway/Feature/AbstractFeature.php | 2 +- src/RowGateway/RowGateway.php | 4 +- src/TableGateway/AbstractTableGateway.php | 33 +- .../Exception/InvalidArgumentException.php | 35 +- .../Exception/RuntimeException.php | 161 ++++++++- .../Feature/GlobalAdapterFeature.php | 2 +- src/TableGateway/Feature/MetadataFeature.php | 6 +- .../Feature/PrimaryReplicaFeature.php | 8 +- .../Feature/RowGatewayFeature.php | 22 +- src/TableGateway/Feature/SequenceFeature.php | 16 +- src/TableGateway/TableGateway.php | 4 +- .../RowGateway/AbstractRowGatewayTest.php | 106 +++--- .../InvalidArgumentExceptionTest.php | 56 +++ .../Exception/RuntimeExceptionTest.php | 74 ++++ .../Feature/AbstractFeatureTest.php | 4 +- test/unit/RowGateway/RowGatewayTest.php | 20 +- .../TableGateway/AbstractTableGatewayTest.php | 320 +++++++++--------- .../InvalidArgumentExceptionTest.php | 69 ++++ .../Exception/RuntimeExceptionTest.php | 160 +++++++++ .../Feature/GlobalAdapterFeatureTest.php | 8 +- .../Feature/MetadataFeatureTest.php | 8 +- .../Feature/PrimaryReplicaFeatureTest.php | 10 +- .../Feature/RowGatewayFeatureTest.php | 34 +- .../Feature/SequenceFeatureTest.php | 30 +- test/unit/TableGateway/TableGatewayTest.php | 6 +- 31 files changed, 945 insertions(+), 490 deletions(-) create mode 100644 test/unit/RowGateway/Exception/InvalidArgumentExceptionTest.php create mode 100644 test/unit/RowGateway/Exception/RuntimeExceptionTest.php create mode 100644 test/unit/TableGateway/Exception/InvalidArgumentExceptionTest.php create mode 100644 test/unit/TableGateway/Exception/RuntimeExceptionTest.php diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml index 93a74a29..1d96e2ee 100644 --- a/analyzer-baseline.toml +++ b/analyzer-baseline.toml @@ -1958,8 +1958,8 @@ count = 14 [[issues]] file = "src/RowGateway/AbstractRowGateway.php" -code = "mixed-operand" -message = "Invalid middle operand: type `mixed` cannot be reliably used in string concatenation." +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\RowGateway\Exception\RuntimeException::forMissingPrimaryKeyData`: expected `string`, but found `mixed`.' count = 1 [[issues]] diff --git a/lint-baseline.toml b/lint-baseline.toml index f4f30de4..5c91fe20 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -3360,36 +3360,6 @@ code = "assertion-style" message = "Inconsistent assertions style." count = 48 -[[issues]] -file = "test/unit/RowGateway/AbstractRowGatewayTest.php" -code = "method-name" -message = "Method name `test__getThrowsExceptionForInvalidColumn` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/AbstractRowGatewayTest.php" -code = "method-name" -message = "Method name `test__get` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/AbstractRowGatewayTest.php" -code = "method-name" -message = "Method name `test__isset` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/AbstractRowGatewayTest.php" -code = "method-name" -message = "Method name `test__set` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/RowGateway/AbstractRowGatewayTest.php" -code = "method-name" -message = "Method name `test__unset` should be in camel case." -count = 1 - [[issues]] file = "test/unit/RowGateway/AbstractRowGatewayTest.php" code = "no-isset" @@ -3531,31 +3501,31 @@ count = 1 [[issues]] file = "test/unit/RowGateway/AbstractRowGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getThrowsExceptionForInvalidColumn`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetThrowsForUnknownColumn`." count = 1 [[issues]] file = "test/unit/RowGateway/AbstractRowGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__get`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsTheColumnValue`." count = 1 [[issues]] file = "test/unit/RowGateway/AbstractRowGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__isset`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicIssetReportsWhetherAColumnIsSet`." count = 1 [[issues]] file = "test/unit/RowGateway/AbstractRowGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__set`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicSetAssignsTheColumnValue`." count = 1 [[issues]] file = "test/unit/RowGateway/AbstractRowGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__unset`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicUnsetClearsTheColumnValue`." count = 1 [[issues]] @@ -8034,84 +8004,6 @@ code = "assertion-style" message = "Inconsistent assertions style." count = 47 -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__callThrowsExceptionForInvalidMethod` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__callWithFeatureSetMagicCall` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__cloneWithAliasedTableIdentifier` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__cloneWithTableIdentifier` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__clone` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__getAdapter` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__getLastInsertValue` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__getTable` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__getThrowsExceptionForInvalidProperty` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__getWithFeatureSetMagicGet` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__get` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__setThrowsExceptionForInvalidProperty` should be in camel case." -count = 1 - -[[issues]] -file = "test/unit/TableGateway/AbstractTableGatewayTest.php" -code = "method-name" -message = "Method name `test__setWithFeatureSetMagicSet` should be in camel case." -count = 1 - [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" @@ -8301,79 +8193,79 @@ count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__callThrowsExceptionForInvalidMethod`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicCallThrowsForUnknownMethod`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__callWithFeatureSetMagicCall`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicCallDelegatesToTheFeatureSet`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__cloneWithAliasedTableIdentifier`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneWithAnAliasedTableIdentifier`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__cloneWithTableIdentifier`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneWithATableIdentifier`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__clone`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneKeepsTheAdapter`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getAdapter`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsTheAdapter`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getLastInsertValue`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsTheLastInsertValue`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getTable`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsTheTable`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getThrowsExceptionForInvalidProperty`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetThrowsForUnknownProperty`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getWithFeatureSetMagicGet`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetDelegatesToTheFeatureSet`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__get`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsTheLastInsertValueAndAdapter`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__setThrowsExceptionForInvalidProperty`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicSetThrowsForUnknownProperty`." count = 1 [[issues]] file = "test/unit/TableGateway/AbstractTableGatewayTest.php" code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `test__setWithFeatureSetMagicSet`." +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicSetDelegatesToTheFeatureSet`." count = 1 [[issues]] @@ -8471,3 +8363,8 @@ file = "test/unit/TableGateway/TableGatewayTest.php" code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." count = 2 +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 diff --git a/mago.toml b/mago.toml index 5ef92248..902a7fc0 100644 --- a/mago.toml +++ b/mago.toml @@ -11,6 +11,13 @@ parentheses-around-new-in-member-access = true [linter] baseline = "lint-baseline.toml" +[linter.rules] +# Exception classes are message catalogues: one template and one named constructor per +# condition, per the convention in docs/book/exceptions.md. The method count tracks the +# number of distinct failures a component can report, so the maintainability signal does +# not apply. "test/" repeats the phpdb-qa-tools default, which this key replaces. +too-many-methods = { exclude = ["test/", "src/TableGateway/Exception/"] } + [analyzer] baseline = "analyzer-baseline.toml" class-initializers = [ diff --git a/src/RowGateway/AbstractRowGateway.php b/src/RowGateway/AbstractRowGateway.php index b363d17f..b583378c 100644 --- a/src/RowGateway/AbstractRowGateway.php +++ b/src/RowGateway/AbstractRowGateway.php @@ -93,15 +93,15 @@ public function initialize(): void $this->featureSet->apply('preInitialize', []); if ($this->table === null) { - throw new Exception\RuntimeException('This row object does not have a valid table set.'); + throw Exception\RuntimeException::forMissingTable(); } if ($this->primaryKeyColumn === null) { - throw new Exception\RuntimeException('This row object does not have a primary key column set.'); + throw Exception\RuntimeException::forMissingPrimaryKeyColumn(); } if ($this->sql === null) { - throw new Exception\RuntimeException('This row object does not have a Sql object set.'); + throw Exception\RuntimeException::forMissingSqlObject(); } $this->featureSet->apply('postInitialize', []); @@ -259,9 +259,7 @@ protected function processPrimaryKeyData(): void $this->primaryKeyData = []; foreach ($this->primaryKeyColumn as $column) { if (! isset($this->data[$column])) { - throw new Exception\RuntimeException( - 'While processing primary key data, a known key ' . $column . ' was not found in the data array', - ); + throw Exception\RuntimeException::forMissingPrimaryKeyData($column); } $this->primaryKeyData[$column] = $this->data[$column]; } @@ -275,7 +273,7 @@ public function __get(string $name): mixed if (array_key_exists($name, $this->data)) { return $this->data[$name]; } - throw new Exception\InvalidArgumentException('Not a valid column in this row: ' . $name); + throw Exception\InvalidArgumentException::forInvalidColumn($name); } public function __isset(string $name): bool diff --git a/src/RowGateway/Exception/InvalidArgumentException.php b/src/RowGateway/Exception/InvalidArgumentException.php index 3b098ef1..8028318e 100644 --- a/src/RowGateway/Exception/InvalidArgumentException.php +++ b/src/RowGateway/Exception/InvalidArgumentException.php @@ -6,4 +6,23 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException {} +use function sprintf; + +class InvalidArgumentException extends Exception\InvalidArgumentException +{ + final public const string INVALID_COLUMN = 'Not a valid column in this row: %s'; + + final public const string SQL_TABLE_MISMATCH = + 'The Sql object provided does not have a table' + . ' that matches this row object'; + + public static function forInvalidColumn(string $name): self + { + return new self(sprintf(self::INVALID_COLUMN, $name)); + } + + public static function forSqlTableMismatch(): self + { + return new self(self::SQL_TABLE_MISMATCH); + } +} diff --git a/src/RowGateway/Exception/RuntimeException.php b/src/RowGateway/Exception/RuntimeException.php index f229bfdc..9a11226e 100644 --- a/src/RowGateway/Exception/RuntimeException.php +++ b/src/RowGateway/Exception/RuntimeException.php @@ -6,4 +6,46 @@ use PhpDb\Exception; -class RuntimeException extends Exception\RuntimeException {} +use function sprintf; + +class RuntimeException extends Exception\RuntimeException +{ + final public const string MISSING_PRIMARY_KEY_COLUMN = + 'This row object does not have' + . ' a primary key column set.'; + + final public const string MISSING_PRIMARY_KEY_DATA = + 'While processing primary key data, a known key %s' + . ' was not found in the data array'; + + final public const string MISSING_SQL_OBJECT = 'This row object does not have a Sql object set.'; + + final public const string MISSING_TABLE = 'This row object does not have a valid table set.'; + + final public const string UNCALLABLE_METHOD = 'This method is not intended to be called on this object.'; + + public static function forMissingPrimaryKeyColumn(): self + { + return new self(self::MISSING_PRIMARY_KEY_COLUMN); + } + + public static function forMissingPrimaryKeyData(string $column): self + { + return new self(sprintf(self::MISSING_PRIMARY_KEY_DATA, $column)); + } + + public static function forMissingSqlObject(): self + { + return new self(self::MISSING_SQL_OBJECT); + } + + public static function forMissingTable(): self + { + return new self(self::MISSING_TABLE); + } + + public static function forUncallableMethod(): self + { + return new self(self::UNCALLABLE_METHOD); + } +} diff --git a/src/RowGateway/Feature/AbstractFeature.php b/src/RowGateway/Feature/AbstractFeature.php index 2e4539db..0593c012 100644 --- a/src/RowGateway/Feature/AbstractFeature.php +++ b/src/RowGateway/Feature/AbstractFeature.php @@ -38,7 +38,7 @@ public function getName(): string #[Override] public function initialize(): void { - throw new Exception\RuntimeException('This method is not intended to be called on this object.'); + throw Exception\RuntimeException::forUncallableMethod(); } #[Override] diff --git a/src/RowGateway/RowGateway.php b/src/RowGateway/RowGateway.php index bebabcb0..f13bd498 100644 --- a/src/RowGateway/RowGateway.php +++ b/src/RowGateway/RowGateway.php @@ -39,9 +39,7 @@ public function __construct( } if ($this->sql->getTable() !== $this->table) { - throw new Exception\InvalidArgumentException( - 'The Sql object provided does not have a table that matches this row object', - ); + throw Exception\InvalidArgumentException::forSqlTableMismatch(); } $this->initialize(); diff --git a/src/TableGateway/AbstractTableGateway.php b/src/TableGateway/AbstractTableGateway.php index feddc57c..203982bf 100644 --- a/src/TableGateway/AbstractTableGateway.php +++ b/src/TableGateway/AbstractTableGateway.php @@ -26,7 +26,6 @@ use function is_array; use function is_object; use function reset; -use function sprintf; /** * @property AdapterInterface $adapter @@ -146,11 +145,11 @@ public function initialize(): void $this->featureSet->apply(EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, []); if (null === $this->adapter) { - throw new Exception\RuntimeException('This table does not have an Adapter setup'); + throw Exception\RuntimeException::forMissingAdapter(); } if (null === $this->table) { - throw new Exception\RuntimeException('This table object does not have a valid table set.'); + throw Exception\RuntimeException::forMissingTable(); } if (null === $this->resultSetPrototype) { @@ -268,9 +267,7 @@ protected function executeDelete(Delete $delete): int { $deleteState = $delete->getRawState(); if ($deleteState['table'] !== $this->table) { - throw new Exception\RuntimeException( - 'The table name of the provided Delete object must match that of the table', - ); + throw Exception\RuntimeException::forTableMismatch('Delete'); } // pre delete update @@ -305,9 +302,7 @@ protected function executeInsert(Insert $insert): int { $insertState = $insert->getRawState(); if ($insertState['table'] !== $this->table) { - throw new Exception\RuntimeException( - 'The table name of the provided Insert object must match that of the table', - ); + throw Exception\RuntimeException::forTableMismatch('Insert'); } // apply preInsert features @@ -351,9 +346,7 @@ protected function executeSelect(Select $select): ResultSetInterface && end($selectState['table']) !== $this->table ) ) { - throw new Exception\RuntimeException( - 'The table name of the provided Select object must match that of the table', - ); + throw Exception\RuntimeException::forTableMismatch('Select'); } if ( @@ -389,9 +382,7 @@ protected function executeUpdate(Update $update): int { $updateState = $update->getRawState(); if ($updateState['table'] !== $this->table) { - throw new Exception\RuntimeException( - 'The table name of the provided Update object must match that of the table', - ); + throw Exception\RuntimeException::forTableMismatch('Update'); } // apply preUpdate features @@ -426,11 +417,7 @@ public function __call(string $method, array $arguments): mixed if ($this->featureSet->canCallMagicCall($method)) { return $this->featureSet->callMagicCall($method, $arguments); } - throw new Exception\InvalidArgumentException(sprintf( - 'Invalid method (%s) called, caught by %s::__call()', - $method, - self::class, - )); + throw Exception\InvalidArgumentException::forInvalidMagicCall($method, static::class); } public function __clone(): void @@ -458,9 +445,7 @@ public function __get(string $property): mixed return match (true) { 'lastInsertValue' === $property, 'adapter' === $property, 'table' === $property => $this->$property, $this->featureSet->canCallMagicGet($property) => $this->featureSet->callMagicGet($property), - default => throw new Exception\InvalidArgumentException( - 'Invalid magic property access in ' . self::class . '::__get()', - ), + default => throw Exception\InvalidArgumentException::forInvalidMagicGet(static::class), }; } @@ -474,6 +459,6 @@ public function __set(string $property, mixed $value): void return; } - throw new Exception\InvalidArgumentException('Invalid magic property access in ' . self::class . '::__set()'); + throw Exception\InvalidArgumentException::forInvalidMagicSet(static::class); } } diff --git a/src/TableGateway/Exception/InvalidArgumentException.php b/src/TableGateway/Exception/InvalidArgumentException.php index b8226ff2..70ef23a9 100644 --- a/src/TableGateway/Exception/InvalidArgumentException.php +++ b/src/TableGateway/Exception/InvalidArgumentException.php @@ -6,4 +6,37 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException {} +use function sprintf; + +class InvalidArgumentException extends Exception\InvalidArgumentException +{ + final public const string INVALID_MAGIC_CALL = 'Invalid method (%s) called, caught by %s::__call()'; + + final public const string INVALID_MAGIC_GET = 'Invalid magic property access in %s::__get()'; + + final public const string INVALID_MAGIC_SET = 'Invalid magic property access in %s::__set()'; + + final public const string SQL_TABLE_MISMATCH = + 'The table inside the provided Sql object' + . ' must match the table of this TableGateway'; + + public static function forInvalidMagicCall(string $method, string $class): self + { + return new self(sprintf(self::INVALID_MAGIC_CALL, $method, $class)); + } + + public static function forInvalidMagicGet(string $class): self + { + return new self(sprintf(self::INVALID_MAGIC_GET, $class)); + } + + public static function forInvalidMagicSet(string $class): self + { + return new self(sprintf(self::INVALID_MAGIC_SET, $class)); + } + + public static function forSqlTableMismatch(): self + { + return new self(self::SQL_TABLE_MISMATCH); + } +} diff --git a/src/TableGateway/Exception/RuntimeException.php b/src/TableGateway/Exception/RuntimeException.php index adedbd55..8ee596b9 100644 --- a/src/TableGateway/Exception/RuntimeException.php +++ b/src/TableGateway/Exception/RuntimeException.php @@ -6,4 +6,163 @@ use PhpDb\Exception; -class RuntimeException extends Exception\RuntimeException {} +use function sprintf; + +class RuntimeException extends Exception\RuntimeException +{ + final public const string MISSING_ADAPTER = 'This table does not have an Adapter setup'; + + final public const string MISSING_CURRENT_SEQUENCE_VALUE = 'The sequence did not return a current value.'; + + final public const string MISSING_NEXT_SEQUENCE_VALUE = 'The sequence did not return a next value.'; + + final public const string MISSING_PRIMARY_KEY = + 'No information was provided to the RowGatewayFeature' + . ' and/or no MetadataFeature could be consulted to find the primary key' + . ' necessary for RowGateway object creation.'; + + final public const string MISSING_PRIMARY_KEY_IN_METADATA = + 'A primary key for this column' + . ' could not be found in the metadata.'; + + final public const string MISSING_PRIMARY_SQL = + 'The primary Sql instance is not available;' + . ' postInitialize() has not been run.'; + + final public const string MISSING_SEQUENCE_RESULT = 'The sequence statement did not produce a result.'; + + final public const string MISSING_SQL_INSTANCE = + 'The table gateway must be initialized with a Sql instance' + . ' before this feature is applied.'; + + final public const string MISSING_STATIC_ADAPTER = 'No database adapter was found in the static registry.'; + + final public const string MISSING_TABLE = 'This table object does not have a valid table set.'; + + final public const string NON_ARRAY_INSERT_DATA = 'The insert does not expose columns and values as arrays.'; + + final public const string NON_ARRAY_METADATA = 'The MetadataFeature did not expose its metadata as an array.'; + + final public const string TABLE_MISMATCH = + 'The table name of the provided %s object' + . ' must match that of the table'; + + final public const string UNEXPECTED_RESULT_SET = + 'This feature %s expects the ResultSet' + . ' to be an instance of %s'; + + final public const string UNNAMED_TABLE_IN_METADATA = + 'The table gateway must reference a named table' + . ' before metadata can be resolved.'; + + final public const string UNNAMED_TABLE_IN_ROW_GATEWAY = + 'The table gateway must reference a named table' + . ' before a RowGateway prototype can be created.'; + + final public const string UNSUPPORTED_LAST_SEQUENCE_PLATFORM = + 'Unsupported platform' + . ' for retrieving last sequence id'; + + final public const string UNSUPPORTED_NEXT_SEQUENCE_PLATFORM = + 'Unsupported platform' + . ' for retrieving next sequence id'; + + final public const string UNUSABLE_PRIMARY_KEY = + 'The MetadataFeature did not expose a usable primary key' + . ' for RowGateway object creation.'; + + public static function forMissingAdapter(): self + { + return new self(self::MISSING_ADAPTER); + } + + public static function forMissingCurrentSequenceValue(): self + { + return new self(self::MISSING_CURRENT_SEQUENCE_VALUE); + } + + public static function forMissingNextSequenceValue(): self + { + return new self(self::MISSING_NEXT_SEQUENCE_VALUE); + } + + public static function forMissingPrimaryKey(): self + { + return new self(self::MISSING_PRIMARY_KEY); + } + + public static function forMissingPrimaryKeyInMetadata(): self + { + return new self(self::MISSING_PRIMARY_KEY_IN_METADATA); + } + + public static function forMissingPrimarySql(): self + { + return new self(self::MISSING_PRIMARY_SQL); + } + + public static function forMissingSequenceResult(): self + { + return new self(self::MISSING_SEQUENCE_RESULT); + } + + public static function forMissingSqlInstance(): self + { + return new self(self::MISSING_SQL_INSTANCE); + } + + public static function forMissingStaticAdapter(): self + { + return new self(self::MISSING_STATIC_ADAPTER); + } + + public static function forMissingTable(): self + { + return new self(self::MISSING_TABLE); + } + + public static function forNonArrayInsertData(): self + { + return new self(self::NON_ARRAY_INSERT_DATA); + } + + public static function forNonArrayMetadata(): self + { + return new self(self::NON_ARRAY_METADATA); + } + + public static function forTableMismatch(string $statement): self + { + return new self(sprintf(self::TABLE_MISMATCH, $statement)); + } + + public static function forUnexpectedResultSet(string $feature, string $expected): self + { + return new self(sprintf(self::UNEXPECTED_RESULT_SET, $feature, $expected)); + } + + public static function forUnnamedTableInMetadata(): self + { + return new self(self::UNNAMED_TABLE_IN_METADATA); + } + + public static function forUnnamedTableInRowGateway(): self + { + return new self(self::UNNAMED_TABLE_IN_ROW_GATEWAY); + } + + public static function forUnsupportedLastSequencePlatform(): self + { + return new self(self::UNSUPPORTED_LAST_SEQUENCE_PLATFORM); + } + + public static function forUnsupportedNextSequencePlatform(): self + { + return new self(self::UNSUPPORTED_NEXT_SEQUENCE_PLATFORM); + } + + public static function forUnusablePrimaryKey(): self + { + return new self(self::UNUSABLE_PRIMARY_KEY); + } +} diff --git a/src/TableGateway/Feature/GlobalAdapterFeature.php b/src/TableGateway/Feature/GlobalAdapterFeature.php index 45b8d0ca..d112fb42 100644 --- a/src/TableGateway/Feature/GlobalAdapterFeature.php +++ b/src/TableGateway/Feature/GlobalAdapterFeature.php @@ -27,7 +27,7 @@ public static function getStaticAdapter(): AdapterInterface $adapter = static::$staticAdapters[$class] ?? static::$staticAdapters[self::class] ?? null; if (! $adapter instanceof AdapterInterface) { - throw new Exception\RuntimeException('No database adapter was found in the static registry.'); + throw Exception\RuntimeException::forMissingStaticAdapter(); } return $adapter; diff --git a/src/TableGateway/Feature/MetadataFeature.php b/src/TableGateway/Feature/MetadataFeature.php index 2bc5423b..ad0340d5 100644 --- a/src/TableGateway/Feature/MetadataFeature.php +++ b/src/TableGateway/Feature/MetadataFeature.php @@ -47,9 +47,7 @@ public function postInitialize(): void } if (! $tableGatewayTable instanceof TableIdentifier && ! is_string($tableGatewayTable)) { - throw new Exception\RuntimeException( - 'The table gateway must reference a named table before metadata can be resolved.', - ); + throw Exception\RuntimeException::forUnnamedTableInMetadata(); } $table = $tableGatewayTable instanceof TableIdentifier @@ -87,7 +85,7 @@ public function postInitialize(): void } if (null === $pkc) { - throw new Exception\RuntimeException('A primary key for this column could not be found in the metadata.'); + throw Exception\RuntimeException::forMissingPrimaryKeyInMetadata(); } $pkcColumns = $pkc->getColumns(); diff --git a/src/TableGateway/Feature/PrimaryReplicaFeature.php b/src/TableGateway/Feature/PrimaryReplicaFeature.php index 54ec91ec..b990b24d 100644 --- a/src/TableGateway/Feature/PrimaryReplicaFeature.php +++ b/src/TableGateway/Feature/PrimaryReplicaFeature.php @@ -43,9 +43,7 @@ public function postInitialize(): void { $primarySql = $this->tableGateway->sql; if (! $primarySql instanceof Sql) { - throw new Exception\RuntimeException( - 'The table gateway must be initialized with a Sql instance before this feature is applied.', - ); + throw Exception\RuntimeException::forMissingSqlInstance(); } $this->primarySql = $primarySql; @@ -66,9 +64,7 @@ public function postInitialize(): void public function postSelect(): void { if (! $this->primarySql instanceof Sql) { - throw new Exception\RuntimeException( - 'The primary Sql instance is not available; postInitialize() has not been run.', - ); + throw Exception\RuntimeException::forMissingPrimarySql(); } $this->tableGateway->sql = $this->primarySql; diff --git a/src/TableGateway/Feature/RowGatewayFeature.php b/src/TableGateway/Feature/RowGatewayFeature.php index 11351d6d..f0ca94bb 100644 --- a/src/TableGateway/Feature/RowGatewayFeature.php +++ b/src/TableGateway/Feature/RowGatewayFeature.php @@ -30,12 +30,7 @@ public function postInitialize(): void { $resultSetPrototype = $this->tableGateway->resultSetPrototype; if (! $resultSetPrototype instanceof RowPrototypeResultSet) { - throw new Exception\RuntimeException( - 'This feature ' - . self::class - . ' expects the ResultSet to be an instance of ' - . RowPrototypeResultSet::class, - ); + throw Exception\RuntimeException::forUnexpectedResultSet(self::class, RowPrototypeResultSet::class); } $firstArgument = $this->constructorArguments[0] ?? null; @@ -53,9 +48,7 @@ public function postInitialize(): void $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.', - ); + throw Exception\RuntimeException::forUnnamedTableInRowGateway(); } $resultSetPrototype->setRowPrototype(new RowGateway( @@ -81,21 +74,16 @@ private function primaryKeyFromMetadata(): string|array : 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.', - ); + throw Exception\RuntimeException::forMissingPrimaryKey(); } if (! is_array($metadataData)) { - throw new Exception\RuntimeException('The MetadataFeature did not expose its metadata as an array.'); + throw Exception\RuntimeException::forNonArrayMetadata(); } $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.', - ); + throw Exception\RuntimeException::forUnusablePrimaryKey(); } return $primaryKey; diff --git a/src/TableGateway/Feature/SequenceFeature.php b/src/TableGateway/Feature/SequenceFeature.php index df4d4e91..23319806 100644 --- a/src/TableGateway/Feature/SequenceFeature.php +++ b/src/TableGateway/Feature/SequenceFeature.php @@ -6,8 +6,8 @@ use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; -use PhpDb\Exception\RuntimeException; use PhpDb\Sql\Insert; +use PhpDb\TableGateway\Exception\RuntimeException; use function array_search; use function is_array; @@ -46,21 +46,21 @@ public function lastSequenceId(): int $sql = match ($platformName) { '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'), + default => throw RuntimeException::forUnsupportedLastSequencePlatform(), }; $statement = $this->tableGateway->adapter->createStatement(); $statement->prepare($sql); $result = $statement->execute(); if (! $result instanceof ResultInterface) { - throw new RuntimeException('The sequence statement did not produce a result.'); + throw RuntimeException::forMissingSequenceResult(); } $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.'); + throw RuntimeException::forMissingCurrentSequenceValue(); } return $sequence['currval']; @@ -81,21 +81,21 @@ public function nextSequenceId(): ?int $sql = match ($platformName) { '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'), + default => throw RuntimeException::forUnsupportedNextSequencePlatform(), }; $statement = $this->tableGateway->adapter->createStatement(); $statement->prepare($sql); $result = $statement->execute(); if (! $result instanceof ResultInterface) { - throw new RuntimeException('The sequence statement did not produce a result.'); + throw RuntimeException::forMissingSequenceResult(); } $sequence = $result->current(); unset($statement, $result); if (! is_array($sequence)) { - throw new RuntimeException('The sequence did not return a next value.'); + throw RuntimeException::forMissingNextSequenceValue(); } $nextValue = $sequence['nextval'] ?? null; @@ -122,7 +122,7 @@ public function preInsert(Insert $insert): Insert $values = $insert->getRawState('values'); if (! is_array($columns) || ! is_array($values)) { - throw new RuntimeException('The insert does not expose columns and values as arrays.'); + throw RuntimeException::forNonArrayInsertData(); } $key = array_search($this->primaryKeyField, $columns, strict: true); diff --git a/src/TableGateway/TableGateway.php b/src/TableGateway/TableGateway.php index 6fff28aa..3fe7b0dd 100644 --- a/src/TableGateway/TableGateway.php +++ b/src/TableGateway/TableGateway.php @@ -42,9 +42,7 @@ public function __construct( $this->sql = $sql ?: new Sql($this->adapter, $this->table); if ($this->sql->getTable() !== $this->table) { - throw new Exception\InvalidArgumentException( - 'The table inside the provided Sql object must match the table of this TableGateway', - ); + throw Exception\InvalidArgumentException::forSqlTableMismatch(); } $this->initialize(); diff --git a/test/unit/RowGateway/AbstractRowGatewayTest.php b/test/unit/RowGateway/AbstractRowGatewayTest.php index cb41a0e3..8a6fc57c 100644 --- a/test/unit/RowGateway/AbstractRowGatewayTest.php +++ b/test/unit/RowGateway/AbstractRowGatewayTest.php @@ -27,6 +27,8 @@ use ReflectionException; use ReflectionObject; +use function sprintf; + #[IgnoreDeprecations] #[CoversMethod(RowGateway::class, 'offsetSet')] #[CoversMethod(RowGateway::class, '__set')] @@ -55,55 +57,6 @@ final class AbstractRowGatewayTest extends TestCase /** @var ResultInterface&MockObject */ protected ResultInterface|MockObject $mockResult; - // @codingStandardsIgnoreStart - public function test__get(): void - { - // @codingStandardsIgnoreEnd - $this->rowGateway->testColumn = 'test'; - self::assertEquals('test', $this->rowGateway->testColumn); - self::assertEquals('test', $this->rowGateway['testColumn']); - } - - // @codingStandardsIgnoreStart - public function test__getThrowsExceptionForInvalidColumn(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Not a valid column in this row'); - - /** @phpstan-ignore property.notFound, expr.resultUnused */ - $this->rowGateway->nonExistentColumn; - } - - // @codingStandardsIgnoreStart - public function test__isset(): void - { - // @codingStandardsIgnoreEnd - self::assertFalse(isset($this->rowGateway->foo)); - $this->rowGateway->foo = 'bar'; - self::assertTrue(isset($this->rowGateway->foo)); - } - - // @codingStandardsIgnoreStart - public function test__set(): void - { - // @codingStandardsIgnoreEnd - $this->rowGateway->testColumn = 'test'; - self::assertEquals('test', $this->rowGateway->testColumn); - self::assertEquals('test', $this->rowGateway['testColumn']); - } - - // @codingStandardsIgnoreStart - public function test__unset(): void - { - // @codingStandardsIgnoreEnd - $this->rowGateway->foo = 'bar'; - self::assertEquals('bar', $this->rowGateway->foo); - unset($this->rowGateway->foo); - self::assertEmpty($this->rowGateway->foo); - self::assertEmpty($this->rowGateway['foo']); - } - public function testCount(): void { $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); @@ -199,16 +152,16 @@ public function testInitializeThrowsExceptionWhenPrimaryKeyColumnIsNull(): void $sqlProp = $refRowGateway->getProperty('sql'); $sqlProp->setValue($rowGateway, new Sql($this->mockAdapter)); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a primary key column set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY_COLUMN); $rowGateway->populate(['name' => 'test']); } public function testInitializeThrowsExceptionWhenSqlIsNull(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a Sql object set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_SQL_OBJECT); $rowGateway = new RowGateway('id', 'temp_table', $this->mockAdapter); @@ -225,8 +178,8 @@ public function testInitializeThrowsExceptionWhenSqlIsNull(): void public function testInitializeThrowsExceptionWhenTableIsNull(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a valid table set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_TABLE); $rowGateway = new RowGateway('id', 'temp_table', $this->mockAdapter); @@ -240,6 +193,45 @@ public function testInitializeThrowsExceptionWhenTableIsNull(): void $rowGateway->populate(['name' => 'test']); } + public function testMagicGetReturnsTheColumnValue(): void + { + $this->rowGateway->testColumn = 'test'; + self::assertEquals('test', $this->rowGateway->testColumn); + self::assertEquals('test', $this->rowGateway['testColumn']); + } + + public function testMagicGetThrowsForUnknownColumn(): void + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(sprintf(InvalidArgumentException::INVALID_COLUMN, 'nonExistentColumn')); + + /** @phpstan-ignore property.notFound, expr.resultUnused */ + $this->rowGateway->nonExistentColumn; + } + + public function testMagicIssetReportsWhetherAColumnIsSet(): void + { + self::assertFalse(isset($this->rowGateway->foo)); + $this->rowGateway->foo = 'bar'; + self::assertTrue(isset($this->rowGateway->foo)); + } + + public function testMagicSetAssignsTheColumnValue(): void + { + $this->rowGateway->testColumn = 'test'; + self::assertEquals('test', $this->rowGateway->testColumn); + self::assertEquals('test', $this->rowGateway['testColumn']); + } + + public function testMagicUnsetClearsTheColumnValue(): void + { + $this->rowGateway->foo = 'bar'; + self::assertEquals('bar', $this->rowGateway->foo); + unset($this->rowGateway->foo); + self::assertEmpty($this->rowGateway->foo); + self::assertEmpty($this->rowGateway['foo']); + } + public function testOffsetExists(): void { self::assertFalse(isset($this->rowGateway['foo'])); @@ -285,8 +277,8 @@ public function testProcessPrimaryKeyData(): void { $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('a known key id was not found'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::MISSING_PRIMARY_KEY_DATA, 'id')); $this->rowGateway->populate(['boo' => 5, 'name' => 'foo'], true); } diff --git a/test/unit/RowGateway/Exception/InvalidArgumentExceptionTest.php b/test/unit/RowGateway/Exception/InvalidArgumentExceptionTest.php new file mode 100644 index 00000000..63c80561 --- /dev/null +++ b/test/unit/RowGateway/Exception/InvalidArgumentExceptionTest.php @@ -0,0 +1,56 @@ +, string}> */ + public static function namedConstructorProvider(): array + { + return [ + 'invalid column' => [ + 'forInvalidColumn', + ['email'], + InvalidArgumentException::INVALID_COLUMN, + ], + 'sql table mismatch' => [ + 'forSqlTableMismatch', + [], + InvalidArgumentException::SQL_TABLE_MISMATCH, + ], + ]; + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorRendersItsTemplate(string $method, array $arguments, string $template): void + { + $exception = InvalidArgumentException::{$method}(...$arguments); + + self::assertSame(sprintf($template, ...$arguments), $exception->getMessage()); + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorReturnsTheComponentExceptionType(string $method, array $arguments): void + { + self::assertInstanceOf(ExceptionInterface::class, InvalidArgumentException::{$method}(...$arguments)); + } +} diff --git a/test/unit/RowGateway/Exception/RuntimeExceptionTest.php b/test/unit/RowGateway/Exception/RuntimeExceptionTest.php new file mode 100644 index 00000000..e8408c07 --- /dev/null +++ b/test/unit/RowGateway/Exception/RuntimeExceptionTest.php @@ -0,0 +1,74 @@ +, string}> */ + public static function namedConstructorProvider(): array + { + return [ + 'missing primary key column' => [ + 'forMissingPrimaryKeyColumn', + [], + RuntimeException::MISSING_PRIMARY_KEY_COLUMN, + ], + 'missing primary key data' => [ + 'forMissingPrimaryKeyData', + ['id'], + RuntimeException::MISSING_PRIMARY_KEY_DATA, + ], + 'missing sql object' => [ + 'forMissingSqlObject', + [], + RuntimeException::MISSING_SQL_OBJECT, + ], + 'missing table' => [ + 'forMissingTable', + [], + RuntimeException::MISSING_TABLE, + ], + 'uncallable method' => [ + 'forUncallableMethod', + [], + RuntimeException::UNCALLABLE_METHOD, + ], + ]; + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorRendersItsTemplate(string $method, array $arguments, string $template): void + { + $exception = RuntimeException::{$method}(...$arguments); + + self::assertSame(sprintf($template, ...$arguments), $exception->getMessage()); + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorReturnsTheComponentExceptionType(string $method, array $arguments): void + { + self::assertInstanceOf(ExceptionInterface::class, RuntimeException::{$method}(...$arguments)); + } +} diff --git a/test/unit/RowGateway/Feature/AbstractFeatureTest.php b/test/unit/RowGateway/Feature/AbstractFeatureTest.php index ef552b06..79336b77 100644 --- a/test/unit/RowGateway/Feature/AbstractFeatureTest.php +++ b/test/unit/RowGateway/Feature/AbstractFeatureTest.php @@ -40,8 +40,8 @@ public function getNameReturnsClassName(): void #[Test] public function initializeThrowsRuntimeException(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This method is not intended to be called on this object.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::UNCALLABLE_METHOD); $this->feature->initialize(); } diff --git a/test/unit/RowGateway/RowGatewayTest.php b/test/unit/RowGateway/RowGatewayTest.php index 94120bf4..7a05a4d2 100644 --- a/test/unit/RowGateway/RowGatewayTest.php +++ b/test/unit/RowGateway/RowGatewayTest.php @@ -34,8 +34,8 @@ public function testConstructorThrowsExceptionWhenSqlTableDoesNotMatch(): void { $sql = new Sql($this->mockAdapter, 'bar'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The Sql object provided does not have a table that matches this row object'); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage(InvalidArgumentException::SQL_TABLE_MISMATCH); new RowGateway('id', 'foo', $sql); } @@ -53,8 +53,8 @@ public function testConstructorWithArrayPrimaryKey(): void public function testConstructorWithNullPrimaryKey(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a primary key column set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY_COLUMN); new RowGateway(null, 'foo', $this->mockAdapter); } @@ -93,8 +93,8 @@ public function testConstructorWithTableIdentifier(): void public function testEmptyPrimaryKey(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a primary key column set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY_COLUMN); $this->rowGateway = new RowGateway('', 'foo', $this->mockAdapter); } @@ -120,8 +120,8 @@ public function testInitializeThrowsWhenSqlIsNull(): void $sqlProp = new ReflectionProperty(RowGateway::class, 'sql'); $sqlProp->setValue($rowGateway, null); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a Sql object set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_SQL_OBJECT); $rowGateway->initialize(); } @@ -136,8 +136,8 @@ public function testInitializeThrowsWhenTableIsNull(): void $tableProp = new ReflectionProperty(RowGateway::class, 'table'); $tableProp->setValue($rowGateway, null); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a valid table set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_TABLE); $rowGateway->initialize(); } diff --git a/test/unit/TableGateway/AbstractTableGatewayTest.php b/test/unit/TableGateway/AbstractTableGatewayTest.php index 42ed03cd..9624a1b8 100644 --- a/test/unit/TableGateway/AbstractTableGatewayTest.php +++ b/test/unit/TableGateway/AbstractTableGatewayTest.php @@ -30,6 +30,8 @@ use PHPUnit\Framework\TestCase; use ReflectionClass; +use function sprintf; + #[IgnoreDeprecations] #[RequiresPhp('<= 8.6')] #[CoversMethod(AbstractTableGateway::class, 'getTable')] @@ -72,56 +74,14 @@ final class AbstractTableGatewayTest extends TestCase protected MockObject&Update $mockUpdate; protected MockObject&Delete $mockDelete; - // @codingStandardsIgnoreStart - public function test__callThrowsExceptionForInvalidMethod(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid method (invalidMethod) called'); - - /** @phpstan-ignore method.notFound */ - $this->table->invalidMethod(); - } - - // @codingStandardsIgnoreStart - public function test__callWithFeatureSetMagicCall(): void + public function testCloneKeepsTheAdapter(): void { - // @codingStandardsIgnoreEnd - // Create a FeatureSet mock that returns true for canCallMagicCall - $featureSet = $this->getMockBuilder(FeatureSet::class) - ->onlyMethods(['canCallMagicCall', 'callMagicCall']) - ->getMock(); - $featureSet->expects($this->once()) - ->method('canCallMagicCall') - ->with('customMethod') - ->willReturn(true); - $featureSet->expects($this->once()) - ->method('callMagicCall') - ->with('customMethod', ['arg1', 'arg2']) - ->willReturn('customResult'); - - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $featureSetProp = $tgReflection->getProperty('featureSet'); - $featureSetProp->setValue($this->table, $featureSet); - - /** @phpstan-ignore method.notFound */ - $result = $this->table->customMethod('arg1', 'arg2'); - - self::assertEquals('customResult', $result); - } - - // @codingStandardsIgnoreStart - public function test__clone(): void - { - // @codingStandardsIgnoreEnd $cTable = clone $this->table; self::assertSame($this->mockAdapter, $cTable->getAdapter()); } - // @codingStandardsIgnoreStart - public function test__cloneWithAliasedTableIdentifier(): void + public function testCloneWithAnAliasedTableIdentifier(): void { - // @codingStandardsIgnoreEnd $tableIdentifier = new Sql\TableIdentifier('bar', 'schema'); $aliasedTable = ['alias' => $tableIdentifier]; @@ -137,10 +97,8 @@ public function test__cloneWithAliasedTableIdentifier(): void self::assertNotSame($tableIdentifier, $clonedTable['alias']); } - // @codingStandardsIgnoreStart - public function test__cloneWithTableIdentifier(): void + public function testCloneWithATableIdentifier(): void { - // @codingStandardsIgnoreEnd $tableIdentifier = new Sql\TableIdentifier('bar', 'schema'); $tgReflection = new ReflectionClass(AbstractTableGateway::class); @@ -154,116 +112,6 @@ public function test__cloneWithTableIdentifier(): void self::assertEquals($tableIdentifier->getTable(), $cloned->getTable()->getTable()); } - // @codingStandardsIgnoreStart - public function test__get(): void - { - // @codingStandardsIgnoreEnd - $this->table->insert(['foo']); // trigger last insert id update - - self::assertEquals(10, $this->table->lastInsertValue); - self::assertSame($this->mockAdapter, $this->table->adapter); - - //self::assertEquals('foo', $this->table->table); - } - - // @codingStandardsIgnoreStart - public function test__getAdapter(): void - { - // @codingStandardsIgnoreEnd - self::assertSame($this->mockAdapter, $this->table->adapter); - } - - // @codingStandardsIgnoreStart - public function test__getLastInsertValue(): void - { - // @codingStandardsIgnoreEnd - self::assertNull($this->table->lastInsertValue); - } - - // @codingStandardsIgnoreStart - public function test__getTable(): void - { - // @codingStandardsIgnoreEnd - self::assertEquals('foo', $this->table->table); - } - - // @codingStandardsIgnoreStart - public function test__getThrowsExceptionForInvalidProperty(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid magic property access'); - - /** @phpstan-ignore expr.resultUnused, property.notFound */ - $this->table->invalidProperty; - } - - // @codingStandardsIgnoreStart - public function test__getWithFeatureSetMagicGet(): void - { - // @codingStandardsIgnoreEnd - // Create a custom feature that can handle magic get - $feature = new TestTableGatewayFeature(); - $feature->magicMethodSpecs = ['get' => ['customProperty']]; - - // Create a FeatureSet mock that returns true for canCallMagicGet - $featureSet = $this->getMockBuilder(FeatureSet::class) - ->onlyMethods(['canCallMagicGet', 'callMagicGet']) - ->getMock(); - $featureSet->expects($this->once()) - ->method('canCallMagicGet') - ->with('customProperty') - ->willReturn(true); - $featureSet->expects($this->once()) - ->method('callMagicGet') - ->with('customProperty') - ->willReturn('customValue'); - - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $featureSetProp = $tgReflection->getProperty('featureSet'); - $featureSetProp->setValue($this->table, $featureSet); - - /** @phpstan-ignore property.notFound */ - $result = $this->table->customProperty; - - self::assertEquals('customValue', $result); - } - - // @codingStandardsIgnoreStart - public function test__setThrowsExceptionForInvalidProperty(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid magic property access'); - - /** @phpstan-ignore property.notFound */ - $this->table->invalidProperty = 'value'; - } - - // @codingStandardsIgnoreStart - public function test__setWithFeatureSetMagicSet(): void - { - // @codingStandardsIgnoreEnd - // Create a FeatureSet mock that returns true for canCallMagicSet - $featureSet = $this->getMockBuilder(FeatureSet::class) - ->onlyMethods(['canCallMagicSet', 'callMagicSet']) - ->getMock(); - $featureSet->expects($this->once()) - ->method('canCallMagicSet') - ->with('customProperty') - ->willReturn(true); - $featureSet->expects($this->once()) - ->method('callMagicSet') - ->with('customProperty', 'customValue'); - - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $featureSetProp = $tgReflection->getProperty('featureSet'); - $featureSetProp->setValue($this->table, $featureSet); - - /** @phpstan-ignore property.notFound */ - $this->table->customProperty = 'customValue'; - } - public function testDelete(): void { $mockDelete = $this->mockDelete; @@ -311,8 +159,8 @@ public function testExecuteDeleteThrowsExceptionWhenTableDoesNotMatch(): void $delete = new Delete('bar'); $delete->where(['id' => 1]); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Delete object must match that of the table'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::TABLE_MISMATCH, 'Delete')); $this->table->deleteWith($delete); } @@ -322,8 +170,8 @@ public function testExecuteInsertThrowsExceptionWhenTableDoesNotMatch(): void $insert = new Insert('bar'); $insert->values(['name' => 'test']); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Insert object must match that of the table'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::TABLE_MISMATCH, 'Insert')); $this->table->insertWith($insert); } @@ -343,8 +191,8 @@ public function testExecuteSelectThrowsExceptionWhenArrayTableDoesNotMatch(): vo 'columns' => [Select::SQL_STAR], ]); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Select object must match that of the table'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::TABLE_MISMATCH, 'Select')); $this->table->selectWith($select); } @@ -354,8 +202,8 @@ public function testExecuteUpdateThrowsExceptionWhenTableDoesNotMatch(): void $update = new Update('bar'); $update->set(['name' => 'test']); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Update object must match that of the table'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf(RuntimeException::TABLE_MISMATCH, 'Update')); $this->table->updateWith($update); } @@ -466,8 +314,8 @@ public function testInitializeThrowsExceptionWithoutAdapter(): void $tableProp = $tgReflection->getProperty('table'); $tableProp->setValue($stub, 'foo'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This table does not have an Adapter setup'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_ADAPTER); $stub->initialize(); } @@ -482,8 +330,8 @@ public function testInitializeThrowsExceptionWithoutTable(): void $adapterProp = $tgReflection->getProperty('adapter'); $adapterProp->setValue($stub, $this->mockAdapter); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This table object does not have a valid table set.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_TABLE); $stub->initialize(); } @@ -536,6 +384,140 @@ public function testIsInitialized(): void self::assertTrue($stub->isInitialized()); } + public function testMagicCallDelegatesToTheFeatureSet(): void + { + // Create a FeatureSet mock that returns true for canCallMagicCall + $featureSet = $this->getMockBuilder(FeatureSet::class) + ->onlyMethods(['canCallMagicCall', 'callMagicCall']) + ->getMock(); + $featureSet->expects($this->once()) + ->method('canCallMagicCall') + ->with('customMethod') + ->willReturn(true); + $featureSet->expects($this->once()) + ->method('callMagicCall') + ->with('customMethod', ['arg1', 'arg2']) + ->willReturn('customResult'); + + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $featureSetProp = $tgReflection->getProperty('featureSet'); + $featureSetProp->setValue($this->table, $featureSet); + + /** @phpstan-ignore method.notFound */ + $result = $this->table->customMethod('arg1', 'arg2'); + + self::assertEquals('customResult', $result); + } + + public function testMagicCallThrowsForUnknownMethod(): void + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage( + sprintf(InvalidArgumentException::INVALID_MAGIC_CALL, 'invalidMethod', $this->table::class), + ); + + /** @phpstan-ignore method.notFound */ + $this->table->invalidMethod(); + } + + public function testMagicGetDelegatesToTheFeatureSet(): void + { + // Create a custom feature that can handle magic get + $feature = new TestTableGatewayFeature(); + $feature->magicMethodSpecs = ['get' => ['customProperty']]; + + // Create a FeatureSet mock that returns true for canCallMagicGet + $featureSet = $this->getMockBuilder(FeatureSet::class) + ->onlyMethods(['canCallMagicGet', 'callMagicGet']) + ->getMock(); + $featureSet->expects($this->once()) + ->method('canCallMagicGet') + ->with('customProperty') + ->willReturn(true); + $featureSet->expects($this->once()) + ->method('callMagicGet') + ->with('customProperty') + ->willReturn('customValue'); + + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $featureSetProp = $tgReflection->getProperty('featureSet'); + $featureSetProp->setValue($this->table, $featureSet); + + /** @phpstan-ignore property.notFound */ + $result = $this->table->customProperty; + + self::assertEquals('customValue', $result); + } + + public function testMagicGetReturnsTheAdapter(): void + { + self::assertSame($this->mockAdapter, $this->table->adapter); + } + + public function testMagicGetReturnsTheLastInsertValue(): void + { + self::assertNull($this->table->lastInsertValue); + } + + public function testMagicGetReturnsTheLastInsertValueAndAdapter(): void + { + $this->table->insert(['foo']); // trigger last insert id update + + self::assertEquals(10, $this->table->lastInsertValue); + self::assertSame($this->mockAdapter, $this->table->adapter); + + //self::assertEquals('foo', $this->table->table); + } + + public function testMagicGetReturnsTheTable(): void + { + self::assertEquals('foo', $this->table->table); + } + + public function testMagicGetThrowsForUnknownProperty(): void + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage( + sprintf(InvalidArgumentException::INVALID_MAGIC_GET, $this->table::class), + ); + + /** @phpstan-ignore expr.resultUnused, property.notFound */ + $this->table->invalidProperty; + } + + public function testMagicSetDelegatesToTheFeatureSet(): void + { + // Create a FeatureSet mock that returns true for canCallMagicSet + $featureSet = $this->getMockBuilder(FeatureSet::class) + ->onlyMethods(['canCallMagicSet', 'callMagicSet']) + ->getMock(); + $featureSet->expects($this->once()) + ->method('canCallMagicSet') + ->with('customProperty') + ->willReturn(true); + $featureSet->expects($this->once()) + ->method('callMagicSet') + ->with('customProperty', 'customValue'); + + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $featureSetProp = $tgReflection->getProperty('featureSet'); + $featureSetProp->setValue($this->table, $featureSet); + + /** @phpstan-ignore property.notFound */ + $this->table->customProperty = 'customValue'; + } + + public function testMagicSetThrowsForUnknownProperty(): void + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage( + sprintf(InvalidArgumentException::INVALID_MAGIC_SET, $this->table::class), + ); + + /** @phpstan-ignore property.notFound */ + $this->table->invalidProperty = 'value'; + } + public function testSelectAppliesColumnsWhenStarSelected(): void { // Set up columns on the table diff --git a/test/unit/TableGateway/Exception/InvalidArgumentExceptionTest.php b/test/unit/TableGateway/Exception/InvalidArgumentExceptionTest.php new file mode 100644 index 00000000..bc80c14b --- /dev/null +++ b/test/unit/TableGateway/Exception/InvalidArgumentExceptionTest.php @@ -0,0 +1,69 @@ +, string}> */ + public static function namedConstructorProvider(): array + { + return [ + 'invalid magic call' => [ + 'forInvalidMagicCall', + ['nope', TableGateway::class], + InvalidArgumentException::INVALID_MAGIC_CALL, + ], + 'invalid magic get' => [ + 'forInvalidMagicGet', + [TableGateway::class], + InvalidArgumentException::INVALID_MAGIC_GET, + ], + 'invalid magic set' => [ + 'forInvalidMagicSet', + [TableGateway::class], + InvalidArgumentException::INVALID_MAGIC_SET, + ], + 'sql table mismatch' => [ + 'forSqlTableMismatch', + [], + InvalidArgumentException::SQL_TABLE_MISMATCH, + ], + ]; + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorRendersItsTemplate(string $method, array $arguments, string $template): void + { + $exception = InvalidArgumentException::{$method}(...$arguments); + + self::assertSame(sprintf($template, ...$arguments), $exception->getMessage()); + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorReturnsTheComponentExceptionType(string $method, array $arguments): void + { + self::assertInstanceOf(ExceptionInterface::class, InvalidArgumentException::{$method}(...$arguments)); + } +} diff --git a/test/unit/TableGateway/Exception/RuntimeExceptionTest.php b/test/unit/TableGateway/Exception/RuntimeExceptionTest.php new file mode 100644 index 00000000..bb3b2384 --- /dev/null +++ b/test/unit/TableGateway/Exception/RuntimeExceptionTest.php @@ -0,0 +1,160 @@ +, string}> */ + public static function namedConstructorProvider(): array + { + return [ + 'missing adapter' => [ + 'forMissingAdapter', + [], + RuntimeException::MISSING_ADAPTER, + ], + 'missing current sequence value' => [ + 'forMissingCurrentSequenceValue', + [], + RuntimeException::MISSING_CURRENT_SEQUENCE_VALUE, + ], + 'missing primary sql' => [ + 'forMissingPrimarySql', + [], + RuntimeException::MISSING_PRIMARY_SQL, + ], + 'missing next sequence value' => [ + 'forMissingNextSequenceValue', + [], + RuntimeException::MISSING_NEXT_SEQUENCE_VALUE, + ], + 'missing primary key' => [ + 'forMissingPrimaryKey', + [], + RuntimeException::MISSING_PRIMARY_KEY, + ], + 'missing primary key in metadata' => [ + 'forMissingPrimaryKeyInMetadata', + [], + RuntimeException::MISSING_PRIMARY_KEY_IN_METADATA, + ], + 'missing sequence result' => [ + 'forMissingSequenceResult', + [], + RuntimeException::MISSING_SEQUENCE_RESULT, + ], + 'missing sql instance' => [ + 'forMissingSqlInstance', + [], + RuntimeException::MISSING_SQL_INSTANCE, + ], + 'missing static adapter' => [ + 'forMissingStaticAdapter', + [], + RuntimeException::MISSING_STATIC_ADAPTER, + ], + 'missing table' => [ + 'forMissingTable', + [], + RuntimeException::MISSING_TABLE, + ], + 'non array insert data' => [ + 'forNonArrayInsertData', + [], + RuntimeException::NON_ARRAY_INSERT_DATA, + ], + 'non array metadata' => [ + 'forNonArrayMetadata', + [], + RuntimeException::NON_ARRAY_METADATA, + ], + 'table mismatch' => [ + 'forTableMismatch', + ['Select'], + RuntimeException::TABLE_MISMATCH, + ], + 'unexpected result set' => [ + 'forUnexpectedResultSet', + [RowGatewayFeature::class, RowPrototypeResultSet::class], + RuntimeException::UNEXPECTED_RESULT_SET, + ], + 'unnamed table in metadata' => [ + 'forUnnamedTableInMetadata', + [], + RuntimeException::UNNAMED_TABLE_IN_METADATA, + ], + 'unnamed table in row gateway' => [ + 'forUnnamedTableInRowGateway', + [], + RuntimeException::UNNAMED_TABLE_IN_ROW_GATEWAY, + ], + 'unsupported last sequence platform' => [ + 'forUnsupportedLastSequencePlatform', + [], + RuntimeException::UNSUPPORTED_LAST_SEQUENCE_PLATFORM, + ], + 'unsupported next sequence platform' => [ + 'forUnsupportedNextSequencePlatform', + [], + RuntimeException::UNSUPPORTED_NEXT_SEQUENCE_PLATFORM, + ], + 'unusable primary key' => [ + 'forUnusablePrimaryKey', + [], + RuntimeException::UNUSABLE_PRIMARY_KEY, + ], + ]; + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorRendersItsTemplate(string $method, array $arguments, string $template): void + { + $exception = RuntimeException::{$method}(...$arguments); + + self::assertSame(sprintf($template, ...$arguments), $exception->getMessage()); + } + + /** @param list $arguments */ + #[Test] + #[DataProvider('namedConstructorProvider')] + public function namedConstructorReturnsTheComponentExceptionType(string $method, array $arguments): void + { + self::assertInstanceOf(ExceptionInterface::class, RuntimeException::{$method}(...$arguments)); + } +} diff --git a/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php b/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php index c3ff8252..538ff430 100644 --- a/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php +++ b/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php @@ -33,8 +33,8 @@ public function getStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet(): #[Test] public function getStaticAdapterThrowsExceptionWhenNoAdapterSet(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No database adapter was found in the static registry.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_STATIC_ADAPTER); GlobalAdapterFeature::getStaticAdapter(); } @@ -109,8 +109,8 @@ public function subclassFallsBackToDefaultAdapterWhenNoSpecificAdapterSet(): voi #[Test] public function subclassThrowsExceptionWhenNoAdaptersSet(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No database adapter was found in the static registry.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_STATIC_ADAPTER); TestGlobalAdapterFeatureSubclass::getStaticAdapter(); } diff --git a/test/unit/TableGateway/Feature/MetadataFeatureTest.php b/test/unit/TableGateway/Feature/MetadataFeatureTest.php index 01ecad78..8fb7da2e 100644 --- a/test/unit/TableGateway/Feature/MetadataFeatureTest.php +++ b/test/unit/TableGateway/Feature/MetadataFeatureTest.php @@ -219,8 +219,8 @@ public function postInitializeThrowsExceptionWhenNoPrimaryKeyFound(): void $feature = new MetadataFeature($metadataMock); $feature->setTableGateway($tableGatewayMock); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('A primary key for this column could not be found in the metadata.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY_IN_METADATA); $feature->postInitialize(); } @@ -240,8 +240,8 @@ public function postInitializeThrowsWhenTableIsNotNamed(): void $feature = new MetadataFeature($metadataMock); $feature->setTableGateway($tableGatewayMock); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( + self::expectException(RuntimeException::class); + self::expectExceptionMessage( 'The table gateway must reference a named table before metadata can be resolved.', ); diff --git a/test/unit/TableGateway/Feature/PrimaryReplicaFeatureTest.php b/test/unit/TableGateway/Feature/PrimaryReplicaFeatureTest.php index 7af3ebb3..a6bc81c6 100644 --- a/test/unit/TableGateway/Feature/PrimaryReplicaFeatureTest.php +++ b/test/unit/TableGateway/Feature/PrimaryReplicaFeatureTest.php @@ -70,10 +70,8 @@ public function postInitializeThrowsWhenTableGatewayHasNoSql(): void $feature = new PrimaryReplicaFeature($this->mockReplicaAdapter); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'The table gateway must be initialized with a Sql instance before this feature is applied.', - ); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_SQL_INSTANCE); $feature->postInitialize(); } @@ -132,8 +130,8 @@ public function postSelectThrowsWhenPostInitializeHasNotRun(): void { $feature = new PrimaryReplicaFeature($this->mockReplicaAdapter); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The primary Sql instance is not available; postInitialize() has not been run.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_SQL); $feature->postSelect(); } diff --git a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php index 3bbc8234..46493dfa 100644 --- a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php +++ b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php @@ -18,6 +18,8 @@ use PHPUnit\Framework\TestCase; use ReflectionProperty; +use function sprintf; + class RowGatewayFeatureTest extends TestCase { #[Test] @@ -83,8 +85,12 @@ public function postInitializeThrowsExceptionForNonResultSet(): void $feature = new RowGatewayFeature('id'); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('expects the ResultSet to be an instance of'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(sprintf( + RuntimeException::UNEXPECTED_RESULT_SET, + RowGatewayFeature::class, + RowPrototypeResultSet::class, + )); $feature->postInitialize(); } @@ -114,8 +120,8 @@ public function postInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): voi $feature = new RowGatewayFeature(); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No information was provided to the RowGatewayFeature'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY); $feature->postInitialize(); } @@ -136,8 +142,8 @@ public function postInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey(): vo $feature = new RowGatewayFeature(); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No information was provided to the RowGatewayFeature'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY); $feature->postInitialize(); } @@ -163,8 +169,8 @@ public function postInitializeThrowsWhenMetadataIsNotAnArray(): void $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.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::NON_ARRAY_METADATA); $feature->postInitialize(); } @@ -192,8 +198,8 @@ public function postInitializeThrowsWhenMetadataPrimaryKeyIsUnusable(): void $feature = new RowGatewayFeature(); $feature->setTableGateway($this->createTableGatewayMock($resultSet, $featureSet)); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( + self::expectException(RuntimeException::class); + self::expectExceptionMessage( 'The MetadataFeature did not expose a usable primary key for RowGateway object creation.', ); @@ -212,8 +218,8 @@ public function postInitializeThrowsWhenTableGatewayHasNoFeatureSet(): void $feature = new RowGatewayFeature(); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No information was provided to the RowGatewayFeature'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_PRIMARY_KEY); $feature->postInitialize(); } @@ -230,8 +236,8 @@ public function postInitializeThrowsWhenTableIsNotNamed(): void $feature = new RowGatewayFeature('id'); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( + self::expectException(RuntimeException::class); + self::expectExceptionMessage( 'The table gateway must reference a named table before a RowGateway prototype can be created.', ); diff --git a/test/unit/TableGateway/Feature/SequenceFeatureTest.php b/test/unit/TableGateway/Feature/SequenceFeatureTest.php index 538b1b84..226172ff 100644 --- a/test/unit/TableGateway/Feature/SequenceFeatureTest.php +++ b/test/unit/TableGateway/Feature/SequenceFeatureTest.php @@ -9,9 +9,9 @@ use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Adapter\Platform\PlatformInterface; -use PhpDb\Exception\RuntimeException; use PhpDb\Sql\Insert; use PhpDb\TableGateway\AbstractTableGateway; +use PhpDb\TableGateway\Exception\RuntimeException; use PhpDb\TableGateway\Feature\SequenceFeature; use PhpDb\TableGateway\TableGateway; use PHPUnit\Framework\Attributes\DataProvider; @@ -69,8 +69,8 @@ public function lastSequenceIdThrowsExceptionForUnsupportedPlatform(): void $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); $this->feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unsupported platform for retrieving last sequence id'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::UNSUPPORTED_LAST_SEQUENCE_PLATFORM); $this->feature->lastSequenceId(); } @@ -80,8 +80,8 @@ public function lastSequenceIdThrowsWhenSequenceHasNoCurrentValue(): void { $this->feature->setTableGateway($this->createTableGatewayReturning('Oracle', [])); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The sequence did not return a current value.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_CURRENT_SEQUENCE_VALUE); $this->feature->lastSequenceId(); } @@ -91,8 +91,8 @@ 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.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_SEQUENCE_RESULT); $this->feature->lastSequenceId(); } @@ -154,8 +154,8 @@ public function nextSequenceIdThrowsExceptionForUnsupportedPlatform(): void $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); $this->feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unsupported platform for retrieving next sequence id'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::UNSUPPORTED_NEXT_SEQUENCE_PLATFORM); $this->feature->nextSequenceId(); } @@ -165,8 +165,8 @@ 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.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_NEXT_SEQUENCE_VALUE); $this->feature->nextSequenceId(); } @@ -176,8 +176,8 @@ 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.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::MISSING_SEQUENCE_RESULT); $this->feature->nextSequenceId(); } @@ -278,8 +278,8 @@ public function preInsertThrowsWhenInsertDoesNotExposeArrays(): void ->method('getRawState') ->willReturn('not-an-array'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The insert does not expose columns and values as arrays.'); + self::expectException(RuntimeException::class); + self::expectExceptionMessage(RuntimeException::NON_ARRAY_INSERT_DATA); $this->feature->preInsert($insert); } diff --git a/test/unit/TableGateway/TableGatewayTest.php b/test/unit/TableGateway/TableGatewayTest.php index 050895e9..a9a6c27d 100644 --- a/test/unit/TableGateway/TableGatewayTest.php +++ b/test/unit/TableGateway/TableGatewayTest.php @@ -83,7 +83,7 @@ public function testConstructor(): void self::assertSame($sql, $table->getSql()); // constructor expects exception - native type declaration throws TypeError for null table - $this->expectException(TypeError::class); + self::expectException(TypeError::class); /** @psalm-suppress NullArgument - Testing incorrect constructor */ new TableGateway( null, @@ -95,8 +95,8 @@ public function testConstructorThrowsExceptionWhenSqlTableDoesNotMatch(): void { $sql = new Sql($this->mockAdapter, 'bar'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage( 'The table inside the provided Sql object must match the table of this TableGateway', );