Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ If you use Doctrine directly, use a bootstrap code similar to the following:

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

use Doctrine\DBAL\Types\JsonbType;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;
use Dunglas\DoctrineJsonOdm\Serializer;
use Dunglas\DoctrineJsonOdm\Type\JsonbDocumentType;
use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
Expand All @@ -45,11 +47,17 @@ use Symfony\Component\Serializer\Normalizer\UidNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

$serializer = new Serializer([new BackedEnumNormalizer(), new UidNormalizer(), new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()]);

if (!Type::hasType('json_document')) {
Type::addType('json_document', JsonDocumentType::class);
Type::getType('json_document')->setSerializer(
new Serializer([new BackedEnumNormalizer(), new UidNormalizer(), new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()])
);
Type::getType('json_document')->setSerializer($serializer);
}

// jsonb_document requires Doctrine DBAL 4.3.0+
if (class_exists(JsonbType::class) && !Type::hasType('jsonb_document')) {
Type::addType('jsonb_document', JsonbDocumentType::class);
Type::getType('jsonb_document')->setSerializer($serializer);
}

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

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

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

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

Then, you need to set an option in the column mapping:
With Doctrine DBAL 4.3.0+, use the dedicated `jsonb_document` type:

```php
// ...

#[Column(type: 'jsonb_document')]
public $foo;

// ...
```

For older DBAL versions, set an option in the column mapping:

