Repository: martin-georgiev/postgresql-for-doctrine
Pull request #307: feat: add support for arrays of `REAL` and `DOUBLE PRECISION`
Author: login: martin-georgiev
Created: 2025-03-26T17:43:50Z | Merged: 2025-03-26T19:56:05Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Documentation**
  - Revised PostgreSQL types reference with a more comprehensive table that now includes practical usage details for each type.

- **New Features**
  - Expanded support for numerical array types by introducing capabilities for handling real and double precision arrays.
  - Implemented enhanced validations and transformations to ensure accurate numeric data handling.

- **Tests**
  - Extended test coverage for numeric array processing and improved clarity in error messaging through refined test naming.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/docs/AVAILABLE-TYPES.md b/docs/AVAILABLE-TYPES.md
index b9183ec0..c2889d1f 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..4f82a419
--- /dev/null
+++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php
@@ -0,0 +1,128 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MartinGeorgiev\Doctrine\DBAL\Types;
+
+use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForDatabaseException;
+use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException;
+
+/**
+ * @since 3.0
+ *
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
+ */
+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
+    {
+        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) {
+            throw InvalidFloatArrayItemForDatabaseException::isNotANumber($item);
+        }
+
+        $stringValue = (string) $item;
+        if (!\preg_match(self::FLOAT_REGEX, $stringValue)) {
+            throw InvalidFloatArrayItemForDatabaseException::doesNotMatchRegex($item);
+        }
+
+        $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()) {
+                throw InvalidFloatArrayItemForDatabaseException::isAScientificNotationWithExcessPrecision($item);
+            }
+        } elseif (\str_contains($stringValue, '.')) {
+            $parts = \explode('.', $stringValue);
+            if (\strlen($parts[1]) > $this->getMaxPrecision()) {
+                throw InvalidFloatArrayItemForDatabaseException::isANormalNumberWithExcessPrecision($item);
+            }
+        }
+
+        $isBelowMinValue = $floatValue < (float) $this->getMinValue();
+        if ($isBelowMinValue) {
+            throw InvalidFloatArrayItemForDatabaseException::isBelowMinValue($item);
+        }
+
+        $isAboveMaxValue = $floatValue > (float) $this->getMaxValue();
+        if ($isAboveMaxValue) {
+            throw InvalidFloatArrayItemForDatabaseException::isAboveMaxValue($item);
+        }
+
+        // Check if value is too close to zero
+        $absoluteValue = \abs($floatValue);
+        $isTooCloseToZero = $absoluteValue > 0 && $absoluteValue < (float) $this->getMinAbsoluteValue();
+        if ($isTooCloseToZero) {
+            throw InvalidFloatArrayItemForDatabaseException::absoluteValueIsTooCloseToZero($item);
+        }
+    }
+
+    public function transformArrayItemForPHP(mixed $item): ?float
+    {
+        if ($item === null) {
+            return null;
+        }
+
+        $isNotANumberCandidate = !\is_float($item) && !\is_int($item) && !\is_string($item);
+        if ($isNotANumberCandidate) {
+            throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME);
+        }
+
+        $stringValue = (string) $item;
+        if (!\preg_match(self::FLOAT_REGEX, $stringValue)) {
+            throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME);
+        }
+
+        $floatValue = (float) $stringValue;
+
+        // Check if value is too close to zero
+        $absValue = \abs($floatValue);
+        if ($absValue > 0 && $absValue < (float) $this->getMinAbsoluteValue()) {
+            throw InvalidFloatArrayItemForPHPException::forValueThatIsTooCloseToZero($item, static::TYPE_NAME);
+        }
+
+        if ($floatValue < (float) $this->getMinValue() || $floatValue > (float) $this->getMaxValue()) {
+            throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME);
+        }
+
+        // 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')) {
+            return $floatValue;
+        }
+
+        // For regular decimal notation, check precision
+        if (\str_contains($stringValue, '.')) {
+            $parts = \explode('.', $stringValue);
+            if (\strlen($parts[1]) > $this->getMaxPrecision()) {
+                throw InvalidFloatArrayItemForPHPException::forValueThatExceedsMaximumPrecision($item, static::TYPE_NAME);
+            }
+        }
+
+        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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MartinGeorgiev\Doctrine\DBAL\Types;
+
+/**
+ * Implementation of PostgreSQL DOUBLE PRECISION[] data type.
+ *
+ * @see https://www.postgresql.org/docs/current/datatype-numeric.html
+ * @since 3.0
+ *
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
+ */
+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/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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MartinGeorgiev\Doctrine\DBAL\Types\Exceptions;
+
+use Doctrine\DBAL\Types\ConversionException;
+
+/**
+ * @since 3.0
+ *
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
+ */
+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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MartinGeorgiev\Doctrine\DBAL\Types\Exceptions;
+
+use Doctrine\DBAL\Types\ConversionException;
+
+/**
+ * @since 3.0
+ *
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
+ */
+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/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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MartinGeorgiev\Doctrine\DBAL\Types;
+
+/**
+ * Implementation of PostgreSQL REAL[] data type.
+ *
+ * @see https://www.postgresql.org/docs/current/datatype-numeric.html
+ * @since 3.0
+ *
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
+ */
+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/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
new file mode 100644
index 00000000..8061f18b
--- /dev/null
+++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php
@@ -0,0 +1,81 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Types\ConversionException;
+use MartinGeorgiev\Doctrine\DBAL\Types\BaseFloatArray;
+use PHPUnit\Framework\TestCase;
+
+abstract class BaseFloatArrayTestCase extends TestCase
+{
+    protected BaseFloatArray $fixture;
+
+    /**
+     * @test
+     *
+     * @dataProvider provideInvalidTransformations
+     */
+    public function can_detect_invalid_for_transformation_php_value(mixed $phpValue): void
+    {
+        self::assertFalse($this->fixture->isValidArrayItemForDatabase($phpValue));
+    }
+
+    /**
+     * @return list<mixed>
+     */
+    public static function provideInvalidTransformations(): array
+    {
+        return [
+            [true],
+            [null],
+            ['string'],
+            [[]],
+            [new \stdClass()],
+            ['1e'], // Invalid scientific notation format
+            ['e1'], // Invalid scientific notation format
+            ['1.23.45'], // Invalid number format
+            ['not_a_number'],
+        ];
+    }
+
+    /**
+     * @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<array{
+     *     phpValue: float,
+     *     postgresValue: string
+     * }>
+     */
+    abstract public static function provideValidTransformations(): array;
+
+    /**
+     * @test
+     */
+    public function throws_domain_exception_when_invalid_array_item_value(): void
+    {
+        $this->expectException(ConversionException::class);
+        $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
new file mode 100644
index 00000000..aa6579c4
--- /dev/null
+++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray;
+use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException;
+
+class DoublePrecisionArrayTest extends BaseFloatArrayTestCase
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->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<int, array{phpValue: float, postgresValue: string}>
+     */
+    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., 'postgresValue' => '1.0'],
+            ['phpValue' => 1.0, 'postgresValue' => '1.0'],
+            ['phpValue' => -1.0, 'postgresValue' => '-1.0'],
+        ];
+    }
+
+    /**
+     * @test
+     */
+    public function throws_domain_exception_when_value_is_too_close_to_zero(): void
+    {
+        $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<string, array{string}>
+     */
+    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
new file mode 100644
index 00000000..c9f5d13a
--- /dev/null
+++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php
@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatArrayItemForPHPException;
+use MartinGeorgiev\Doctrine\DBAL\Types\RealArray;
+
+class RealArrayTest extends BaseFloatArrayTestCase
+{
+    protected function setUp(): void
+    {
+        $this->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<int, array{phpValue: float, postgresValue: string}>
+     */
+    public static function provideValidTransformations(): array
+    {
+        return [
+            [
+                'phpValue' => -3.402823466E+8,
+                'postgresValue' => '-3.402823466E+8',
+            ],
+            [
+                'phpValue' => 3.402823466E+8,
+                'postgresValue' => '3.402823466E+8',
+            ],
+            [
+                'phpValue' => 1.123456,
+                'postgresValue' => '1.123456',
+            ],
+            [
+                'phpValue' => -1.123456,
+                'postgresValue' => '-1.123456',
+            ],
+            [
+                'phpValue' => 1.,
+                'postgresValue' => '1.0',
+            ],
+            [
+                'phpValue' => 0.0,
+                'postgresValue' => '0',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     */
+    public function throws_domain_exception_when_value_too_close_to_zero(): void
+    {
+        $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<string, array{string}>
+     */
+    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'],
+        ];
+    }
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 5**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (1)</summary><blockquote>

`16-29`: **Consider adding more descriptive doc-blocks.**

The usage of `var_export()` for detailed output is excellent, and the static constructor approach is consistent. For completeness, consider adding doc-blocks for each method to clarify the intended usage scenarios and parameter constraints.

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary><blockquote>

`71-114`: **Reduce duplicated validation logic.**

Logic in `transformArrayItemForPHP()` partially overlaps with `isValidArrayItemForDatabase()` (e.g., checking for numeric content, float regex, min/max values). Consider refactoring into a shared helper method to ensure consistency and reduce code duplication.

</blockquote></details>
<details>
<summary>docs/AVAILABLE-TYPES.md (1)</summary><blockquote>

`3-13`: **Expand on precision/edge-case details in documentation.**

The updated table is clear and comprehensive. To further assist users, consider adding notes about valid float ranges, handling of near-zero values, and behavior for Infinity/NaN if relevant.

</blockquote></details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (2)</summary><blockquote>

`1-10`: **Structure looks good, but missing an informative file docblock.**

The namespace, imports, and file structure follow best practices. However, consider adding a file-level docblock explaining the purpose of this test case class and its role in testing float array types.

```diff
+/**
+ * Abstract test case for float array type implementations.
+ * Provides common test functionality for REAL[] and DOUBLE PRECISION[] array types.
+ */
 <?php

 declare(strict_types=1);
```

---

`50-58`: **Test passes though the test for transformation is incomplete.**

The method only tests `transformArrayItemForPHP` for single values, but doesn't test the full array transformation flow. Since this is testing a PostgreSQL array type, it would be beneficial to also test the conversion of a complete array.

```diff
     public function can_transform_to_php_value(float $phpValue, string $postgresValue): void
     {
         self::assertEquals($phpValue, $this->fixture->transformArrayItemForPHP($postgresValue));
+        
+        // Test full array transformation
+        $postgresArray = '{' . $postgresValue . '}';
+        $phpArray = $this->fixture->convertToPHPValue($postgresArray, null);
+        self::assertIsArray($phpArray);
+        self::assertCount(1, $phpArray);
+        self::assertEquals($phpValue, $phpArray[0]);
     }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c584099652b612cc4a89f24573fddcd7060bb4fe and 49634db09f47e459fdca8e5cb06aacfe34c8ee8c.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `docs/AVAILABLE-TYPES.md` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (5)</summary>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php (2)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (5)</summary>

* `BaseFloatArray` (14-115)
* `getMinValue` (18-18)
* `getMaxValue` (20-20)
* `getMaxPrecision` (22-22)
* `getMinAbsoluteValue` (24-24)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php (4)</summary>

* `getMinValue` (19-22)
* `getMaxValue` (24-27)
* `getMaxPrecision` (29-32)
* `getMinAbsoluteValue` (34-37)

</details>

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php (2)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (5)</summary>

* `BaseFloatArray` (14-115)
* `getMinValue` (18-18)
* `getMaxValue` (20-20)
* `getMaxPrecision` (22-22)
* `getMinAbsoluteValue` (24-24)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php (4)</summary>

* `getMinValue` (19-22)
* `getMaxValue` (24-27)
* `getMaxPrecision` (29-32)
* `getMinAbsoluteValue` (34-37)

</details>

</blockquote></details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php (3)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php (1)</summary>

* `DoublePrecisionArray` (15-38)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (1)</summary>

* `InvalidFloatValueException` (14-30)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary>

* `transformArrayItemForPHP` (71-114)

</details>

</blockquote></details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php (4)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (1)</summary>

* `InvalidFloatValueException` (14-30)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php (1)</summary>

* `RealArray` (15-38)

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (3)</summary>

* `BaseFloatArrayTestCase` (11-78)
* `provideInvalidTransformations` (28-38)
* `provideValidTransformations` (66-66)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary>

* `transformArrayItemForPHP` (71-114)

</details>

</blockquote></details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (3)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (3)</summary>

* `BaseFloatArray` (14-115)
* `isValidArrayItemForDatabase` (26-69)
* `transformArrayItemForPHP` (71-114)

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php (2)</summary>

* `provideInvalidTransformations` (25-35)
* `provideValidTransformations` (40-64)

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php (2)</summary>

* `provideInvalidTransformations` (26-39)
* `provideValidTransformations` (44-53)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php (1)</summary>

`1-38`: **Implementation looks good!**

The RealArray class implementation correctly defines the appropriate constraints for PostgreSQL's REAL[] type. The min/max values, precision limit, and minimum absolute value align with PostgreSQL's specifications.

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php (1)</summary>

`1-38`: **Implementation looks good!**

The DoublePrecisionArray class correctly implements PostgreSQL's DOUBLE PRECISION[] type with appropriate constraints. The class structure follows the same pattern as RealArray, providing a consistent approach across the codebase.

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php (2)</summary>

`27-34`: **Comprehensive test cases for invalid values**

Good job providing thorough test cases for invalid transformations, including values that are too large, too small, with too many decimal places, and those that are too close to zero.

---

`40-64`: **Good coverage of valid transformations**

The test cases for valid transformations cover important scenarios including edge cases close to min/max values, regular decimal values, and zero.

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php (4)</summary>

`27-38`: **Extensive invalid transformation test cases**

Excellent coverage of invalid transformation scenarios including boundary values, precision issues, and various malformed inputs. This will help ensure robust validation.

---

`46-52`: **Good coverage of valid transformations**

The test cases appropriately cover scientific notation, which is important for the DOUBLE PRECISION type, along with regular decimal values and basic positive/negative scenarios.

---

`58-64`: <details>
<summary>✅ Verification successful</summary>

**Exception message consistency check**

The error message test expects "too close to zero for PostgreSQL DOUBLE PRECISION type" which aligns with the current implementation of `InvalidFloatValueException`. However, if the exception class is updated to use dynamic type names (to fix the issue in RealArrayTestCase), this test would need to be updated as well.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check implementation of InvalidFloatValueException::forValueThatIsTooCloseToZero
grep -A 5 "forValueThatIsTooCloseToZero" src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php
```

Length of output: 432

---



</details>

**Exception message consistency confirmed**

The test in `tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php` correctly expects the exception message 
```
"Given value of '1.18E-308' is too close to zero for PostgreSQL DOUBLE PRECISION type"
```
which aligns with the current implementation in `InvalidFloatValueException::forValueThatIsTooCloseToZero` (as verified by the grep output). Note that if you later update the exception class to use a dynamic type name—as addressed in `RealArrayTestCase`—this test will also need to be updated to match the new message.

---

`14-15`: <details>
<summary>✅ Verification successful</summary>

**Potential parent::setUp() issue**

The method calls `parent::setUp()` but it's not clear from the provided snippets if `BaseFloatArrayTestCase` implements a `setUp` method. If it doesn't, this could potentially cause an error.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if BaseFloatArrayTestCase has a setUp method
grep -A 10 "function setUp" tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php
```

Length of output: 97

---

I'll run an additional verification to get more context on the parent class. This next script will search for both any `setUp` method in the file as well as the class inheritance declaration to confirm whether `BaseFloatArrayTestCase` might indirectly define or inherit `setUp()`.




Please run the above script and share the output so we can definitively verify whether the parent class chain provides a `setUp` method or if there's a risk here.

---

🏁 Script executed:

```shell
#!/bin/bash
echo "Searching for setUp method definition in BaseFloatArrayTestCase.php:"
rg -C 5 "function\s+setUp\(" tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php

echo "Checking the class declaration for BaseFloatArrayTestCase to see what it extends:"
rg "class\s+BaseFloatArrayTestCase" tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php
```

Length of output: 579

---



</details>

**Parent setUp Method Usage Confirmed**

After verifying the code, we found that `BaseFloatArrayTestCase` (located at `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php`) is declared as:

```php
abstract class BaseFloatArrayTestCase extends TestCase
```

Although no `setUp()` method is explicitly defined in `BaseFloatArrayTestCase`, it inherits the `setUp()` method from its parent class (`TestCase`), which is provided by PHPUnit. Therefore, the call to `parent::setUp()` in `DoublePrecisionArrayTestCase` is valid and follows the standard PHPUnit pattern.

No corrective action is necessary.

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary>

`94-97`: **Verify handling of special float values.**

Infinity or NaN values may bypass current checks or pose edge cases. If the code should handle or reject them, ensure test coverage and explicit handling.  

Would you like me to open a new issue to test Infinity and NaN cases?

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (3)</summary>

`11-14`: **LGTM! The class declaration is clear and properly abstract.**

The class correctly extends TestCase and defines the required protected property for the test fixture.

---

`15-23`: **The test method correctly validates rejection of invalid values.**

This test ensures that the `isValidArrayItemForDatabase()` method properly rejects invalid values that shouldn't be stored in the database.

---

`60-66`: **LGTM! Well-documented abstract method with proper return type.**

The PHPDoc accurately describes the expected structure of the data provider and the method is appropriately marked as abstract for subclasses to implement.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-26T17:58:09Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (1)</summary><blockquote>

`1-31`: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Address code style issues flagged by the CI pipeline.**

The pipeline failure indicates that `PHP CS Fixer` found code style violations. Please run `php-cs-fixer fix` (as suggested by the pipeline logs) to resolve any remaining style inconsistencies before merging.

---

🏁 Script executed:

```shell
#!/bin/bash
# Automatically fix code style issues using PHP CS Fixer.
composer global require friendsofphp/php-cs-fixer
php-cs-fixer fix --allow-risky=yes
```

Length of output: 6155

---



</details>

**Action Required: Resolve Code Style Issues with PHP CS Fixer**

The CI pipeline still detects code style issues. The automated command failed with a “php-cs-fixer: command not found” error, which means the PHP CS Fixer executable isn’t available in the current PATH. Please verify that the composer global installation directory (typically something like `~/.composer/vendor/bin` or `~/.config/composer/vendor/bin`) is added to your PATH, or reference the full path to the executable. Once that’s done, re-run the fixer command (`php-cs-fixer fix --allow-risky=yes`) to address the code style issues before merging.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 1-1: PHP CS Fixer found issues with code style. Please run 'php-cs-fixer fix' to apply the suggested fixes.

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (1)</summary><blockquote>

`16-29`: **Strengthen test coverage for precision limit exceptions.**

Your exception methods allow for discrete error messages covering invalid floats, excessively precise floats, or floats too close to zero. Consider adding tests that specifically target `forValueThatExceedsMaximumPrecision()` to confirm correct behavior under max precision scenarios.

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (2)</summary><blockquote>

`16-17`: **Consider allowing trailing decimal notation (e.g., "3.").**

The current regex (`^-?\d*\.?\d+(?:[eE][-+]?\d+)?$`) disallows floats that end with a decimal and have no digits after it. Although this may be intentional, "3." is often considered valid in many contexts. If desired, you could update the pattern to permit trailing decimal notation.

```diff
- private const FLOAT_REGEX = '/^-?\d*\.?\d+(?:[eE][-+]?\d+)?$/';
+ private const FLOAT_REGEX = '/^-?\d+(\.\d+)?([eE][-+]?\d+)?$/';
```

---

`71-114`: **Comprehensive transformation with consistent exception handling.**

The `transformArrayItemForPHP()` method ensures invalid values throw descriptive exceptions, fostering clear error-handling paths. Good job differentiating between invalid numeric formats, excessively small magnitudes, and out-of-range values. Consider adding unit tests specifically covering borderline edge cases (e.g. precise exponent boundaries).


Would you like help adding tests to increase coverage for these edge conditions?

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 49634db09f47e459fdca8e5cb06aacfe34c8ee8c and c13c75e8b16a858d5eb6755abd5f5f54d3ad7b6f.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php (2)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (1)</summary>

* `InvalidFloatValueException` (14-30)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary>

* `transformArrayItemForPHP` (71-114)

</details>

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (3)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php (4)</summary>

* `InvalidFloatValueException` (14-30)
* `forValueThatIsNotAValidPHPFloat` (16-19)
* `forValueThatIsTooCloseToZero` (21-24)
* `forValueThatExceedsMaximumPrecision` (26-29)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php (4)</summary>

* `getMinValue` (19-22)
* `getMaxValue` (24-27)
* `getMaxPrecision` (29-32)
* `getMinAbsoluteValue` (34-37)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php (4)</summary>

* `getMinValue` (19-22)
* `getMaxValue` (24-27)
* `getMaxPrecision` (29-32)
* `getMinAbsoluteValue` (34-37)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatValueException.php</summary>

[error] 1-1: PHP CS Fixer found issues with code style. Please run 'php-cs-fixer fix' to apply the suggested fixes.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php (2)</summary>

`58-75`: **Well-defined negative test scenario.**

The method `throws_conversion_exception_when_value_too_close_to_zero` provides excellent coverage for edge-case float inputs. It's good to see negative scenarios covered as well.

---

`25-35`: <details>
<summary>❓ Verification inconclusive</summary>

**Check boundaries for oversized float values.**

You've flagged `'3.402823467E+38'` and `'1.17E-38'` as invalid inputs for the real type due to exceeding range or being too close to zero. The margin between valid and invalid can be very tight. Ensure there's a firm reference for the exact boundary of the PostgreSQL `real` type to avoid flaky tests on certain platforms or PHP versions.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify actual float range boundaries.
php -r 'echo "Float max: " . PHP_FLOAT_MAX . "\n"; echo "Float min: " . PHP_FLOAT_MIN . "\n";'
```

Length of output: 160

---



</details>

**Action Item: Revisit Float Boundary Values for PostgreSQL**

The recent output confirms that PHP’s `PHP_FLOAT_MAX` and `PHP_FLOAT_MIN` reflect 64‑bit double values—not the boundaries for PostgreSQL’s 4‑byte `real` type. PostgreSQL’s valid float range is much more constrained (approximately 3.4028235E+38 for the maximum and 1.17549435E-38 for the minimum normalized value). Given that the tests currently invalidate inputs like `'3.402823467E+38'` and `'1.17E-38'`, please ensure these thresholds precisely match the PostgreSQL documentation. This verification will help avoid flaky tests across different platforms and PHP versions.

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php (2)</summary>

`46-53`: **Good variety of valid transformations.**

Your test cases for valid transformations cover typical numeric patterns, decimal, and scientific notation. This helps ensure thorough coverage for double precision arrays.

---

`28-38`: <details>
<summary>❓ Verification inconclusive</summary>

**Consider borderline double precision values carefully.**

The chosen test data (`'1.7976931348623157E+309'` and `'2.2250738585072014E-309'`) are indeed outside the typical IEEE-754 double range. Keep in mind that certain platforms may handle subnormal or overflow differently. Confirm that these values are consistently throwing the exception in different environments.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify consistent behavior across environments.
php -r 'var_dump((float) "1.7976931348623157E+309"); var_dump((float) "2.2250738585072014E-309");'
```

Length of output: 141

---



</details>

**Borderline Double Precision Value Handling in Invalid Transformations Test**

Our local test confirms that converting `"1.7976931348623157E+309"` results in `INF` and `"2.2250738585072014E-309"` produces approximately `2.225073858507203E-309`. These outcomes align with IEEE-754 expectations in our current PHP environment. However, since different platforms may treat subnormal numbers or overflows differently, please ensure that the transformation logic reliably throws the expected exception for these border cases across all target environments.

- File: `tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php` (Lines 28–38)
- Verify that invalid transformations using these values consistently trigger an exception regardless of the execution environment.

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (2)</summary>

`1-14`: **Well-structured class definition and documentation.**

The class declaration and docblocks are well-organized, making it clear this file provides a foundational, abstract float array type with robust validation rules. Good introduction of abstract methods to enforce constraints in child classes.

---

`26-69`: **Solid validation logic with thorough checks for numeric range and precision.**

The `isValidArrayItemForDatabase()` method effectively filters out invalid or out-of-range values. The approach of expanding scientific notation to standard form and splitting on the decimal point is clear and reduces the likelihood of silent precision loss.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-26T18:24:08Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php (1)</summary><blockquote>

`25-35`: **Consider adding PHPDoc with return type annotation.**

The `provideInvalidTransformations` method is missing PHPDoc with a return type annotation similar to the one in `provideValidTransformations`. Adding it would improve consistency and code documentation.


```diff
+/**
+ * @return array<int, array{0: string}>
+ */
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)
    ]);
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5a668bfa94627b49f5d2eecd2961ed03032dbe66 and f6b52ac891849cf1d6348c01d595b69efcc39741.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer 1.2
* GitHub Check: PHP 8.2 + Doctrine ORM 2.18 + Doctrine Lexer latest
* GitHub Check: PHP 8.2 + Doctrine ORM 2.18 + Doctrine Lexer 3.0

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php (4)</summary>

`40-68`: **Test case coverage looks good.**

The test cases for valid transformations cover a comprehensive range of inputs, including boundary values, normal values, and edge cases, which is excellent for ensuring robustness.

---

`70-79`: **Good explicit test for boundary condition.**

Great job including a specific test for the "too close to zero" error case. This ensures proper error handling and messaging for this boundary condition.

---

`1-15`: **Class structure looks good.**

The test class is well-organized, properly extends the base test case, and correctly initializes the fixture in the setUp method.

---

`17-23`: **Simple but effective name test.**

This test ensures that the type name is correctly registered, which is important for proper integration with Doctrine.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-26T18:55:26Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php (1)</summary><blockquote>

`12-15`: **Consider calling parent::setUp()**

For consistency with DoublePrecisionArrayTest, consider adding a call to parent::setUp() in your setUp method.

```diff
    protected function setUp(): void
    {
+        parent::setUp();
        $this->fixture = new RealArray();
    }
```

</blockquote></details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php (1)</summary><blockquote>

`26-39`: **Some redundancy in invalid transformation test cases.**

Lines 34-37 include test cases that are already defined in the parent class's provideInvalidTransformations method (as seen in the referenced BaseFloatArrayTestCase snippet). Consider removing these redundant cases to improve maintainability.

```diff
    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
        ]);
    }
```

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary><blockquote>

`102-110`: **Consider using more specific error messages for out-of-range values.**

While the validation is correct, the error message for out-of-range values is the same generic one used for invalid format values. Consider using a more specific error message for clarity.

```diff
 if ($floatValue < (float) $this->getMinValue() || $floatValue > (float) $this->getMaxValue()) {
-    throw InvalidFloatArrayItemForPHPException::forValueThatIsNotAValidPHPFloat($item, static::TYPE_NAME);
+    throw InvalidFloatArrayItemForPHPException::forValueOutOfRange($item, static::TYPE_NAME);
 }
```

This would require adding a new method to the `InvalidFloatArrayItemForPHPException` class.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f6b52ac891849cf1d6348c01d595b69efcc39741 and 59c63a396065d89992b83626bb52d2c4c1763316.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php` (1 hunks)
* `src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php` (2 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php` (1 hunks)
* `tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (4)</summary>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php (3)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php (1)</summary>

* `InvalidFloatArrayItemForPHPException` (14-35)

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (3)</summary>

* `BaseFloatArrayTestCase` (11-81)
* `provideInvalidTransformations` (28-41)
* `provideValidTransformations` (69-69)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (1)</summary>

* `transformArrayItemForPHP` (84-127)

</details>

</blockquote></details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php (1)</summary><blockquote>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (1)</summary>

* `throws_domain_exception_when_invalid_array_item_value` (74-80)

</details>

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (4)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php (8)</summary>

* `InvalidFloatArrayItemForDatabaseException` (14-55)
* `isNotANumber` (21-24)
* `doesNotMatchRegex` (26-29)
* `isAScientificNotationWithExcessPrecision` (31-34)
* `isANormalNumberWithExcessPrecision` (36-39)
* `isBelowMinValue` (41-44)
* `isAboveMaxValue` (46-49)
* `absoluteValueIsTooCloseToZero` (51-54)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php (4)</summary>

* `InvalidFloatArrayItemForPHPException` (14-35)
* `forValueThatIsNotAValidPHPFloat` (21-24)
* `forValueThatIsTooCloseToZero` (26-29)
* `forValueThatExceedsMaximumPrecision` (31-34)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/RealArray.php (4)</summary>

* `getMinValue` (19-22)
* `getMaxValue` (24-27)
* `getMaxPrecision` (29-32)
* `getMinAbsoluteValue` (34-37)

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArray.php (4)</summary>

* `getMinValue` (19-22)
* `getMaxValue` (24-27)
* `getMaxPrecision` (29-32)
* `getMinAbsoluteValue` (34-37)

</details>

</blockquote></details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php (1)</summary><blockquote>

<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php (1)</summary>

* `create` (16-19)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: PHP 8.1 + Doctrine ORM 2.14 + Doctrine Lexer 1.2
* GitHub Check: PHP 8.4 + Doctrine ORM latest + Doctrine Lexer latest
* GitHub Check: PHP 8.4 + Doctrine ORM 2.18 + Doctrine Lexer 3.0
* GitHub Check: PHP 8.4 + Doctrine ORM 3.0 + Doctrine Lexer 3.0
* GitHub Check: sloth

</details>

<details>
<summary>🔇 Additional comments (30)</summary><blockquote>

<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTest.php (4)</summary>

`1-24`: **Well-structured test class with appropriate type name verification.**

The test class is correctly set up to test RealArray functionality with proper type name verification.

---

`25-35`: **Comprehensive test cases for invalid transformations.**

Good coverage of invalid transformations specific to the REAL type constraints, including values exceeding bounds, precision limits, and values too close to zero.

---

`37-68`: **Appropriate test cases for valid transformations.**

The test cases cover a good range of valid values within the constraints of PostgreSQL's REAL type.

---

`70-103`: **Well-structured exception testing.**

The exception tests appropriately verify that the correct exceptions are thrown with proper error messages for values too close to zero or exceeding precision limits.

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseArrayTest.php (2)</summary>

`97-97`: **Good naming change for more domain-specific test method.**

Renaming from "throws_conversion_exception_when_invalid_array_item_value" to "throws_domain_exception_when_invalid_array_item_value" improves clarity about the nature of the exception scenario being tested.

---

`113-113`: **Good naming change for consistency.**

Renaming follows the same pattern as the previous method, improving consistency and clarity in test method naming.

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTest.php (3)</summary>

`10-24`: **Well-structured test class with proper setup and type verification.**

The class correctly extends BaseFloatArrayTestCase and initializes the DoublePrecisionArray fixture.

---

`41-54`: **Good test cases for valid transformations.**

The test cases appropriately include scientific notation which is supported by DOUBLE PRECISION type, showing good attention to type-specific details.

---

`56-102`: **Comprehensive exception testing.**

The tests thoroughly check for appropriate exceptions when values violate the DOUBLE PRECISION type constraints, with good coverage of edge cases.

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForPHPException.php (3)</summary>

`1-35`: **Well-designed exception class with clear error messages.**

This exception class provides a clean abstraction for handling specific validation failures during float array conversion. The factory methods for different validation scenarios and the formatted error messages improve code readability and maintainability.

---

`16-19`: **Good use of private helper method for DRY error message creation.**

The private create method eliminates duplication in error message formatting across different validation scenarios.

---

`21-34`: **Well-defined factory methods for specific error scenarios.**

Each factory method provides a clear, specific error message that helps identify the exact nature of the validation failure, which will be very helpful for debugging.

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php (8)</summary>

`1-15`: **New abstract class follows good design principles.**

The abstract class is well-structured with a clear purpose of handling float array data types. It properly extends `BaseArray` and includes the necessary exception imports.

---

`17-25`: **Good design with abstract constraints.**

The abstract methods approach ensures concrete implementations must define their specific constraints (min/max values, precision, etc.), which is a good use of abstraction.

---

`27-36`: **Clean error handling pattern.**

Using a try-catch block to wrap validation logic and return a boolean result is a clean pattern that keeps the public method simple while delegating the detailed validation to a private method.

---

`38-64`: **Thorough validation logic for database items.**

The validation logic is comprehensive, checking for:
1. Type validation (must be a number)
2. Regex pattern validation
3. Precision validation for both scientific notation and regular decimal formats

This ensures data integrity when storing values in the database.

---

`66-74`: **Proper comparison with explicit type casting.**

The code correctly casts the string values to float before comparison, avoiding potential string comparison issues mentioned in previous feedback.

---

`76-82`: **Good handling of values close to zero.**

The check for values too close to zero will help prevent potential precision issues when working with very small numbers that might be outside PostgreSQL's supported range.

---

`84-98`: **PHP transformation has consistent validation.**

The validation logic in `transformArrayItemForPHP` mirrors the validation in `isValidArrayItemForDatabase`, ensuring consistency when transforming values. The handling of null values is also implemented correctly.

---

`112-127`: **Consistent handling of scientific notation and decimal formats.**

The code correctly handles both scientific notation and regular decimal formats. The special case for scientific notation (lines 114-116) is appropriate since precision is already validated during the conversion to float.

</details>
<details>
<summary>tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php (7)</summary>

`1-14`: **Well-structured test case class.**

The test case is properly organized with the right imports and a clear abstract base class pattern that will allow specific implementations to extend it.

---

`15-23`: **Test method correctly validates detection of invalid values.**

This test method properly checks that invalid values are detected by the `isValidArrayItemForDatabase` method.

---

`25-41`: **Comprehensive test data for invalid transformations.**

The data provider includes a wide range of invalid values, including edge cases like invalid scientific notation formats, which is good for thorough testing.

---

`43-51`: **Test method name doesn't match what's being tested.**

The method is named `can_transform_from_php_value`, but it only tests if the value is valid for the database using `isValidArrayItemForDatabase`. It doesn't actually test the transformation process itself.


```diff
-    public function can_transform_from_php_value(float $phpValue, string $postgresValue): void
+    public function can_validate_php_value_for_database(float $phpValue, string $postgresValue): void
     {
         self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue));
     }
