Skip to content

Commit 34ad85f

Browse files
committed
feat(type): add jsonb_document type for DBAL 4.3.0+
New jsonb_document type leveraging native DBAL JsonbType. Shared serialization logic extracted into JsonDocumentTypeTrait. Type is conditionally registered when DBAL 4.3.0+ is available.
1 parent 5c096f5 commit 34ad85f

File tree

7 files changed

+255
-84
lines changed

7 files changed

+255
-84
lines changed

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ If you use Doctrine directly, use a bootstrap code similar to the following:
3333

3434
require_once __DIR__.'/../vendor/autoload.php'; // Adjust to your path
3535

36+
use Doctrine\DBAL\Types\JsonbType;
3637
use Doctrine\DBAL\Types\Type;
3738
use Doctrine\ORM\EntityManager;
3839
use Doctrine\ORM\Tools\Setup;
3940
use Dunglas\DoctrineJsonOdm\Serializer;
41+
use Dunglas\DoctrineJsonOdm\Type\JsonbDocumentType;
4042
use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType;
4143
use Symfony\Component\Serializer\Encoder\JsonEncoder;
4244
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
@@ -45,11 +47,17 @@ use Symfony\Component\Serializer\Normalizer\UidNormalizer;
4547
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
4648
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
4749

50+
$serializer = new Serializer([new BackedEnumNormalizer(), new UidNormalizer(), new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()]);
51+
4852
if (!Type::hasType('json_document')) {
4953
Type::addType('json_document', JsonDocumentType::class);
50-
Type::getType('json_document')->setSerializer(
51-
new Serializer([new BackedEnumNormalizer(), new UidNormalizer(), new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()])
52-
);
54+
Type::getType('json_document')->setSerializer($serializer);
55+
}
56+
57+
// jsonb_document requires Doctrine DBAL 4.3.0+
58+
if (class_exists(JsonbType::class) && !Type::hasType('jsonb_document')) {
59+
Type::addType('jsonb_document', JsonbDocumentType::class);
60+
Type::getType('jsonb_document')->setSerializer($serializer);
5361
}
5462

5563
// Sample bootstrapping code here, adapt to fit your needs
@@ -70,6 +78,7 @@ return EntityManager::create($conn, $config);
7078
## Usage
7179

7280
Doctrine JSON ODM provides a `json_document` column type for properties of Doctrine entities.
81+
Starting with Doctrine DBAL 4.3.0+, a `jsonb_document` type is also available, leveraging native JSONB support.
7382

