• Stars
    star
    525
  • Rank 84,404 (Top 2 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An object document mapper for Doctrine ORM using JSON types of modern RDBMS.

Doctrine JSON ODM

An Object-Document Mapper (ODM) for Doctrine ORM leveraging new JSON types of modern RDBMS.

tests Scrutinizer Code Quality StyleCI

Did you ever dream of a tool creating powerful data models mixing traditional, efficient relational mappings with modern schema-less and NoSQL-like ones?

With Doctrine JSON ODM, it's now possible to create and query such hybrid data models with ease. Thanks to modern JSON types of RDBMS, querying schema-less documents is easy, powerful and fast as hell (similar in performance to a MongoDB database)! You can even define indexes for those documents.

Doctrine JSON ODM allows to store PHP objects as JSON documents in modern, dynamic columns of an RDBMS. It works with JSON and JSONB columns of PostgreSQL (>= 9.4) and the JSON column type of MySQL (>= 5.7.8).

For more information about concepts behind Doctrine JSON ODM, take a look at the presentation given by Benjamin Eberlei at Symfony Catalunya 2016.

Install

To install the library, use Composer, the PHP package manager:

composer require dunglas/doctrine-json-odm

If you are using Symfony or API Platform, you don't need to do anything else! If you use Doctrine directly, use a bootstrap code similar to the following:

<?php

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

use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;
use Dunglas\DoctrineJsonOdm\Serializer;
use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

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

// Sample bootstrapping code here, adapt to fit your needs
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration([__DIR__ . '/../src'], $_ENV['DEBUG'] ?? false); // Adapt to your path

$conn = [
    'dbname' => $_ENV['DATABASE_NAME'],
    'user' => $_ENV['DATABASE_USER'],
    'password' => $_ENV['DATABASE_PASSWORD'],
    'host' => $_ENV['DATABASE_HOST'],
    'driver' => 'pdo_mysql' // or pdo_pgsql
];

return EntityManager::create($conn, $config);

Usage

Doctrine JSON ODM provides a json_document column type for properties of Doctrine entities.

The content of properties mapped with this type is serialized in JSON using the Symfony Serializer then, it is stored in a dynamic JSON column in the database.

When the object will be hydrated, the JSON content of this column is transformed back to its original values, thanks again to the Symfony Serializer. All PHP objects and structures will be preserved.

You can store any type of (serializable) PHP data structures in properties mapped using the json_document type.

Example:

namespace App\Entity;

use Doctrine\ORM\Mapping\{Entity, Column, Id, GeneratedValue};

// This is a typical Doctrine ORM entity.
#[Entity]
class Foo
{
  #[Column]
  #[Id]
  #[GeneratedValue]
  public int $id;

  #[Column]
  public string $name;

  // Can contain anything: array, objects, nested objects...
  #[Column(type: 'json_document', options: ['jsonb' => true])]
  public $misc;

  // Works with private and protected methods with getters and setters too.
}
namespace App\Entity;

// This is NOT an entity! It's a POPO (Plain Old PHP Object). It can contain anything.
class Bar
{
    public string $title;
    public float $weight;
}
namespace App\Entity;

// This is NOT an entity. It's another POPO and it can contain anything.
class Baz
{
    public string $name;
    public int $size;
}

Store a graph of random object in the JSON type of the database:

// $entityManager = $managerRegistry->getManagerForClass(Foo::class);

$bar = new Bar();
$bar->title = 'Bar';
$bar->weight = 12.3;

$baz = new Baz();
$baz->name = 'Baz';
$baz->size = 7;

$foo = new Foo();
$foo->name = 'Foo';
$foo->misc = [$bar, $baz];

$entityManager->persist($foo);
$entityManager->flush();

Retrieve the object graph back:

$foo = $entityManager->find(Foo::class, $foo->getId());
var_dump($foo->misc); // Same as what we set earlier

Using type aliases

Using custom type aliases as #type rather than FQCNs has a couple of benefits:

  • In case you move or rename your document classes, you can just update your type map without migrating database content
  • For applications that might store millions of records with JSON documents, this can also save some storage space

You can introduce type aliases at any point in time. Already persisted JSON documents with class names will still get deserialized correctly.

Using Symfony

In order to use type aliases, add the bundle configuration, e.g. in config/packages/doctrine_json_odm.yaml:

dunglas_doctrine_json_odm:
    type_map:
        foo: App\Something\Foo
        bar: App\SomethingElse\Bar

With this, Foo objects will be serialized as:

{ "#type": "foo", "someProperty": "someValue" }

Another option is to use your own custom type mapper implementing Dunglas\DoctrineJsonOdm\TypeMapperInterface. For this, just override the service definition:

services:
    dunglas_doctrine_json_odm.type_mapper: '@App\Something\MyFancyTypeMapper'

Without Symfony

When instantiating Dunglas\DoctrineJsonOdm\Serializer, you need to pass an extra argument that implements Dunglas\DoctrineJsonOdm\TypeMapperInterface.

For using the built-in type mapper:

    // …
    use Dunglas\DoctrineJsonOdm\Serializer;
    use Dunglas\DoctrineJsonOdm\TypeMapper;
    use App\Something\Foo;
    use App\SomethingElse\Bar;
    
    // For using the built-in type mapper:
    $typeMapper = new TypeMapper([
        'foo' => Foo::class,
        'bar' => Bar::class,
    ]);
    
    // Or implement TypeMapperInterface with your own class:
    $typeMapper = new MyTypeMapper();

    // Then pass it into the Serializer constructor
    Type::getType('json_document')->setSerializer(
        new Serializer([new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()], $typeMapper)
    );

Limitations when updating nested properties

Due to how Doctrine works, it will not detect changes to nested objects or properties. The reason for this is that Doctrine compares objects by reference to optimize UPDATE queries. If you experience problems where no UPDATE queries are executed, you might need to clone the object before you set it. That way Doctrine will notice the change. See #21 for more information.

FAQ

What DBMS are supported?

PostgreSQL 9.4+ and MySQL 5.7+ are supported.

Which versions of Doctrine are supported?

Doctrine ORM 2.6+ and DBAL 2.6+ are supported.

How to use the JSONB type of PostgreSQL?

Then, you need to set an option in the column mapping:

// ...

    #[Column(type: 'json_document', options: ['jsonb' => true])]
    public $foo;

// ...

Does the ODM support nested objects and object graphs?

Yes.

Can I use the native PostgreSQL and MySQL /JSON functions?

Yes! You can execute complex queries using native queries.

Alternatively, install scienta/doctrine-json-functions to be able to use run JSON functions in DQL and query builders.

How to change the (de)serialization context

You may need to change the (de)serialization context, for instance to avoid escaping slashes.

If you are using Symfony, modify your Kernel like this:

<?php
// src/Kernel.php

declare(strict_types=1);

namespace App;

use Doctrine\DBAL\Types\Type;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Serializer\Encoder\JsonEncode;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    public function boot(): void
    {
        parent::boot();

        $type = Type::getType('json_document');
        $type->setSerializationContext([JsonEncode::OPTIONS => JSON_UNESCAPED_SLASHES]);
        $type->setDeserializationContext([/* ... */]);
    }
}