```

Alternatively, modify the test to actually check the transformation:

```diff
     public function can_transform_from_php_value(float $phpValue, string $postgresValue): void
     {
         self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue));
+        // Test actual transformation
+        $transformedValue = $this->fixture->convertToDatabaseValue([$phpValue], \Doctrine\DBAL\Platforms\PostgreSQLPlatform::class);
+        self::assertStringContainsString($postgresValue, $transformedValue);
     }
```

---

`53-61`: **Good test for PHP value transformation.**

This test correctly verifies that string values from the database are properly transformed into PHP float values.

---

`63-69`: **Clear type definitions in PHPDoc.**

The return type annotation for `provideValidTransformations` is well-defined with specific array structure, which helps with type checking and IDE support.

---

`71-80`: **Fixed scientific notation exception test.**

The test for invalid scientific notation has been corrected to use a more generic assertion message and a valid test case (`1.e234` instead of the previously hardcoded `1.23e4`), addressing previous feedback.

</details>
<details>
<summary>src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php (3)</summary>

`1-15`: **Well-structured exception class.**

The exception class properly extends Doctrine's `ConversionException` and includes appropriate namespace and documentation.

---

`16-19`: **Clean factory pattern for exception creation.**

The private `create` method follows good practice by centralizing the error message formatting, making the code DRY and easier to maintain.

---

`21-54`: **Comprehensive set of specific exception factory methods.**

The class provides a rich set of factory methods for different error conditions, each with a clear and descriptive error message. This will make debugging easier when issues occur.


One observation: The implementation in `InvalidFloatArrayItemForDatabaseException` is similar to `InvalidFloatArrayItemForPHPException` but with slight differences in method signatures. The former takes only `$value` while the latter takes both `$value` and `$type`. This is consistent with their usage patterns in the code.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-26T19:55:37Z

Review comments:
diff_hunk: @@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException;
+use MartinGeorgiev\Doctrine\DBAL\Types\RealArray;
+
+class RealArrayTestCase extends BaseFloatArrayTestCase
+{
+    protected function setUp(): void
+    {
+        $this->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<int, array{phpValue: float, postgresValue: string}>
+     */
+    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");
path: tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Exception message inconsistency**

The exception message test expects "too close to zero for PostgreSQL REAL type", but the `InvalidFloatValueException::forValueThatIsTooCloseToZero` method appears to hardcode "DOUBLE PRECISION type" in its message according to the relevant code snippets.

This test will likely fail unless the exception message is customized per type. Consider modifying the exception class to use the correct type name dynamically.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-26T17:58:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,115 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MartinGeorgiev\Doctrine\DBAL\Types;
+
+use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidFloatValueException;
+
+/**
+ * @since 3.0
+ *
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
+ */
+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();
path: src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix string comparison for min/max checks.**

Using `$stringValue < $this->getMinValue()` results in a string-based comparison rather than a numeric one. Convert `$stringValue` to float before comparing to avoid incorrect rejections:

```diff
-$isInvalidPHPFloat = !\preg_match(self::FLOAT_REGEX, $stringValue)
-    || $stringValue < $this->getMinValue()
-    || $stringValue > $this->getMaxValue();
+$floatValue = (float) $stringValue;
+$isInvalidPHPFloat = !\preg_match(self::FLOAT_REGEX, $stringValue)
+    || $floatValue < (float) $this->getMinValue()
+    || $floatValue > (float) $this->getMaxValue();
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        $stringValue = (string) $item;
+       $floatValue = (float) $stringValue;
-       $isInvalidPHPFloat = !\preg_match(self::FLOAT_REGEX, $stringValue)
-           || $stringValue < $this->getMinValue()
-           || $stringValue > $this->getMaxValue();
+       $isInvalidPHPFloat = !\preg_match(self::FLOAT_REGEX, $stringValue)
+           || $floatValue < (float) $this->getMinValue()
+           || $floatValue > (float) $this->getMaxValue();
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit c13c75e
created_at: 2025-03-26T17:58:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,78 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Types\ConversionException;
+use MartinGeorgiev\Doctrine\DBAL\Types\BaseFloatArray;
+use PHPUnit\Framework\TestCase;
+
+abstract class BaseFloatArrayTestCase extends TestCase
+{
+    protected BaseFloatArray $fixture;
+
+    /**
+     * @test
+     *
+     * @dataProvider provideInvalidTransformations
+     */
+    public function can_detect_invalid_for_transformation_php_value(mixed $phpValue): void
+    {
+        self::assertFalse($this->fixture->isValidArrayItemForDatabase($phpValue));
+    }
+
+    /**
+     * @return list<mixed>
+     */
+    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<array{
+     *     phpValue: float,
+     *     postgresValue: string
+     * }>
+     */
+    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');
+    }
path: tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**The test hardcodes the scientific notation example.**

