From 3cbad4fd48dc57bfe8fa9d52b5ebef63c2a50f51 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 26 Mar 2025 17:43:15 +0000 Subject: [PATCH 1/6] feat: add support for arrays of `REAL` and `DOUBLE PRECISION` --- docs/AVAILABLE-TYPES.md | 20 +-- .../Doctrine/DBAL/Types/BaseFloatArray.php | 115 ++++++++++++++++++ .../DBAL/Types/DoublePrecisionArray.php | 38 ++++++ .../Exceptions/InvalidFloatValueException.php | 30 +++++ .../Doctrine/DBAL/Types/RealArray.php | 38 ++++++ .../DBAL/Types/BaseFloatArrayTestCase.php | 78 ++++++++++++ .../Types/DoublePrecisionArrayTestCase.php | 66 ++++++++++ .../Doctrine/DBAL/Types/RealArrayTestCase.php | 77 ++++++++++++ 8 files changed, 453 insertions(+), 9 deletions(-) create mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php create mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php create mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php create mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php create mode 100644 tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php create mode 100644 tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php create mode 100644 tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php diff --git a/docs/AVAILABLE-TYPES.md b/docs/AVAILABLE-TYPES.md index b9183ec0..e620aa9f 100644 --- a/docs/AVAILABLE-TYPES.md +++ b/docs/AVAILABLE-TYPES.md @@ -1,11 +1,13 @@ # Available types -| PostgreSQL type | Implemented by | -|---|---| -| _bool | `MartinGeorgiev\Doctrine\DBAL\Types\BooleanArray` | -| _int2 | `MartinGeorgiev\Doctrine\DBAL\Types\SmallIntArray` | -| _int4 | `MartinGeorgiev\Doctrine\DBAL\Types\IntegerArray` | -| _int8 | `MartinGeorgiev\Doctrine\DBAL\Types\BigIntArray` | -| _text | `MartinGeorgiev\Doctrine\DBAL\Types\TextArray` | -| _jsonb | `MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray` | -| jsonb | `MartinGeorgiev\Doctrine\DBAL\Types\Jsonb` | \ No newline at end of file +| PostgreSQL type in practical use | PostgreSQL internal system catalogue name | Implemented by | +|----------------------------------|-------------------------------------------|------------------------------------------------| +| bool[] | _bool | `MartinGeorgiev\Doctrine\DBAL\Types\BooleanArray` | +| smallint[] | _int2 | `MartinGeorgiev\Doctrine\DBAL\Types\SmallIntArray` | +| integer[] | _int4 | `MartinGeorgiev\Doctrine\DBAL\Types\IntegerArray` | +| bigint[] | _int8 | `MartinGeorgiev\Doctrine\DBAL\Types\BigIntArray` | +| real[] | _float4 | `MartinGeorgiev\Doctrine\DBAL\Types\RealArray` | +| double precision[] | _float8 | `MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray` | +| text[] | _text | `MartinGeorgiev\Doctrine\DBAL\Types\TextArray` | +| jsonb[] | _jsonb | `MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray` | +| jsonb | jsonb | `MartinGeorgiev\Doctrine\DBAL\Types\Jsonb` | diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php new file mode 100644 index 00000000..a4433b93 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php @@ -0,0 +1,115 @@ + + */ +abstract class BaseFloatArray extends BaseArray +{ + private const FLOAT_REGEX = '/^-?\d*\.?\d+(?:[eE][-+]?\d+)?$/'; + + abstract protected function getMinValue(): string; + + abstract protected function getMaxValue(): string; + + abstract protected function getMaxPrecision(): int; + + abstract protected function getMinAbsoluteValue(): string; + + public function isValidArrayItemForDatabase(mixed $item): bool + { + $isNotANumber = !\is_float($item) && !\is_int($item) && !\is_string($item); + if ($isNotANumber) { + return false; + } + + $stringValue = (string) $item; + if (!\preg_match(self::FLOAT_REGEX, $stringValue)) { + return false; + } + + $floatValue = (float) $stringValue; + + // For scientific notation, convert to standard decimal form before checking precision + if (\str_contains($stringValue, 'e') || \str_contains($stringValue, 'E')) { + $standardForm = \sprintf('%.'.($this->getMaxPrecision() + 1).'f', $floatValue); + $parts = \explode('.', $standardForm); + if (isset($parts[1]) && \strlen($parts[1]) > $this->getMaxPrecision()) { + return false; + } + } elseif (\str_contains($stringValue, '.')) { + $parts = \explode('.', $stringValue); + if (\strlen($parts[1]) > $this->getMaxPrecision()) { + return false; + } + } + + $isBelowMinValue = $floatValue < (float) $this->getMinValue(); + if ($isBelowMinValue) { + return false; + } + + $isAboveMaxValue = $floatValue > (float) $this->getMaxValue(); + if ($isAboveMaxValue) { + return false; + } + + // Check if value is too close to zero + $absoluteValue = \abs($floatValue); + $isTooCloseToZero = $absoluteValue > 0 && $absoluteValue < (float) $this->getMinAbsoluteValue(); + + return !$isTooCloseToZero; + } + + public function transformArrayItemForPHP(mixed $item): ?float + { + if ($item === null) { + return null; + } + + $isNotANumberCandidate = !\is_float($item) && !\is_int($item) && !\is_string($item); + if ($isNotANumberCandidate) { + throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); + } + + $stringValue = (string) $item; + $isInvalidPHPFloat = !\preg_match(self::FLOAT_REGEX, $stringValue) + || $stringValue < $this->getMinValue() + || $stringValue > $this->getMaxValue(); + + if ($isInvalidPHPFloat) { + throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); + } + + $floatValue = (float) $stringValue; + + // Check if value is too close to zero + $absValue = \abs($floatValue); + if ($absValue > 0 && $absValue < (float) $this->getMinAbsoluteValue()) { + throw InvalidFloatValueException::forValueThatIsTooCloseToZero($item); + } + + // For scientific notation, convert to standard decimal form before checking precision + if (\str_contains($stringValue, 'e') || \str_contains($stringValue, 'E')) { + $standardForm = \sprintf('%.'.($this->getMaxPrecision() + 1).'f', $floatValue); + $parts = \explode('.', $standardForm); + if (isset($parts[1]) && \strlen($parts[1]) > $this->getMaxPrecision()) { + throw InvalidFloatValueException::forValueThatExceedsMaximumPrecision($item, $this->getMaxPrecision()); + } + } elseif (\str_contains($stringValue, '.')) { + $parts = \explode('.', $stringValue); + if (\strlen($parts[1]) > $this->getMaxPrecision()) { + throw InvalidFloatValueException::forValueThatExceedsMaximumPrecision($item, $this->getMaxPrecision()); + } + } + + return $floatValue; + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php new file mode 100644 index 00000000..044c1f31 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php @@ -0,0 +1,38 @@ + + */ +class DoublePrecisionArray extends BaseFloatArray +{ + protected const TYPE_NAME = 'double precision[]'; + + protected function getMinValue(): string + { + return '-1.7976931348623157E+308'; + } + + protected function getMaxValue(): string + { + return '1.7976931348623157E+308'; + } + + protected function getMaxPrecision(): int + { + return 15; + } + + protected function getMinAbsoluteValue(): string + { + return '2.2250738585072014E-308'; + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php new file mode 100644 index 00000000..75fbdef5 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php @@ -0,0 +1,30 @@ + + */ +class InvalidFloatValueException extends ConversionException +{ + public static function forValueThatIsTooCloseToZero(mixed $value): self + { + return new self(\sprintf("Given value of '%s' is too close to zero for PostgreSQL DOUBLE PRECISION type", \var_export($value, true))); + } + + public static function forValueThatIsNotAValidPHPFloat(mixed $value): self + { + return new self(\sprintf('Given value of %s content cannot be transformed to valid PHP float.', \var_export($value, true))); + } + + public static function forValueThatExceedsMaximumPrecision(mixed $value, int $maxPrecision): self + { + return new self(\sprintf("Given value of '%s' exceeds maximum precision of %d", \var_export($value, true), $maxPrecision)); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php new file mode 100644 index 00000000..fa0e62aa --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php @@ -0,0 +1,38 @@ + + */ +class RealArray extends BaseFloatArray +{ + protected const TYPE_NAME = 'real[]'; + + protected function getMinValue(): string + { + return '-3.4028235E+38'; + } + + protected function getMaxValue(): string + { + return '3.4028235E+38'; + } + + protected function getMaxPrecision(): int + { + return 6; + } + + protected function getMinAbsoluteValue(): string + { + return '1.17549435E-38'; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php new file mode 100644 index 00000000..733630f3 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php @@ -0,0 +1,78 @@ +fixture->isValidArrayItemForDatabase($phpValue)); + } + + /** + * @return list + */ + public static function provideInvalidTransformations(): array + { + return [ + [true], + [null], + ['string'], + [[]], + [new \stdClass()], + ['1.23e4'], // Scientific notation not allowed + ]; + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(float $phpValue, string $postgresValue): void + { + self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(float $phpValue, string $postgresValue): void + { + self::assertEquals($phpValue, $this->fixture->transformArrayItemForPHP($postgresValue)); + } + + /** + * @return list + */ + abstract public static function provideValidTransformations(): array; + + /** + * @test + */ + public function throws_conversion_exception_when_invalid_array_item_value(): void + { + $this->expectException(ConversionException::class); + $this->expectExceptionMessage("Given value of '1.23e4' content cannot be transformed to valid PHP float."); + + $this->fixture->transformArrayItemForPHP('1.23e4'); + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php new file mode 100644 index 00000000..30fda3e3 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php @@ -0,0 +1,66 @@ +fixture = new DoublePrecisionArray(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('double precision[]', $this->fixture->getName()); + } + + public static function provideInvalidTransformations(): array + { + return \array_merge(parent::provideInvalidTransformations(), [ + ['1.7976931348623157E+309'], // Too large + ['-1.7976931348623157E+309'], // Too small + ['1.123456789012345678'], // Too many decimal places (>15) + ['2.2250738585072014E-309'], // Too close to zero + ['-2.2250738585072014E-309'], // Too close to zero (negative) + ['not_a_number'], + ['1.23.45'], + ['1e'], // Invalid scientific notation + ['e1'], // Invalid scientific notation + ]); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + ['phpValue' => 1.23e4, 'postgresValue' => '1.23e4'], + ['phpValue' => 1.23e-4, 'postgresValue' => '1.23e-4'], + ['phpValue' => 1.234567890123456, 'postgresValue' => '1.234567890123456'], + ['phpValue' => 1.0, 'postgresValue' => '1.0'], + ['phpValue' => -1.0, 'postgresValue' => '-1.0'], + ]; + } + + /** + * @test + */ + public function throws_conversion_exception_when_value_is_too_close_to_zero(): void + { + $this->expectException(InvalidFloatValueException::class); + $this->expectExceptionMessage("Given value of '1.18E-308' is too close to zero for PostgreSQL DOUBLE PRECISION type"); + + $this->fixture->transformArrayItemForPHP('1.18E-308'); + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php new file mode 100644 index 00000000..bb168b4f --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php @@ -0,0 +1,77 @@ +fixture = new RealArray(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('real[]', $this->fixture->getName()); + } + + public static function provideInvalidTransformations(): array + { + return \array_merge(parent::provideInvalidTransformations(), [ + ['3.402823467E+38'], // Too large + ['-3.402823467E+38'], // Too small + ['1.1234567'], // Too many decimal places (>6) + ['1e38'], // Scientific notation not allowed + ['1.17E-38'], // Too close to zero + ['-1.17E-38'], // Too close to zero (negative) + ]); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + [ + 'phpValue' => -3.402823466E+38, + 'postgresValue' => '-340282346600000000000000000000000000000', + ], + [ + 'phpValue' => 3.402823466E+38, + 'postgresValue' => '340282346600000000000000000000000000000', + ], + [ + 'phpValue' => 1.123456, + 'postgresValue' => '1.123456', + ], + [ + 'phpValue' => -1.123456, + 'postgresValue' => '-1.123456', + ], + [ + 'phpValue' => 0.0, + 'postgresValue' => '0', + ], + ]; + } + + /** + * @test + */ + public function throws_conversion_exception_when_value_too_close_to_zero(): void + { + $this->expectException(InvalidFloatValueException::class); + $this->expectExceptionMessage("Given value of '1.17E-38' is too close to zero for PostgreSQL REAL type"); + + $this->fixture->transformArrayItemForPHP('1.17E-38'); + } +} From 49634db09f47e459fdca8e5cb06aacfe34c8ee8c Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 26 Mar 2025 17:47:44 +0000 Subject: [PATCH 2/6] cs-fixer ;) --- docs/AVAILABLE-TYPES.md | 2 +- .../Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php | 1 - tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/AVAILABLE-TYPES.md b/docs/AVAILABLE-TYPES.md index e620aa9f..c2889d1f 100644 --- a/docs/AVAILABLE-TYPES.md +++ b/docs/AVAILABLE-TYPES.md @@ -1,7 +1,7 @@ # Available types | PostgreSQL type in practical use | PostgreSQL internal system catalogue name | Implemented by | -|----------------------------------|-------------------------------------------|------------------------------------------------| +|---|---|---| | bool[] | _bool | `MartinGeorgiev\Doctrine\DBAL\Types\BooleanArray` | | smallint[] | _int2 | `MartinGeorgiev\Doctrine\DBAL\Types\SmallIntArray` | | integer[] | _int4 | `MartinGeorgiev\Doctrine\DBAL\Types\IntegerArray` | diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php index 30fda3e3..02885d4e 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php @@ -4,7 +4,6 @@ namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types; -use Doctrine\DBAL\Types\ConversionException; use MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray; use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php index bb168b4f..2be30012 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php @@ -4,7 +4,6 @@ namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types; -use Doctrine\DBAL\Types\ConversionException; use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; use MartinGeorgiev\Doctrine\DBAL\Types\RealArray; From c13c75e8b16a858d5eb6755abd5f5f54d3ad7b6f Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 26 Mar 2025 18:15:22 +0000 Subject: [PATCH 3/6] Address some of AI's code-review comments and tidy up loose ends. Still WIP though. --- .../Doctrine/DBAL/Types/BaseFloatArray.php | 28 +++++++++---------- .../Exceptions/InvalidFloatValueException.php | 12 ++++---- ...tCase.php => DoublePrecisionArrayTest.php} | 4 +-- ...ealArrayTestCase.php => RealArrayTest.php} | 4 +-- 4 files changed, 24 insertions(+), 24 deletions(-) rename tests/MartinGeorgiev/Doctrine/DBAL/Types/{DoublePrecisionArrayTestCase.php => DoublePrecisionArrayTest.php} (93%) rename tests/MartinGeorgiev/Doctrine/DBAL/Types/{RealArrayTestCase.php => RealArrayTest.php} (95%) diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php index a4433b93..c9669571 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php @@ -80,11 +80,7 @@ public function transformArrayItemForPHP(mixed $item): ?float } $stringValue = (string) $item; - $isInvalidPHPFloat = !\preg_match(self::FLOAT_REGEX, $stringValue) - || $stringValue < $this->getMinValue() - || $stringValue > $this->getMaxValue(); - - if ($isInvalidPHPFloat) { + if (!\preg_match(self::FLOAT_REGEX, $stringValue)) { throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); } @@ -93,20 +89,24 @@ public function transformArrayItemForPHP(mixed $item): ?float // Check if value is too close to zero $absValue = \abs($floatValue); if ($absValue > 0 && $absValue < (float) $this->getMinAbsoluteValue()) { - throw InvalidFloatValueException::forValueThatIsTooCloseToZero($item); + throw InvalidFloatValueException::forValueThatIsTooCloseToZero($item, static::TYPE_NAME); } - // For scientific notation, convert to standard decimal form before checking precision + if ($floatValue < (float) $this->getMinValue() || $floatValue > (float) $this->getMaxValue()) { + throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); + } + + // Scientific notation is valid for input as long as the resulting number + // when converted to decimal doesn't exceed precision limits if (\str_contains($stringValue, 'e') || \str_contains($stringValue, 'E')) { - $standardForm = \sprintf('%.'.($this->getMaxPrecision() + 1).'f', $floatValue); - $parts = \explode('.', $standardForm); - if (isset($parts[1]) && \strlen($parts[1]) > $this->getMaxPrecision()) { - throw InvalidFloatValueException::forValueThatExceedsMaximumPrecision($item, $this->getMaxPrecision()); - } - } elseif (\str_contains($stringValue, '.')) { + return $floatValue; + } + + // For regular decimal notation, check precision + if (\str_contains($stringValue, '.')) { $parts = \explode('.', $stringValue); if (\strlen($parts[1]) > $this->getMaxPrecision()) { - throw InvalidFloatValueException::forValueThatExceedsMaximumPrecision($item, $this->getMaxPrecision()); + throw InvalidFloatValueException::forValueThatExceedsMaximumPrecision($item, $this->getMaxPrecision(), static::TYPE_NAME); } } diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php index 75fbdef5..2443415e 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php @@ -13,18 +13,18 @@ */ class InvalidFloatValueException extends ConversionException { - public static function forValueThatIsTooCloseToZero(mixed $value): self + public static function forValueThatIsNotAValidPHPFloat(mixed $value): self { - return new self(\sprintf("Given value of '%s' is too close to zero for PostgreSQL DOUBLE PRECISION type", \var_export($value, true))); + return new self(\sprintf("Given value of %s content cannot be transformed to valid PHP float.", \var_export($value, true))); } - public static function forValueThatIsNotAValidPHPFloat(mixed $value): self + public static function forValueThatIsTooCloseToZero(mixed $value, string $type): self { - return new self(\sprintf('Given value of %s content cannot be transformed to valid PHP float.', \var_export($value, true))); + return new self(sprintf("Given value of %s is too close to zero for PostgreSQL %s type", var_export($value, true), $type)); } - public static function forValueThatExceedsMaximumPrecision(mixed $value, int $maxPrecision): self + public static function forValueThatExceedsMaximumPrecision(mixed $value, int $maxPrecision, string $type): self { - return new self(\sprintf("Given value of '%s' exceeds maximum precision of %d", \var_export($value, true), $maxPrecision)); + return new self(\sprintf("Given value of %s exceeds maximum precision of %d for PostgreSQL %s type ", \var_export($value, true), $maxPrecision, $type)); } } diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php similarity index 93% rename from tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php rename to tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php index 02885d4e..d6f2789f 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php @@ -7,7 +7,7 @@ use MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray; use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; -class DoublePrecisionArrayTestCase extends BaseFloatArrayTestCase +class DoublePrecisionArrayTest extends BaseFloatArrayTestCase { protected function setUp(): void { @@ -58,7 +58,7 @@ public static function provideValidTransformations(): array public function throws_conversion_exception_when_value_is_too_close_to_zero(): void { $this->expectException(InvalidFloatValueException::class); - $this->expectExceptionMessage("Given value of '1.18E-308' is too close to zero for PostgreSQL DOUBLE PRECISION type"); + $this->expectExceptionMessage("Given value of '1.18E-308' is too close to zero for PostgreSQL double precision[] type"); $this->fixture->transformArrayItemForPHP('1.18E-308'); } diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php similarity index 95% rename from tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php rename to tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php index 2be30012..fa84564b 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php @@ -7,7 +7,7 @@ use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; use MartinGeorgiev\Doctrine\DBAL\Types\RealArray; -class RealArrayTestCase extends BaseFloatArrayTestCase +class RealArrayTest extends BaseFloatArrayTestCase { protected function setUp(): void { @@ -69,7 +69,7 @@ public static function provideValidTransformations(): array public function throws_conversion_exception_when_value_too_close_to_zero(): void { $this->expectException(InvalidFloatValueException::class); - $this->expectExceptionMessage("Given value of '1.17E-38' is too close to zero for PostgreSQL REAL type"); + $this->expectExceptionMessage("Given value of '1.17E-38' is too close to zero for PostgreSQL real[] type"); $this->fixture->transformArrayItemForPHP('1.17E-38'); } From 5a668bfa94627b49f5d2eecd2961ed03032dbe66 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 26 Mar 2025 18:42:03 +0000 Subject: [PATCH 4/6] fix some of the AI generated tests --- .../DBAL/Types/Exceptions/InvalidFloatValueException.php | 6 +++--- .../Doctrine/DBAL/Types/BaseFloatArrayTestCase.php | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php index 2443415e..a885eadc 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php @@ -15,16 +15,16 @@ class InvalidFloatValueException extends ConversionException { public static function forValueThatIsNotAValidPHPFloat(mixed $value): self { - return new self(\sprintf("Given value of %s content cannot be transformed to valid PHP float.", \var_export($value, true))); + return new self(\sprintf('Given value of %s content cannot be transformed to valid PHP float.', \var_export($value, true))); } public static function forValueThatIsTooCloseToZero(mixed $value, string $type): self { - return new self(sprintf("Given value of %s is too close to zero for PostgreSQL %s type", var_export($value, true), $type)); + return new self(\sprintf('Given value of %s is too close to zero for PostgreSQL %s type', \var_export($value, true), $type)); } public static function forValueThatExceedsMaximumPrecision(mixed $value, int $maxPrecision, string $type): self { - return new self(\sprintf("Given value of %s exceeds maximum precision of %d for PostgreSQL %s type ", \var_export($value, true), $maxPrecision, $type)); + return new self(\sprintf('Given value of %s exceeds maximum precision of %d for PostgreSQL %s type ', \var_export($value, true), $maxPrecision, $type)); } } diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php index 733630f3..97781b8c 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php @@ -33,7 +33,10 @@ public static function provideInvalidTransformations(): array ['string'], [[]], [new \stdClass()], - ['1.23e4'], // Scientific notation not allowed + ['1e'], // Invalid scientific notation format + ['e1'], // Invalid scientific notation format + ['1.23.45'], // Invalid number format + ['not_a_number'], ]; } @@ -71,8 +74,8 @@ abstract public static function provideValidTransformations(): array; public function throws_conversion_exception_when_invalid_array_item_value(): void { $this->expectException(ConversionException::class); - $this->expectExceptionMessage("Given value of '1.23e4' content cannot be transformed to valid PHP float."); + $this->expectExceptionMessage("Given value of '1.e234' content cannot be transformed to valid PHP float."); - $this->fixture->transformArrayItemForPHP('1.23e4'); + $this->fixture->transformArrayItemForPHP('1.e234'); } } From f6b52ac891849cf1d6348c01d595b69efcc39741 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 26 Mar 2025 18:52:50 +0000 Subject: [PATCH 5/6] add test scenario for PHP floats that end with a decimal and have no digits after it --- .../Doctrine/DBAL/Types/DoublePrecisionArrayTest.php | 1 + tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php index d6f2789f..b3dab239 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php @@ -47,6 +47,7 @@ public static function provideValidTransformations(): array ['phpValue' => 1.23e4, 'postgresValue' => '1.23e4'], ['phpValue' => 1.23e-4, 'postgresValue' => '1.23e-4'], ['phpValue' => 1.234567890123456, 'postgresValue' => '1.234567890123456'], + ['phpValue' => 1., 'postgresValue' => '1.0'], ['phpValue' => 1.0, 'postgresValue' => '1.0'], ['phpValue' => -1.0, 'postgresValue' => '-1.0'], ]; diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php index fa84564b..acb95e4f 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php @@ -56,6 +56,10 @@ public static function provideValidTransformations(): array 'phpValue' => -1.123456, 'postgresValue' => '-1.123456', ], + [ + 'phpValue' => 1., + 'postgresValue' => '1.0', + ], [ 'phpValue' => 0.0, 'postgresValue' => '0', From 59c63a396065d89992b83626bb52d2c4c1763316 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 26 Mar 2025 19:52:05 +0000 Subject: [PATCH 6/6] detailed exception handling --- .../Doctrine/DBAL/Types/BaseFloatArray.php | 41 +++++++++----- ...alidFloatArrayItemForDatabaseException.php | 55 +++++++++++++++++++ .../InvalidFloatArrayItemForPHPException.php | 35 ++++++++++++ .../Exceptions/InvalidFloatValueException.php | 30 ---------- .../Doctrine/DBAL/Types/BaseArrayTest.php | 4 +- .../DBAL/Types/BaseFloatArrayTestCase.php | 4 +- .../DBAL/Types/DoublePrecisionArrayTest.php | 45 +++++++++++++-- .../Doctrine/DBAL/Types/RealArrayTest.php | 53 +++++++++++++++--- 8 files changed, 207 insertions(+), 60 deletions(-) create mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php create mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php delete mode 100644 src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php index c9669571..4f82a419 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php @@ -4,7 +4,8 @@ namespace MartinGeorgiev\Doctrine\DBAL\Types; -use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; +use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForDatabaseException; +use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException; /** * @since 3.0 @@ -24,15 +25,26 @@ abstract protected function getMaxPrecision(): int; abstract protected function getMinAbsoluteValue(): string; public function isValidArrayItemForDatabase(mixed $item): bool + { + try { + $this->throwIfInvalidArrayItemForDatabase($item); + } catch (InvalidFloatArrayItemForDatabaseException) { + return false; + } + + return true; + } + + private function throwIfInvalidArrayItemForDatabase(mixed $item): void { $isNotANumber = !\is_float($item) && !\is_int($item) && !\is_string($item); if ($isNotANumber) { - return false; + throw InvalidFloatArrayItemForDatabaseException::isNotANumber($item); } $stringValue = (string) $item; if (!\preg_match(self::FLOAT_REGEX, $stringValue)) { - return false; + throw InvalidFloatArrayItemForDatabaseException::doesNotMatchRegex($item); } $floatValue = (float) $stringValue; @@ -42,30 +54,31 @@ public function isValidArrayItemForDatabase(mixed $item): bool $standardForm = \sprintf('%.'.($this->getMaxPrecision() + 1).'f', $floatValue); $parts = \explode('.', $standardForm); if (isset($parts[1]) && \strlen($parts[1]) > $this->getMaxPrecision()) { - return false; + throw InvalidFloatArrayItemForDatabaseException::isAScientificNotationWithExcessPrecision($item); } } elseif (\str_contains($stringValue, '.')) { $parts = \explode('.', $stringValue); if (\strlen($parts[1]) > $this->getMaxPrecision()) { - return false; + throw InvalidFloatArrayItemForDatabaseException::isANormalNumberWithExcessPrecision($item); } } $isBelowMinValue = $floatValue < (float) $this->getMinValue(); if ($isBelowMinValue) { - return false; + throw InvalidFloatArrayItemForDatabaseException::isBelowMinValue($item); } $isAboveMaxValue = $floatValue > (float) $this->getMaxValue(); if ($isAboveMaxValue) { - return false; + throw InvalidFloatArrayItemForDatabaseException::isAboveMaxValue($item); } // Check if value is too close to zero $absoluteValue = \abs($floatValue); $isTooCloseToZero = $absoluteValue > 0 && $absoluteValue < (float) $this->getMinAbsoluteValue(); - - return !$isTooCloseToZero; + if ($isTooCloseToZero) { + throw InvalidFloatArrayItemForDatabaseException::absoluteValueIsTooCloseToZero($item); + } } public function transformArrayItemForPHP(mixed $item): ?float @@ -76,12 +89,12 @@ public function transformArrayItemForPHP(mixed $item): ?float $isNotANumberCandidate = !\is_float($item) && !\is_int($item) && !\is_string($item); if ($isNotANumberCandidate) { - throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); + throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME); } $stringValue = (string) $item; if (!\preg_match(self::FLOAT_REGEX, $stringValue)) { - throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); + throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME); } $floatValue = (float) $stringValue; @@ -89,11 +102,11 @@ public function transformArrayItemForPHP(mixed $item): ?float // Check if value is too close to zero $absValue = \abs($floatValue); if ($absValue > 0 && $absValue < (float) $this->getMinAbsoluteValue()) { - throw InvalidFloatValueException::forValueThatIsTooCloseToZero($item, static::TYPE_NAME); + throw InvalidFloatArrayItemForPHPException::forValueThatIsTooCloseToZero($item, static::TYPE_NAME); } if ($floatValue < (float) $this->getMinValue() || $floatValue > (float) $this->getMaxValue()) { - throw InvalidFloatValueException::forValueThatIsNotAValidPHPFloat($item); + throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME); } // Scientific notation is valid for input as long as the resulting number @@ -106,7 +119,7 @@ public function transformArrayItemForPHP(mixed $item): ?float if (\str_contains($stringValue, '.')) { $parts = \explode('.', $stringValue); if (\strlen($parts[1]) > $this->getMaxPrecision()) { - throw InvalidFloatValueException::forValueThatExceedsMaximumPrecision($item, $this->getMaxPrecision(), static::TYPE_NAME); + throw InvalidFloatArrayItemForPHPException::forValueThatExceedsMaximumPrecision($item, static::TYPE_NAME); } } diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php new file mode 100644 index 00000000..af299cf5 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php @@ -0,0 +1,55 @@ + + */ +class InvalidFloatArrayItemForDatabaseException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function isNotANumber(mixed $value): self + { + return self::create('Given value of %s is not a number.', $value); + } + + public static function doesNotMatchRegex(mixed $value): self + { + return self::create('Given value of %s does not match float regex.', $value); + } + + public static function isAScientificNotationWithExcessPrecision(mixed $value): self + { + return self::create('Given value of %s is a scientific notation with excess precision.', $value); + } + + public static function isANormalNumberWithExcessPrecision(mixed $value): self + { + return self::create('Given value of %s is a normal number with excess precision.', $value); + } + + public static function isBelowMinValue(mixed $value): self + { + return self::create('Given value of %s is below minimum value.', $value); + } + + public static function isAboveMaxValue(mixed $value): self + { + return self::create('Given value of %s is above maximum value.', $value); + } + + public static function absoluteValueIsTooCloseToZero(mixed $value): self + { + return self::create('Given absolute value of %s is too close to zero.', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php new file mode 100644 index 00000000..44c9c255 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php @@ -0,0 +1,35 @@ + + */ +class InvalidFloatArrayItemForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value, string $type): self + { + return new self(\sprintf($message, \var_export($value, true), $type)); + } + + public static function forValueThatIsNotAValidPHPFloat(mixed $value, string $type): self + { + return self::create('Given value of %s content cannot be transformed to valid PHP float from PostgreSQL %s type', $value, $type); + } + + public static function forValueThatIsTooCloseToZero(mixed $value, string $type): self + { + return self::create('Given value of %s is too close to zero for PostgreSQL %s type', $value, $type); + } + + public static function forValueThatExceedsMaximumPrecision(mixed $value, string $type): self + { + return self::create('Given value of %s exceeds maximum precision for PostgreSQL %s type', $value, $type); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php deleted file mode 100644 index a885eadc..00000000 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php +++ /dev/null @@ -1,30 +0,0 @@ - - */ -class InvalidFloatValueException extends ConversionException -{ - public static function forValueThatIsNotAValidPHPFloat(mixed $value): self - { - return new self(\sprintf('Given value of %s content cannot be transformed to valid PHP float.', \var_export($value, true))); - } - - public static function forValueThatIsTooCloseToZero(mixed $value, string $type): self - { - return new self(\sprintf('Given value of %s is too close to zero for PostgreSQL %s type', \var_export($value, true), $type)); - } - - public static function forValueThatExceedsMaximumPrecision(mixed $value, int $maxPrecision, string $type): self - { - return new self(\sprintf('Given value of %s exceeds maximum precision of %d for PostgreSQL %s type ', \var_export($value, true), $maxPrecision, $type)); - } -} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php index f366463b..0858bf29 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php @@ -94,7 +94,7 @@ public function throws_invalid_argument_exception_when_php_value_is_not_array(): /** * @test */ - public function throws_conversion_exception_when_invalid_array_item_value(): void + public function throws_domain_exception_when_invalid_array_item_value(): void { $this->expectException(ConversionException::class); $this->expectExceptionMessage("One or more of the items given doesn't look valid."); @@ -110,7 +110,7 @@ public function throws_conversion_exception_when_invalid_array_item_value(): voi /** * @test */ - public function throws_conversion_exception_when_postgres_value_is_not_valid_php_array(): void + public function throws_domain_exception_when_postgres_value_is_not_valid_php_array(): void { $this->expectException(ConversionException::class); $this->expectExceptionMessageMatches('/Given PostgreSQL value content type is not PHP string. Instead it is "\w+"./'); diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php index 97781b8c..8061f18b 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php @@ -71,10 +71,10 @@ abstract public static function provideValidTransformations(): array; /** * @test */ - public function throws_conversion_exception_when_invalid_array_item_value(): void + public function throws_domain_exception_when_invalid_array_item_value(): void { $this->expectException(ConversionException::class); - $this->expectExceptionMessage("Given value of '1.e234' content cannot be transformed to valid PHP float."); + $this->expectExceptionMessage('cannot be transformed to valid PHP float'); $this->fixture->transformArrayItemForPHP('1.e234'); } diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php index b3dab239..aa6579c4 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php @@ -5,7 +5,7 @@ namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types; use MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray; -use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; +use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException; class DoublePrecisionArrayTest extends BaseFloatArrayTestCase { @@ -56,11 +56,48 @@ public static function provideValidTransformations(): array /** * @test */ - public function throws_conversion_exception_when_value_is_too_close_to_zero(): void + public function throws_domain_exception_when_value_is_too_close_to_zero(): void { - $this->expectException(InvalidFloatValueException::class); - $this->expectExceptionMessage("Given value of '1.18E-308' is too close to zero for PostgreSQL double precision[] type"); + $this->expectException(InvalidFloatArrayItemForPHPException::class); + $this->expectExceptionMessage('is too close to zero for PostgreSQL double precision[] type'); $this->fixture->transformArrayItemForPHP('1.18E-308'); } + + /** + * @test + */ + public function throws_domain_exception_when_value_exceeds_precision_limit(): void + { + $this->expectException(InvalidFloatArrayItemForPHPException::class); + $this->expectExceptionMessage('exceeds maximum precision for PostgreSQL double precision[] type'); + + $this->fixture->transformArrayItemForPHP('1.123456789012345678'); + } + + /** + * @test + * + * @dataProvider providePrecisionExceedingValues + */ + public function throws_domain_exception_for_various_precision_violations(string $value): void + { + $this->expectException(InvalidFloatArrayItemForPHPException::class); + $this->expectExceptionMessage('exceeds maximum precision for PostgreSQL double precision[] type'); + + $this->fixture->transformArrayItemForPHP($value); + } + + /** + * @return array + */ + public static function providePrecisionExceedingValues(): array + { + return [ + 'sixteen decimals' => ['1.1234567890123456789'], + 'many trailing zeros' => ['1.123456789012345000000'], + 'large number with excess precision' => ['123456.1234567890123456789'], + 'negative with excess precision' => ['-1.1234567890123456789'], + ]; + } } diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php index acb95e4f..c9f5d13a 100644 --- a/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php @@ -4,7 +4,7 @@ namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types; -use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException; +use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException; use MartinGeorgiev\Doctrine\DBAL\Types\RealArray; class RealArrayTest extends BaseFloatArrayTestCase @@ -41,12 +41,12 @@ public static function provideValidTransformations(): array { return [ [ - 'phpValue' => -3.402823466E+38, - 'postgresValue' => '-340282346600000000000000000000000000000', + 'phpValue' => -3.402823466E+8, + 'postgresValue' => '-3.402823466E+8', ], [ - 'phpValue' => 3.402823466E+38, - 'postgresValue' => '340282346600000000000000000000000000000', + 'phpValue' => 3.402823466E+8, + 'postgresValue' => '3.402823466E+8', ], [ 'phpValue' => 1.123456, @@ -70,11 +70,48 @@ public static function provideValidTransformations(): array /** * @test */ - public function throws_conversion_exception_when_value_too_close_to_zero(): void + public function throws_domain_exception_when_value_too_close_to_zero(): void { - $this->expectException(InvalidFloatValueException::class); - $this->expectExceptionMessage("Given value of '1.17E-38' is too close to zero for PostgreSQL real[] type"); + $this->expectException(InvalidFloatArrayItemForPHPException::class); + $this->expectExceptionMessage('is too close to zero for PostgreSQL real[] type'); $this->fixture->transformArrayItemForPHP('1.17E-38'); } + + /** + * @test + */ + public function throws_domain_exception_when_value_exceeds_precision_limit(): void + { + $this->expectException(InvalidFloatArrayItemForPHPException::class); + $this->expectExceptionMessage('exceeds maximum precision for PostgreSQL real[] type'); + + $this->fixture->transformArrayItemForPHP('1.1234567'); + } + + /** + * @test + * + * @dataProvider providePrecisionExceedingValues + */ + public function throws_domain_exception_for_various_precision_violations(string $value): void + { + $this->expectException(InvalidFloatArrayItemForPHPException::class); + $this->expectExceptionMessage('exceeds maximum precision for PostgreSQL real[] type'); + + $this->fixture->transformArrayItemForPHP($value); + } + + /** + * @return array + */ + public static function providePrecisionExceedingValues(): array + { + return [ + 'seven decimals' => ['1.1234567'], + 'many trailing zeros' => ['1.123000000'], + 'large number with excess precision' => ['123456.1234567'], + 'negative with excess precision' => ['-1.1234567'], + ]; + } }