How can I add additional normalizers?

The Symfony Serializer is easily extensible. This bundle registers and uses a service with ID dunglas_doctrine_json_odm.serializer as the serializer for the JSON type. This means we can easily override it in our services.yaml to use additional normalizers. As an example we inject a custom normalizer service. Be aware that the order of the normalizers might be relevant depending on the normalizers you use.

    # Add DateTime Normalizer to Dunglas' Doctrine JSON ODM Bundle
    dunglas_doctrine_json_odm.serializer:
        class: Dunglas\DoctrineJsonOdm\Serializer
        arguments:
          - ['@App\MyCustom\Normalizer', '@?dunglas_doctrine_json_odm.normalizer.backed_enum', '@dunglas_doctrine_json_odm.normalizer.datetime', '@dunglas_doctrine_json_odm.normalizer.array', '@dunglas_doctrine_json_odm.normalizer.object']
          - ['@serializer.encoder.json']
          - '@?dunglas_doctrine_json_odm.type_mapper'
        public: true
        autowire: false
        autoconfigure: false

When the namespace of a used entity changes

For classes without type aliases, because we store the #type along with the data in the database, you have to migrate the already existing data in your database to reflect the new namespace.

Example: If we have a project that we migrate from AppBundle to App, we have the namespace AppBundle/Entity/Bar in our database which has to become App/Entity/Bar instead.

