Skip to content

Commit d213967

Browse files
fix: address broken escaping of array items for JsonbArray and preserve integer values as strings if they are outside PHP integer range (#442)
1 parent 1b9aea6 commit d213967

File tree

4 files changed

+89
-6
lines changed

4 files changed

+89
-6
lines changed

src/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArray.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
namespace MartinGeorgiev\Doctrine\DBAL\Types;
66

7+
use MartinGeorgiev\Utils\PostgresArrayToPHPArrayTransformer;
78
use MartinGeorgiev\Utils\PostgresJsonToPHPArrayTransformer;
89

910
/**
1011
* Implementation of PostgreSQL JSONB[] data type.
1112
*
12-
* @see https://www.postgresql.org/docs/9.4/static/arrays.html
13+
* @see https://www.postgresql.org/docs/17/arrays.html
1314
* @since 0.1
1415
*
1516
* @author Martin Georgiev <martin.georgiev@gmail.com>
@@ -22,12 +23,18 @@ class JsonbArray extends BaseArray
2223

2324
protected function transformArrayItemForPostgres(mixed $item): string
2425
{
25-
return $this->transformToPostgresJson($item);
26+
// Quote each JSON value as a PostgreSQL array element and escape inner quotes and backslashes
27+
$escaped = \strtr(
28+
$this->transformToPostgresJson($item),
29+
['\\' => '\\\\', '"' => '\\"']
30+
);
31+
32+
return '"'.$escaped.'"';
2633
}
2734

2835
protected function transformPostgresArrayToPHPArray(string $postgresArray): array
2936
{
30-
return PostgresJsonToPHPArrayTransformer::transformPostgresArrayToPHPArray($postgresArray);
37+
return PostgresArrayToPHPArrayTransformer::transformPostgresArrayToPHPArray($postgresArray);
3138
}
3239

3340
/**

src/MartinGeorgiev/Utils/PostgresJsonToPHPArrayTransformer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static function transformPostgresArrayToPHPArray(string $postgresValue):
3939
public static function transformPostgresJsonEncodedValueToPHPArray(string $postgresValue): array
4040
{
4141
try {
42-
$transformedValue = \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR);
42+
$transformedValue = \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING);
4343
if (!\is_array($transformedValue)) {
4444
throw InvalidJsonArrayItemForPHPException::forInvalidType($postgresValue);
4545
}
@@ -57,7 +57,7 @@ public static function transformPostgresJsonEncodedValueToPHPValue(string $postg
5757
{
5858
try {
5959
// @phpstan-ignore-next-line
60-
return \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR);
60+
return \json_decode($postgresValue, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING);
6161
} catch (\JsonException) {
6262
throw InvalidJsonItemForPHPException::forInvalidType($postgresValue);
6363
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Integration\MartinGeorgiev\Doctrine\DBAL\Types;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
10+
class JsonbArrayTypeTest extends ArrayTypeTestCase
11+
{
12+
protected function getTypeName(): string
13+
{
14+
return 'jsonb[]';
15+
}
16+
17+
protected function getPostgresTypeName(): string
18+
{
19+
return 'JSONB[]';
20+
}
21+
22+
#[DataProvider('provideValidTransformations')]
23+
#[Test]
24+
public function can_handle_array_values(string $testName, array $arrayValue): void
25+
{
26+
parent::can_handle_array_values($testName, $arrayValue);
27+
}
28+
29+
/**
30+
* @return array<string, array{string, array<int, array<string, mixed>>}>
31+
*/
32+
public static function provideValidTransformations(): array
33+
{
34+
return [
35+
'simple jsonb array' => ['simple jsonb array', [
36+
['key1' => 'value1', 'key2' => false],
37+
['key1' => 'value2', 'key2' => true],
38+
]],
39+
'jsonb array with nested structures' => ['jsonb array with nested structures', [
40+
[
41+
'user' => ['id' => 1, 'name' => 'John'],
42+
'meta' => ['active' => true, 'roles' => ['admin', 'user']],
43+
],
44+
[
45+
'user' => ['id' => 2, 'name' => 'Jane'],
46+
'meta' => ['active' => false, 'roles' => ['user']],
47+
],
48+
]],
49+
'jsonb array with mixed types' => ['jsonb array with mixed types', [
50+
[
51+
'string' => 'value',
52+
'number' => 42,
53+
'boolean' => false,
54+
'null' => null,
55+
'array' => [1, 2, 3],
56+
'object' => ['a' => 1],
57+
],
58+
[
59+
'different' => 'structure',
60+
'count' => 999,
61+
'enabled' => true,
62+
],
63+
]],
64+
'jsonb array with big integers' => ['jsonb array with big integers', [
65+
[
66+
'bigint' => '9223372036854775807', // PHP_INT_MAX as string
67+
'regular' => 123,
68+
],
69+
[
70+
'huge_number' => '18446744073709551615', // Larger than PHP_INT_MAX
71+
'small' => 1,
72+
],
73+
]],
74+
];
75+
}
76+
}

tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/JsonbArrayTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public static function provideValidTransformations(): array
8282
'key5' => [304, 404, 504, 604],
8383
],
8484
],
85-
'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]}}',
85+
'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]}"}',
8686
],
8787
];
8888
}

0 commit comments

Comments
 (0)