This test expects an exception specifically for the value `'1.23e4'`, but there's inconsistency with how scientific notation is handled in the implementation. The test should be made more generic or accommodate different behaviors for different float array types.

```diff
     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.");
+        // Get an invalid value from the data provider instead of hardcoding
+        $invalidValue = static::provideInvalidTransformations()[0][0];
+        $this->expectExceptionMessage("cannot be transformed to valid PHP float");
 
-        $this->fixture->transformArrayItemForPHP('1.23e4');
+        $this->fixture->transformArrayItemForPHP($invalidValue);
     }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    /**
     * @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.");
+        // Get an invalid value from the data provider instead of hardcoding
+        $invalidValue = static::provideInvalidTransformations()[0][0];
+        $this->expectExceptionMessage("cannot be transformed to valid PHP float");

-        $this->fixture->transformArrayItemForPHP('1.23e4');
+        $this->fixture->transformArrayItemForPHP($invalidValue);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-26T17:58:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,78 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Types\ConversionException;
+use MartinGeorgiev\Doctrine\DBAL\Types\BaseFloatArray;
+use PHPUnit\Framework\TestCase;
+
+abstract class BaseFloatArrayTestCase extends TestCase
+{
+    protected BaseFloatArray $fixture;
+
+    /**
+     * @test
+     *
+     * @dataProvider provideInvalidTransformations
+     */
+    public function can_detect_invalid_for_transformation_php_value(mixed $phpValue): void
+    {
+        self::assertFalse($this->fixture->isValidArrayItemForDatabase($phpValue));
+    }
+
+    /**
+     * @return list<mixed>
+     */
+    public static function provideInvalidTransformations(): array
+    {
+        return [
+            [true],
+            [null],
+            ['string'],
+            [[]],
+            [new \stdClass()],
+            ['1.23e4'], // Scientific notation not allowed
+        ];
+    }
path: tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**There's a discrepancy in scientific notation handling.**

In line 36, you're testing that scientific notation is "not allowed," but the `BaseFloatArray` implementation appears to support scientific notation based on the FLOAT_REGEX and special handling in the code from the `src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php` file.

This test case assumes scientific notation is invalid, but the concrete implementations in `DoublePrecisionArray` and `RealArray` have different behaviors - `DoublePrecisionArray` appears to accept scientific notation while `RealArray` rejects it.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there's a discrepancy between the test fixtures and implementation

# Look for scientific notation handling in the implementation
echo "Checking how scientific notation is handled in the implementation:"
rg -A 5 "\\\str_contains\(\\\$stringValue, 'e'\)" src/

# Check if DoublePrecisionArray allows scientific notation
echo "Checking if DoublePrecisionArray allows scientific notation:"
rg -A 5 "provideValidTransformations" tests/MartinGeorgiev/Doctrine/DBAL/Types/DoublePrecisionArrayTestCase.php

# Check if RealArray allows scientific notation  
echo "Checking if RealArray allows scientific notation:"
rg -A 5 "provideValidTransformations" tests/MartinGeorgiev/Doctrine/DBAL/Types/RealArrayTestCase.php
```