When you use MySQL, you can use this query to migrate the data:

UPDATE Baz
SET misc = JSON_REPLACE(misc, '$."#type"', 'App\\\Entity\\\Bar')
WHERE 'AppBundle\\\Entity\\\Bar' = JSON_EXTRACT(misc, '$."#type"');

Credits

This bundle is brought to you by Kévin Dunglas and awesome contributors. Sponsored by Les-Tilleuls.coop.

Former Maintainers

Yanick Witschi helped maintain this bundle, thanks!

More Repositories

1

frankenphp

🧟 The modern PHP app server
Go
6,849
star
2

vulcain

🔨 Fast and idiomatic client-driven REST APIs.
Go
3,513
star
3

mercure

An open, easy, fast, reliable and battery-efficient solution for real-time communications
Go
3,349
star
4

symfony-docker

A Docker-based installer and runtime for Symfony. Install: download and `docker compose up`.
Dockerfile
2,354
star
5

react-esi

React ESI: Blazing-fast Server-Side Rendering for React and Next.js
TypeScript
677
star
6

DunglasActionBundle

Symfony controllers, redesigned
PHP
258
star
7

phpdoc-to-typehint

Add scalar type hints and return types to existing PHP projects using PHPDoc annotations
PHP
226
star
8

DunglasAngularCsrfBundle

Automatic CSRF protection for JavaScript apps using a Symfony API
PHP
151
star
9

vaccin.click

Une extension Firefox pour trouver et réserver automatiquement votre créneau de vaccination COVID-19.
JavaScript
94
star
10

php-torcontrol

PHP TorControl, a library to control TOR
PHP
86
star
11

php-socialshare

Server-side social networks share counts and share links retriever
PHP
80
star
12

DunglasTodoMVCBundle

A TodoMVC implementation wrote with Symfony, Chaplin.js and Backbone.js
PHP
78
star
13

solid-client-php

PHP library for accessing data and managing permissions on data stored in a Solid Pod
PHP
59
star
14

frankenphp-demo

Demo app for FrankenPHP
HTML
59
star
15

httpsfv

A Go library to parse and serialize HTTP structured field values
Go
57
star
16

symfonycon-lisbon

A joind.in clone built with Symfony 4 and Vue.js
PHP
39
star
17

demo-vulcain-api-platform

Use API Platform with the Vulcain protocol and Varnish!
JavaScript
34
star
18

php-to-json-schema

Creates a JSON Schema from a PHP class
PHP
32
star
19

stack2slack

A Slack bot to monitor StackOverflow/StackExchange tags
Go
29
star
20

frankenphp-wordpress

WordPress on FrankenPHP
Dockerfile
29
star
21

php-property-info

Retrieve type and description of PHP properties using various sources
PHP
27
star
22

prestashop-html5-theme

HTML5 Prestashop tempate enhanced for SEO with Google Rich Snippets support
Smarty
27
star
23

DunglasDigitalOceanBundle

DigitalOcean API v2 client for Symfony and API Platform
PHP
25
star
24

frankenphp-drupal

Drupal on FrankenPHP
Dockerfile
23
star
25

blog-api

A demonstration blog API for the API Platform framework
PHP
22
star
26

stripe-invoice-exporter

Download all your Stripe PDF invoices in bulk.
PHP
20
star
27

jquery.confirmExit

jQuery confirm before exit plugin
JavaScript
16
star
28

calavera

A (static) Single Page Application generator using Markdown files
Go
16
star
29

ShopApiPlugin

PHP
16
star
30

kdDoctrineGuardFacebookConnectPlugin