```php
// ...
Expand Down
15 changes: 14 additions & 1 deletion src/Bundle/DunglasDoctrineJsonOdmBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

namespace Dunglas\DoctrineJsonOdm\Bundle;

use Doctrine\DBAL\Types\JsonbType;
use Doctrine\DBAL\Types\Type;
use Dunglas\DoctrineJsonOdm\Type\JsonbDocumentType;
use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -25,14 +27,25 @@ public function __construct()
if (!Type::hasType('json_document')) {
Type::addType('json_document', JsonDocumentType::class);
}

if (class_exists(JsonbType::class) && !Type::hasType('jsonb_document')) {
Type::addType('jsonb_document', JsonbDocumentType::class);
}
}

/**
* {@inheritdoc}
*/
public function boot(): void
{
$serializer = $this->container->get('dunglas_doctrine_json_odm.serializer');

$type = Type::getType('json_document');
$type->setSerializer($this->container->get('dunglas_doctrine_json_odm.serializer'));
$type->setSerializer($serializer);

if (Type::hasType('jsonb_document')) {
$type = Type::getType('jsonb_document');
$type->setSerializer($serializer);
}
}
}
81 changes: 2 additions & 79 deletions src/Type/JsonDocumentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@

namespace Dunglas\DoctrineJsonOdm\Type;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\JsonType;
use Symfony\Component\Serializer\SerializerInterface;

/**
* The JSON document type.
Expand All @@ -20,84 +18,9 @@
*/
final class JsonDocumentType extends JsonType
{
public const NAME = 'json_document';

private SerializerInterface $serializer;

private string $format = 'json';

private array $serializationContext = [];

private array $deserializationContext = [];

/**
* Sets the serializer to use.
*/
public function setSerializer(SerializerInterface $serializer): void
{
$this->serializer = $serializer;
}

/**
* Gets the serializer or throw an exception if it isn't available.
*
* @throws \RuntimeException
*/
private function getSerializer(): SerializerInterface
{
if (null === $this->serializer) {
throw new \RuntimeException(sprintf('An instance of "%s" must be available. Call the "setSerializer" method.', SerializerInterface::class));
}

return $this->serializer;
}

/**
* Sets the serialization format (default to "json").
*/
public function setFormat(string $format): void
{
$this->format = $format;
}

/**
* Sets the serialization context (default to an empty array).
*/
public function setSerializationContext(array $serializationContext): void
{
$this->serializationContext = $serializationContext;
}

/**
* Sets the deserialization context (default to an empty array).
*/
public function setDeserializationContext(array $deserializationContext): void
{
$this->deserializationContext = $deserializationContext;
}

public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
{
if (null === $value) {
return null;
}

return $this->getSerializer()->serialize($value, $this->format, $this->serializationContext);
}

public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
{
if (null === $value || $value === '') {
return null;
}

return $this->getSerializer()->deserialize($value, '', $this->format, $this->deserializationContext);
}
use JsonDocumentTypeTrait;

public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}
public const NAME = 'json_document';

public function getName(): string
{
Expand Down
98 changes: 98 additions & 0 deletions src/Type/JsonDocumentTypeTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/*
* (c) Kévin Dunglas <[email protected]>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Dunglas\DoctrineJsonOdm\Type;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Symfony\Component\Serializer\SerializerInterface;

/**
* Common serialization logic for JSON document types.
*
* @author Kévin Dunglas <[email protected]>
*/
trait JsonDocumentTypeTrait
{
private SerializerInterface $serializer;

private string $format = 'json';

private array $serializationContext = [];

private array $deserializationContext = [];

/**
* Sets the serializer to use.
*/
public function setSerializer(SerializerInterface $serializer): void
{
$this->serializer = $serializer;
}

/**
* Gets the serializer or throw an exception if it isn't available.
*
* @throws \RuntimeException
*/
private function getSerializer(): SerializerInterface
{
if (null === $this->serializer) {
throw new \RuntimeException(sprintf('An instance of "%s" must be available. Call the "setSerializer" method.', SerializerInterface::class));
}

return $this->serializer;
}

/**
* Sets the serialization format (default to "json").
*/
public function setFormat(string $format): void
{
$this->format = $format;
}

/**
* Sets the serialization context (default to an empty array).
*/
public function setSerializationContext(array $serializationContext): void
{
$this->serializationContext = $serializationContext;
}

/**
* Sets the deserialization context (default to an empty array).
*/
public function setDeserializationContext(array $deserializationContext): void
{
$this->deserializationContext = $deserializationContext;
}

public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
{
if (null === $value) {
return null;
}

return $this->getSerializer()->serialize($value, $this->format, $this->serializationContext);
}

public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
{
if (null === $value || $value === '') {
return null;
}

return $this->getSerializer()->deserialize($value, '', $this->format, $this->deserializationContext);
}

public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}
}
31 changes: 31 additions & 0 deletions src/Type/JsonbDocumentType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* (c) Kévin Dunglas <[email protected]>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Dunglas\DoctrineJsonOdm\Type;

use Doctrine\DBAL\Types\JsonbType;

/**
* The JSONB document type.
*
* Requires Doctrine DBAL 4.3.0+.
*
* @author Kévin Dunglas <[email protected]>
*/
final class JsonbDocumentType extends JsonbType
{
use JsonDocumentTypeTrait;

public const NAME = 'jsonb_document';

public function getName(): string
{
return self::NAME;
}
}
15 changes: 11 additions & 4 deletions tests/Fixtures/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* with this source code in the file LICENSE.
*/

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Dunglas\DoctrineJsonOdm\Bundle\DunglasDoctrineJsonOdmBundle;
use Dunglas\DoctrineJsonOdm\Tests\Fixtures\TestBundle\DependencyInjection\MakeServicesPublicPass;
Expand Down Expand Up @@ -49,14 +50,20 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa
'http_method_override' => false,
]);

$ormConfig = [
'auto_generate_proxy_classes' => true,
'auto_mapping' => true,
];

if (class_exists(AsDoctrineListener::class)) {
$ormConfig[\PHP_VERSION_ID >= 80400 ? 'enable_native_lazy_objects' : 'enable_lazy_ghost_objects'] = true;
}

$container->loadFromExtension('doctrine', [
'dbal' => [
'url' => '%env(resolve:DATABASE_URL)%',
],
'orm' => [
'auto_generate_proxy_classes' => true,
'auto_mapping' => true,
],
'orm' => $ormConfig,
]);

// Make a few services public until we depend on Symfony 4.1+ and can use the new test container
Expand Down
Loading
Loading