7483
The content of properties mapped with this type is serialized in JSON using the [Symfony Serializer](http://symfony.com/doc/current/components/serializer.html)
7584
then, it is stored in a dynamic JSON column in the database.
@@ -236,7 +245,18 @@ Doctrine ORM 2.6+ and DBAL 2.6+ are supported.
236245

237246
**How to use [the JSONB type of PostgreSQL](http://www.postgresql.org/docs/current/static/datatype-json.html)?**
238247

239-
Then, you need to set an option in the column mapping:
248+
With Doctrine DBAL 4.3.0+, use the dedicated `jsonb_document` type:
249+
250+
```php
251+
// ...
252+
253+
#[Column(type: 'jsonb_document')]
254+
public $foo;
255+
256+
// ...
257+
```
258+
259+
For older DBAL versions, set an option in the column mapping:
240260

241261
```php
242262
// ...

src/Bundle/DunglasDoctrineJsonOdmBundle.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
namespace Dunglas\DoctrineJsonOdm\Bundle;
1111

12+
use Doctrine\DBAL\Types\JsonbType;
1213
use Doctrine\DBAL\Types\Type;
14+
use Dunglas\DoctrineJsonOdm\Type\JsonbDocumentType;
1315
use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType;
1416
use Symfony\Component\HttpKernel\Bundle\Bundle;
1517

@@ -25,14 +27,25 @@ public function __construct()
2527
if (!Type::hasType('json_document')) {
2628
Type::addType('json_document', JsonDocumentType::class);
2729
}
30+
31+
if (class_exists(JsonbType::class) && !Type::hasType('jsonb_document')) {
32+
Type::addType('jsonb_document', JsonbDocumentType::class);
33+
}
2834
}
2935

3036
/**
3137
* {@inheritdoc}
3238
*/
3339
public function boot(): void
3440
{
41+
$serializer = $this->container->get('dunglas_doctrine_json_odm.serializer');
42+
3543
$type = Type::getType('json_document');
36-
$type->setSerializer($this->container->get('dunglas_doctrine_json_odm.serializer'));
44+
$type->setSerializer($serializer);
45+
46+
if (Type::hasType('jsonb_document')) {
47+
$type = Type::getType('jsonb_document');
48+
$type->setSerializer($serializer);
49+
}
3750
}
3851
}

src/Type/JsonDocumentType.php

Lines changed: 2 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99

1010
namespace Dunglas\DoctrineJsonOdm\Type;
1111

12-
use Doctrine\DBAL\Platforms\AbstractPlatform;
1312
use Doctrine\DBAL\Types\JsonType;
14-
use Symfony\Component\Serializer\SerializerInterface;
1513

1614
/**
1715
* The JSON document type.
@@ -20,84 +18,9 @@
2018
*/
2119
final class JsonDocumentType extends JsonType
2220
{
23-
public const NAME = 'json_document';
24-
25-
private SerializerInterface $serializer;
26-
27-
private string $format = 'json';
28-
29-
private array $serializationContext = [];
30-
31-
private array $deserializationContext = [];
32-
33-
/**
34-
* Sets the serializer to use.
35-
*/
36-
public function setSerializer(SerializerInterface $serializer): void
37-
{
38-
$this->serializer = $serializer;
39-
}
40-
41-
/**
42-
* Gets the serializer or throw an exception if it isn't available.
43-
*
44-
* @throws \RuntimeException
45-
*/
46-
private function getSerializer(): SerializerInterface
47-
{
48-
if (null === $this->serializer) {
49-
throw new \RuntimeException(sprintf('An instance of "%s" must be available. Call the "setSerializer" method.', SerializerInterface::class));
50-
}
51-
52-
return $this->serializer;
53-
}
54-
55-
/**
56-
* Sets the serialization format (default to "json").
57-
*/
58-
public function setFormat(string $format): void
59-
{
60-
$this->format = $format;
61-
}
62-
63-
/**
64-
* Sets the serialization context (default to an empty array).
65-
*/
66-
public function setSerializationContext(array $serializationContext): void
67-
{
68-
$this->serializationContext = $serializationContext;
69-
}
70-
71-
/**
72-
* Sets the deserialization context (default to an empty array).
73-
*/
74-
public function setDeserializationContext(array $deserializationContext): void
75-
{
76-
$this->deserializationContext = $deserializationContext;
77-
}
78-
79-
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
80-
{
81-
if (null === $value) {
82-
return null;
83-
}
84-
85-
return $this->getSerializer()->serialize($value, $this->format, $this->serializationContext);
86-
}
87-
88-
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
89-
{
90-
if (null === $value || $value === '') {
91-
return null;
92-
}
93-
94-
return $this->getSerializer()->deserialize($value, '', $this->format, $this->deserializationContext);
95-
}
21+
use JsonDocumentTypeTrait;
9622

97-
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
98-
{
99-
return true;
100-
}
23+
public const NAME = 'json_document';
10124

10225
public function getName(): string
10326
{

src/Type/JsonDocumentTypeTrait.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* (c) Kévin Dunglas <[email protected]>
5+
*
6+
* This source file is subject to the MIT license that is bundled
7+
* with this source code in the file LICENSE.
8+
*/
9+
10+
namespace Dunglas\DoctrineJsonOdm\Type;
11+
12+
use Doctrine\DBAL\Platforms\AbstractPlatform;
13+
use Symfony\Component\Serializer\SerializerInterface;
14+
15+
/**
16+
* Common serialization logic for JSON document types.
17+
*
18+
* @author Kévin Dunglas <[email protected]>
19+
*/
20+
trait JsonDocumentTypeTrait
21+
{
22+
private SerializerInterface $serializer;
23+
24+
private string $format = 'json';
25+
26+
private array $serializationContext = [];
27+
28+
private array $deserializationContext = [];
29+
30+
/**
31+
* Sets the serializer to use.
32+
*/
33+
public function setSerializer(SerializerInterface $serializer): void
34+
{
35+
$this->serializer = $serializer;
36+
}
37+
38+
/**
39+
* Gets the serializer or throw an exception if it isn't available.
40+
*
41+
* @throws \RuntimeException
42+
*/
43+
private function getSerializer(): SerializerInterface
44+
{
45+
if (null === $this->serializer) {
46+
throw new \RuntimeException(sprintf('An instance of "%s" must be available. Call the "setSerializer" method.', SerializerInterface::class));
47+
}
48+
49+
return $this->serializer;
50+
}
51+
52+
/**
53+
* Sets the serialization format (default to "json").
54+
*/
55+
public function setFormat(string $format): void
56+
{
57+
$this->format = $format;
58+
}
59+
60+
/**
61+
* Sets the serialization context (default to an empty array).
62+
*/
63+
public function setSerializationContext(array $serializationContext): void
64+
{
65+
$this->serializationContext = $serializationContext;
66+
}
67+
68+
/**
69+
* Sets the deserialization context (default to an empty array).
70+
*/
71+
public function setDeserializationContext(array $deserializationContext): void
72+
{
73+
$this->deserializationContext = $deserializationContext;
74+
}
75+
76+
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
77+
{
78+
if (null === $value) {
79+
return null;
80+
}
81+
82+
return $this->getSerializer()->serialize($value, $this->format, $this->serializationContext);
83+
}
84+
85+
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
86+
{
87+
if (null === $value || $value === '') {
88+
return null;
89+
}
90+
91+
return $this->getSerializer()->deserialize($value, '', $this->format, $this->deserializationContext);
92+
}
93+
94+
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
95+
{
96+
return true;
97+
}
98+
}

src/Type/JsonbDocumentType.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* (c) Kévin Dunglas <[email protected]>
5+
*
6+
* This source file is subject to the MIT license that is bundled
7+
* with this source code in the file LICENSE.
8+
*/
9+
10+
namespace Dunglas\DoctrineJsonOdm\Type;
11+
12+
use Doctrine\DBAL\Types\JsonbType;
13+
14+
/**
15+
* The JSONB document type.
16+
*
17+
* Requires Doctrine DBAL 4.3.0+.
18+
*
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
final class JsonbDocumentType extends JsonbType
22+
{
23+
use JsonDocumentTypeTrait;
24+
25+
public const NAME = 'jsonb_document';
26+
27+
public function getName(): string
28+
{
29+
return self::NAME;
30+
}
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* (c) Kévin Dunglas <[email protected]>
5+
*
6+
* This source file is subject to the MIT license that is bundled
7+
* with this source code in the file LICENSE.
8+
*/
9+
10+
namespace Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\Entity;
11+
12+
use Doctrine\ORM\Mapping as ORM;
13+
14+
/**
15+
* Entity using jsonb_document type.
16+
*
17+
* @author Kévin Dunglas <[email protected]>
18+
*/
19+
#[ORM\Entity]
20+
class ProductJsonb
21+
{
22+
#[
23+
ORM\Column(type: 'integer'),
24+
ORM\Id,
25+
ORM\GeneratedValue(strategy: 'AUTO'),
26+
]
27+
public $id;
28+
29+
#[ORM\Column(type: 'string')]
30+
public $name;
31+
32+
#[ORM\Column(type: 'jsonb_document', nullable: true)]
33+
public $attributes;
34+
}

0 commit comments

Comments
 (0)