Facebook Connect symfony plugin (extends sfGuardPlugin)
PHP
16
star
31

demo-postgres-listen-notify

Demo of the PostgreSQL LISTEN/NOTIFY support in Symfony Messenger
PHP
14
star
32

DunglasTorControlBundle

Integration of PHP TorControl library in Symfony
PHP
13
star
33

blog-client

A demonstration blog client for the API Platform framework
ApacheConf
12
star
34

symfony-demo-mercure

A demo project using Symfony's Mercure integration
PHP
10
star
35

docker-private-composer-packages

Example: Securely Access Private Composer Packages
Dockerfile
9
star
36

uri-template-tester

Test if a URI matches a given URI template (RFC6570)
HTML
8
star
37

planning

Planning management written in Symfony2 and Doctrine 2
PHP
8
star
38

symfony-lock

Symfony lock
8
star
39

piy

A modern self-managed Content Management System
PHP
7
star
40

api-parallelism-benchmark

A benchmark comparing HTTP/2 Server Push to GraphQL-Like compound documents
HTML
6
star
41

Elgg-profile_friendlyurl

Creates friendly URLs for user's Elgg profiles as subdomains.
PHP
6
star
42

forumphp2016

PHP
6
star
43

api-platform-heroku

Helpers to use API Platform and Symfony applications on Heroku.
PHP
5
star
44

Elgg-fblink

Link a Facebook and an Elgg account
PHP
5
star
45

dunglas

My GitHub profile!
5
star
46

php-basics

Cours de PHP (en français)
JavaScript
5
star
47

Elgg-presence

Friends Online on Elgg, Facebook and Twitter
PHP
4
star
48

frankenphp-website

The website of FrankenPHP
HTML
4
star
49

Elgg-twitterlogin

Login to Elgg using Twitter
PHP
4
star
50

Signal-TLS-Proxy

Dockerfile
3
star
51

vclient-web

Web interface for Viessmann Vito heating system
Python
3
star
52

debian-hosting

Automatically exported from code.google.com/p/debian-hosting
Python
3
star
53

Elgg-groups_bookmarkswidget

Elgg plugin to display a bookmarks widget in groups homepages
PHP
3
star
54

workshop-mercure

Code produced during my Mercure workshop
PHP
3
star
55

mercure-reproducer-cors

Reproducer for CORS issues with the Mercure.rocks Hub
HTML
3
star
56

slides-sfLive-2015

Slides de ma présentation au Symfony Live 2015
JavaScript
3
star
57

php-documention-generator

3
star
58

workshop-panther

Code produced during by Panther workshop
PHP
2
star
59

demo-autowiring

Symfony autowiring demo
PHP
2
star
60

slides-sfPot-2015-07-10

Slides: using PSR-7 with the Symfony framework
JavaScript
2
star
61

.github

My GitHub files
2
star
62

api-platform-crud-demo

JavaScript
2
star
63

slides-sfPot-2015-01-15

API-first et Linked Data avec Symfony
JavaScript
2
star
64

symfony-psr7-benchmark

Benchmark PSR-7 support in Symfony
PHP
1
star
65

ajax-syntax-highlighter

Automatically exported from code.google.com/p/ajax-syntax-highlighter
JavaScript
1
star
66

selfpublish

Automatically exported from code.google.com/p/selfpublish
PHP
1
star
67

training-apr-19

PHP
1
star
68

test-again

Vue
1
star
69

symfony-4-0

Symfony 4.0 benchmark on phpbenchmarks.com
PHP
1
star
70

scoopeo

Automatically exported from code.google.com/p/scoopeo
1
star
71

easyubuntu

Automatically exported from code.google.com/p/easyubuntu
1
star
72

serializer-pack

1
star
73

panther-legacy

Legacy releases of symfony/panther (including old versions of ChromeDriver and Geckodriver)
PHP
1
star
74

platform-on-platform-workshop

#APIDays: API Platform on Platform.sh workshop
JavaScript
1
star
75

crypt-manager

Automatically exported from code.google.com/p/crypt-manager
Python
1
star