Length of output: 2690

---



</details>

**Action Required: Resolve Scientific Notation Discrepancy**

The current invalid transformations list in `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php` includes `'1.23e4'` as an invalid input. However, our implementation in `src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php` explicitly handles scientific notation (by converting it to a standard form) and both the `DoublePrecisionArrayTestCase` and `RealArrayTestCase` treat scientific notation as valid. This inconsistency means that the BaseFloatArray test case is misaligned with the actual behavior of its concrete implementations.

**Suggested changes:**
- **In `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php` (lines 25–38):**  
  • Remove or update the line testing `'1.23e4'` as an invalid transformation.  
- **Alternatively:**  
  • If the intended design was to disallow scientific notation for BaseFloatArray, then adjust the implementation and ensure consistency with the concrete classes’ behavior.

Please adjust the tests (or implementation) accordingly so that the handling of scientific notation is consistent across the codebase.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 5a668bf
created_at: 2025-03-26T17:58:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,78 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\MartinGeorgiev\Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Types\ConversionException;
+use MartinGeorgiev\Doctrine\DBAL\Types\BaseFloatArray;
+use PHPUnit\Framework\TestCase;
+
+abstract class BaseFloatArrayTestCase extends TestCase
+{
+    protected BaseFloatArray $fixture;
+
+    /**
+     * @test
+     *
+     * @dataProvider provideInvalidTransformations
+     */
+    public function can_detect_invalid_for_transformation_php_value(mixed $phpValue): void
+    {
+        self::assertFalse($this->fixture->isValidArrayItemForDatabase($phpValue));
+    }
+
+    /**
+     * @return list<mixed>
+     */
+    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));
+    }
path: tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**The test method name doesn't match the assertion.**

