From d89699c5e225c8a9634688bcc54daa7e607edf45 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 01:31:44 +0300 Subject: [PATCH 1/3] fix: address fundamentally broken `JsonbArray` parsing --- .../Doctrine/DBAL/Types/JsonbArray.php | 17 ++++- .../DBAL/Types/JsonbArrayTypeTest.php | 66 +++++++++++++++++++ .../Doctrine/DBAL/Types/JsonbArrayTest.php | 2 +- 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php index 558377cf..01a8f589 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php @@ -4,12 +4,13 @@ namespace MartinGeorgiev\Doctrine\DBAL\Types; +use MartinGeorgiev\Utils\PostgresArrayToPHPArrayTransformer; use MartinGeorgiev\Utils\PostgresJsonToPHPArrayTransformer; /** * Implementation of PostgreSQL JSONB[] data type. * - * @see https://www.postgresql.org/docs/9.4/static/arrays.html + * @see https://www.postgresql.org/docs/17/arrays.html * @since 0.1 * * @author Martin Georgiev @@ -22,12 +23,22 @@ class JsonbArray extends BaseArray protected function transformArrayItemForPostgres(mixed $item): string { - return $this->transformToPostgresJson($item); + // Quote each JSON value as a PostgreSQL array element and escape inner quotes and backslashes + $json = $this->transformToPostgresJson($item); + $escaped = \str_replace(['\\', '"'], ['\\\\', '\\"'], $json); + + return '"'.$escaped.'"'; } protected function transformPostgresArrayToPHPArray(string $postgresArray): array { - return PostgresJsonToPHPArrayTransformer::transformPostgresArrayToPHPArray($postgresArray); + $trimmed = \trim($postgresArray); + if ($trimmed === '{}') { + return []; + } + + // Standard array literal with quoted JSON elements + return PostgresArrayToPHPArrayTransformer::transformPostgresArrayToPHPArray($postgresArray); } /** diff --git a/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php new file mode 100644 index 00000000..6ad6cc9c --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php @@ -0,0 +1,66 @@ +>}> + */ + public static function provideValidTransformations(): array + { + return [ + 'simple jsonb array' => ['simple jsonb array', [ + ['key1' => 'value1', 'key2' => false], + ['key1' => 'value2', 'key2' => true], + ]], + 'jsonb array with nested structures' => ['jsonb array with nested structures', [ + [ + 'user' => ['id' => 1, 'name' => 'John'], + 'meta' => ['active' => true, 'roles' => ['admin', 'user']], + ], + [ + 'user' => ['id' => 2, 'name' => 'Jane'], + 'meta' => ['active' => false, 'roles' => ['user']], + ], + ]], + 'jsonb array with mixed types' => ['jsonb array with mixed types', [ + [ + 'string' => 'value', + 'number' => 42, + 'boolean' => false, + 'null' => null, + 'array' => [1, 2, 3], + 'object' => ['a' => 1], + ], + [ + 'different' => 'structure', + 'count' => 999, + 'enabled' => true, + ], + ]], + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTest.php b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTest.php index 5af80832..fe6cd1e7 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTest.php @@ -82,7 +82,7 @@ public static function provideValidTransformations(): array 'key5' => [304, 404, 504, 604], ], ], - 'postgresValue' => '{{"key1":"value1","key2":false,"key3":"15","key4":15,"key5":[112,242,309,310]},{"key1":"value2","key2":true,"key3":"115","key4":115,"key5":[304,404,504,604]}}', + 'postgresValue' => '{"{\"key1\":\"value1\",\"key2\":false,\"key3\":\"15\",\"key4\":15,\"key5\":[112,242,309,310]}","{\"key1\":\"value2\",\"key2\":true,\"key3\":\"115\",\"key4\":115,\"key5\":[304,404,504,604]}"}', ], ]; } From 32c5116db3d22f8d49f15c3cce31edfc919f9fe8 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 15:17:01 +0300 Subject: [PATCH 2/3] no message --- src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php index 01a8f589..f5a090bb 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php @@ -37,7 +37,6 @@ protected function transformPostgresArrayToPHPArray(string $postgresArray): arra return []; } - // Standard array literal with quoted JSON elements return PostgresArrayToPHPArrayTransformer::transformPostgresArrayToPHPArray($postgresArray); } From 595edf615342952d0ebf4307ba80a8dfba549cb9 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 15:30:07 +0300 Subject: [PATCH 3/3] no message --- src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php | 11 ++++------- .../Utils/PostgresJsonToPHPArrayTransformer.php | 4 ++-- .../Doctrine/DBAL/Types/JsonbArrayTypeTest.php | 10 ++++++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php index f5a090bb..2379988c 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php @@ -24,19 +24,16 @@ class JsonbArray extends BaseArray protected function transformArrayItemForPostgres(mixed $item): string { // Quote each JSON value as a PostgreSQL array element and escape inner quotes and backslashes - $json = $this->transformToPostgresJson($item); - $escaped = \str_replace(['\\', '"'], ['\\\\', '\\"'], $json); + $escaped = \strtr( + $this->transformToPostgresJson($item), + ['\\' => '\\\\', '"' => '\\"'] + ); return '"'.$escaped.'"'; } protected function transformPostgresArrayToPHPArray(string $postgresArray): array { - $trimmed = \trim($postgresArray); - if ($trimmed === '{}') { - return []; - } - return PostgresArrayToPHPArrayTransformer::transformPostgresArrayToPHPArray($postgresArray); } diff --git a/src/MartinGeorgiev/Utils/PostgresJsonToPHPArrayTransformer.php b/src/MartinGeorgiev/Utils/PostgresJsonToPHPArrayTransformer.php index 9718f188..65d29b85 100644 --- a/src/MartinGeorgiev/Utils/PostgresJsonToPHPArrayTransformer.php +++ b/src/MartinGeorgiev/Utils/PostgresJsonToPHPArrayTransformer.php @@ -39,7 +39,7 @@ public static function transformPostgresArrayToPHPArray(string $postgresValue): public static function transformPostgresJsonEncodedValueToPHPArray(string $postgresValue): array { try { - $transformedValue = \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR); + $transformedValue = \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING); if (!\is_array($transformedValue)) { throw InvalidJsonArrayItemForPHPException::forInvalidType($postgresValue); } @@ -57,7 +57,7 @@ public static function transformPostgresJsonEncodedValueToPHPValue(string $postg { try { // @phpstan-ignore-next-line - return \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR); + return \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING); } catch (\JsonException) { throw InvalidJsonItemForPHPException::forInvalidType($postgresValue); } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php index 6ad6cc9c..7b991ac9 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTypeTest.php @@ -61,6 +61,16 @@ public static function provideValidTransformations(): array 'enabled' => true, ], ]], + 'jsonb array with big integers' => ['jsonb array with big integers', [ + [ + 'bigint' => '9223372036854775807', // PHP_INT_MAX as string + 'regular' => 123, + ], + [ + 'huge_number' => '18446744073709551615', // Larger than PHP_INT_MAX + 'small' => 1, + ], + ]], ]; } }