diff --git a/CHANGELOG.md b/CHANGELOG.md index f2de15d..2808adb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.2 +### Fixed +- PHP 8.4 deprecation notices: the optional parameters of `getBool()`, `getFloat()`, `getInt()`, `getString()` and of the exception constructors are declared as explicit nullable types + ## 0.4.1 ### Fixed - Suppress Symfony deprecations diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 015244f..04e9bf8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,6 +5,7 @@ backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" + convertDeprecationsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" diff --git a/src/Exception/InvalidItemException.php b/src/Exception/InvalidItemException.php index 329ace1..227ecba 100644 --- a/src/Exception/InvalidItemException.php +++ b/src/Exception/InvalidItemException.php @@ -3,11 +3,13 @@ namespace Paysera\Component\ObjectWrapper\Exception; -class InvalidItemException extends \Exception +use Exception; + +class InvalidItemException extends Exception { private $key; - public function __construct(string $key, string $message = null, \Exception $previous = null) + public function __construct(string $key, ?string $message = null, ?Exception $previous = null) { parent::__construct($message ?? sprintf('Invalid key "%s"', $key), 0, $previous); $this->key = $key; diff --git a/src/Exception/InvalidItemTypeException.php b/src/Exception/InvalidItemTypeException.php index 043c8e1..16d4cb5 100644 --- a/src/Exception/InvalidItemTypeException.php +++ b/src/Exception/InvalidItemTypeException.php @@ -3,12 +3,14 @@ namespace Paysera\Component\ObjectWrapper\Exception; +use Exception; + class InvalidItemTypeException extends InvalidItemException { private $expectedType; private $givenType; - public function __construct(string $expectedType, string $givenType, string $key, \Exception $previous = null) + public function __construct(string $expectedType, string $givenType, string $key, ?Exception $previous = null) { parent::__construct( $key, diff --git a/src/Exception/MissingItemException.php b/src/Exception/MissingItemException.php index 4874905..914805a 100644 --- a/src/Exception/MissingItemException.php +++ b/src/Exception/MissingItemException.php @@ -3,9 +3,11 @@ namespace Paysera\Component\ObjectWrapper\Exception; +use Exception; + class MissingItemException extends InvalidItemException { - public function __construct(string $key, \Exception $previous = null) + public function __construct(string $key, ?Exception $previous = null) { parent::__construct($key, sprintf('Missing required key "%s"', $key), $previous); } diff --git a/src/ObjectWrapper.php b/src/ObjectWrapper.php index 98465ad..ae8fa13 100644 --- a/src/ObjectWrapper.php +++ b/src/ObjectWrapper.php @@ -103,7 +103,7 @@ public function getRequiredBool(string $key): bool * @param bool|null $default * @return bool|null */ - public function getBool(string $key, bool $default = null) + public function getBool(string $key, ?bool $default = null) { return $this->getOfType($key, 'boolean', $default); } @@ -118,7 +118,7 @@ public function getRequiredFloat(string $key): float * @param float|null $default * @return float|null */ - public function getFloat(string $key, float $default = null) + public function getFloat(string $key, ?float $default = null) { return $this->getOfType($key, 'float', $default); } @@ -133,7 +133,7 @@ public function getRequiredInt(string $key): int * @param int|null $default * @return int|null */ - public function getInt(string $key, int $default = null) + public function getInt(string $key, ?int $default = null) { return $this->getOfType($key, 'integer', $default); } @@ -162,7 +162,7 @@ public function getRequiredString(string $key): string * @param string|null $default * @return string|null */ - public function getString(string $key, string $default = null) + public function getString(string $key, ?string $default = null) { return $this->getOfType($key, 'string', $default); } diff --git a/tests/Exception/InvalidItemExceptionTest.php b/tests/Exception/InvalidItemExceptionTest.php new file mode 100644 index 0000000..67a60cb --- /dev/null +++ b/tests/Exception/InvalidItemExceptionTest.php @@ -0,0 +1,118 @@ + $expected + */ + public function testExceptionDescribesTheItem(callable $createException, array $expected): void + { + $exception = $createException(); + + $this->assertSame($expected, [ + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'key' => $exception->getKey(), + 'expectedType' => $exception instanceof InvalidItemTypeException ? $exception->getExpectedType() : null, + 'givenType' => $exception instanceof InvalidItemTypeException ? $exception->getGivenType() : null, + 'previous' => $exception->getPrevious(), + ]); + } + + /** + * @return array}> + */ + public static function exceptionProvider(): array + { + $previous = new Exception('x'); + + return [ + 'invalid item with null message and previous' => [ + static function () { + return new InvalidItemException('a.b', null, null); + }, + self::expected(InvalidItemException::class, 'Invalid key "a.b"', 'a.b'), + ], + 'invalid item type with a null previous exception' => [ + static function () { + return new InvalidItemTypeException('string', 'integer', 'a.b', null); + }, + self::expected( + InvalidItemTypeException::class, + 'Expected string but got integer for key "a.b"', + 'a.b', + 'string', + 'integer' + ), + ], + 'missing item with a null previous exception' => [ + static function () { + return new MissingItemException('a.b', null); + }, + self::expected(MissingItemException::class, 'Missing required key "a.b"', 'a.b'), + ], + 'invalid item with a custom message' => [ + static function () { + return new InvalidItemException('a.b', 'Custom message'); + }, + self::expected(InvalidItemException::class, 'Custom message', 'a.b'), + ], + 'invalid item with a previous exception' => [ + static function () use ($previous) { + return new InvalidItemException('a.b', null, $previous); + }, + self::expected(InvalidItemException::class, 'Invalid key "a.b"', 'a.b', null, null, $previous), + ], + 'invalid item type with a previous exception' => [ + static function () use ($previous) { + return new InvalidItemTypeException('string', 'integer', 'a.b', $previous); + }, + self::expected( + InvalidItemTypeException::class, + 'Expected string but got integer for key "a.b"', + 'a.b', + 'string', + 'integer', + $previous + ), + ], + 'missing item with a previous exception' => [ + static function () use ($previous) { + return new MissingItemException('a.b', $previous); + }, + self::expected(MissingItemException::class, 'Missing required key "a.b"', 'a.b', null, null, $previous), + ], + ]; + } + + /** + * @return array + */ + private static function expected( + string $class, + string $message, + string $key, + ?string $expectedType = null, + ?string $givenType = null, + ?Exception $previous = null + ): array { + return [ + 'class' => $class, + 'message' => $message, + 'key' => $key, + 'expectedType' => $expectedType, + 'givenType' => $givenType, + 'previous' => $previous, + ]; + } +} diff --git a/tests/ObjectWrapperTest.php b/tests/ObjectWrapperTest.php index 63c2d52..3e4d9ed 100644 --- a/tests/ObjectWrapperTest.php +++ b/tests/ObjectWrapperTest.php @@ -425,6 +425,30 @@ public function testGetOriginalDataAsArray() $this->assertDeepEquals($expectedArray, $originalData); } + /** + * @dataProvider optionalGetterProvider + * @param mixed $value + */ + public function testOptionalGettersAcceptAnExplicitNullDefault(string $method, $value): void + { + $object = new ObjectWrapper((object)['present' => $value]); + + $this->assertSame([$value, null], [$object->$method('present', null), $object->$method('absent', null)]); + } + + /** + * @return array + */ + public static function optionalGetterProvider(): array + { + return [ + 'getBool' => ['getBool', true], + 'getFloat' => ['getFloat', 1.5], + 'getInt' => ['getInt', 7], + 'getString' => ['getString', 'text'], + ]; + } + private function assertDeepEquals($expectedData, $dataWithWrappers) { $this->assertEquals($expectedData, $this->unwrap($dataWithWrappers));