The method is named `can_transform_from_php_value`, but it only tests if the value is valid for the database using `isValidArrayItemForDatabase`. It doesn't actually test the transformation process itself.

```diff
-    public function can_transform_from_php_value(float $phpValue, string $postgresValue): void
+    public function can_validate_php_value_for_database(float $phpValue, string $postgresValue): void
     {
         self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue));
     }
```

Alternatively, modify the test to actually check the transformation:

```diff
     public function can_transform_from_php_value(float $phpValue, string $postgresValue): void
     {
         self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue));
+        // Test actual transformation
+        $transformedValue = $this->fixture->convertToDatabaseValue([$phpValue], \Doctrine\DBAL\Platforms\PostgreSQLPlatform::class);
+        self::assertStringContainsString($postgresValue, $transformedValue);
     }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    /**
     * @test
     *
     * @dataProvider provideValidTransformations
     */
-    public function can_transform_from_php_value(float $phpValue, string $postgresValue): void
+    public function can_validate_php_value_for_database(float $phpValue, string $postgresValue): void
    {
        self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue));
    }
`````

`````suggestion
    /**
     * @test
     *
     * @dataProvider provideValidTransformations
     */
    public function can_transform_from_php_value(float $phpValue, string $postgresValue): void
    {
        self::assertTrue($this->fixture->isValidArrayItemForDatabase($phpValue));
+        // Test actual transformation
+        $transformedValue = $this->fixture->convertToDatabaseValue([$phpValue], \Doctrine\DBAL\Platforms\PostgreSQLPlatform::class);
+        self::assertStringContainsString($postgresValue, $transformedValue);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-26T17:58:08Z
author_association: NONE

PR comments:
user: login: coveralls
created_at: 2025-03-26T17:50:28Z
body: 
[![Coverage Status](https://coveralls.io/builds/72940701/badge)](https://coveralls.io/builds/72940701)

coverage: 94.768% (-0.1%) from 94.913%
when pulling **59c63a396065d89992b83626bb52d2c4c1763316 on new-array-types**
into **c584099652b612cc4a89f24573fddcd7060bb4fe on main**.

author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-26T17:52:56Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The pull request restructures the PostgreSQL types documentation by expanding the table to include practical usage and internal catalog names, with existing types updated to their array counterparts and new types added. It introduces a new abstract class for handling float array types, along with two concrete implementations defining constraints for DOUBLE PRECISION[] and REAL[] types. Additionally, custom exception classes and extensive tests have been added to validate and ensure proper transformation behavior, with minor renamings in existing tests to reflect domain concerns.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `docs/AVAILABLE-TYPES.md` | Restructured table to add a "practical use" column; updated type entries to use array variants; added new types (`real[]` and `double precision[]`). |
| `src/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArray.php`, `.../DoublePrecisionArray.php`, `.../RealArray.php` | Introduced a new abstract `BaseFloatArray` with float validation and transformation logic; implemented type-specific classes (`DoublePrecisionArray` and `RealArray`) with defined numeric constraints. |
| `src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidFloatArrayItemForDatabaseException.php`, `.../InvalidFloatArrayItemForPHPException.php` | Added custom exception classes with static methods for detailed error messaging on invalid float array items. |
| `tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseFloatArrayTestCase.php`, `.../DoublePrecisionArrayTest.php`, `.../RealArrayTest.php`, `.../BaseArrayTest.php` | Introduced new test classes for the float array types; added tests for valid and invalid transformations and updated method names to indicate domain exceptions. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant Client
    participant FloatArray as BaseFloatArray
    participant TypeHandler as {DoublePrecisionArray / RealArray}
    participant ExceptionHandler

    Client->>FloatArray: transformArrayItemForPHP(input)
    FloatArray->>TypeHandler: Validate (min, max, precision)
    alt Valid Value
        TypeHandler-->>FloatArray: Validated value
        FloatArray-->>Client: Return converted float
    else Invalid Value
        TypeHandler-->>ExceptionHandler: Identify error type
        ExceptionHandler-->>Client: Throw exception with message
    end
```

## Poem
> Oh, I hop with glee, a coding rabbit so spry,  
> Watching arrays and floats leap high in the sky.  
> New types now dance, precise as a carrot’s bite,  
> With validations twinkling in code’s soft light.  
> Exceptions hop out when the numbers aren’t quite right,  
> Testing each step from morning till night.  
> Cheers to fresh code—let’s celebrate this flight! 🐰🥕

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=martin-georgiev/postgresql-for-doctrine&utm_content=307):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE