dunglas/doctrine-json-odm

使用现代关系型数据库管理系统(RDBMS)的JSON类型,为Doctrine ORM提供对象文档映射器。

v1.4.1 2024-02-05 09:21 UTC

This package is auto-updated.

Last update: 2024-09-05 10:28:31 UTC


README

Doctrine ORM提供一个对象-文档映射器(ODM),利用现代关系型数据库管理系统(RDBMS)的新JSON类型。

tests Scrutinizer Code Quality StyleCI

你是否梦想过一款工具,可以将传统、高效的关系映射与现代无模式以及类似NoSQL的映射混合在一起创建强大的数据模型?

使用Doctrine JSON ODM,现在可以轻松创建和查询这种混合数据模型。得益于现代RDBMS的JSON类型,查询无模式文档变得简单、强大且速度飞快(性能类似于MongoDB数据库)!您甚至可以为这些文档定义索引

Doctrine JSON ODM允许将PHP对象作为JSON文档存储在现代数据库的动态列中。它与PostgreSQL(≥9.4)的JSON和JSONB列以及MySQL(≥5.7.8)的JSON列类型兼容。

有关Doctrine JSON ODM背后的概念的更多信息,请查看Benjamin Eberlei在Symfony Catalunya 2016上提供的演示文稿

安装

要安装库,请使用PHP包管理器Composer

composer require dunglas/doctrine-json-odm

如果您正在使用SymfonyAPI Platform,您不需要做任何事情!如果您直接使用Doctrine,请使用以下类似的引导代码

<?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\UidNormalizer;
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 UidNormalizer(), 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);

用法

Doctrine JSON ODM为Doctrine实体的属性提供json_document列类型。

使用此类型映射的属性内容使用Symfony Serializer进行序列化,然后存储在数据库中的动态JSON列中。

当对象被填充时,该列的JSON内容将转换回其原始值,再次得益于Symfony Serializer。所有PHP对象和结构都将得到保留。

您可以使用json_document类型在属性中存储任何类型的(可序列化)PHP数据结构。

示例

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;
}

将随机对象图存储在数据库的JSON类型中

// $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();

检索对象图

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

使用类型别名

使用自定义类型别名(而不是完全限定类名FQCN)作为#type有一些好处

  • 如果您移动或重命名文档类,您只需更新您的类型映射,无需迁移数据库内容
  • 对于可能存储数百万条记录的JSON文档的应用程序,这还可以节省一些存储空间

您可以在任何时候引入类型别名。已经持久化的包含类名的JSON文档仍然可以正确反序列化。

使用Symfony

为了使用类型别名,请添加包配置,例如在 config/packages/doctrine_json_odm.yaml

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

有了这个,Foo 对象将被序列化为

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

另一个选项是使用自定义类型映射器实现 Dunglas\DoctrineJsonOdm\TypeMapperInterface。为此,只需覆盖服务定义

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

没有 Symfony

在实例化 Dunglas\DoctrineJsonOdm\Serializer 时,您需要传递一个实现 Dunglas\DoctrineJsonOdm\TypeMapperInterface 的额外参数。

对于使用内置类型映射器

    // …
    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)
    );

更新嵌套属性时的限制

由于 Doctrine 的工作方式,它不会检测嵌套对象或属性的更改。原因是 Doctrine 通过引用比较对象以优化 UPDATE 查询。如果您遇到没有执行任何 UPDATE 查询的问题,您可能需要在设置它之前对对象进行 clone。这样,Doctrine 就会注意到更改。有关更多信息,请参阅 #21

常见问题解答

支持哪些数据库管理系统 (DBMS)?

支持 PostgreSQL 9.4+ 和 MySQL 5.7+。

支持哪些 Doctrine 版本?

支持 Doctrine ORM 2.6+ 和 DBAL 2.6+。

如何使用 PostgreSQL 的 JSONB 类型

然后,您需要设置列映射中的选项

// ...

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

// ...

ODM 是否支持嵌套对象和对象图?

是的。

我能否使用本地的 PostgreSQLMySQL /JSON 函数?

是的!您可以使用 原生查询 执行复杂的查询。

或者,安装 scienta/doctrine-json-functions 以能够在 DQL 和查询构建器中运行 JSON 函数。

如何更改序列化/反序列化上下文

您可能需要更改序列化/反序列化上下文,例如,以避免转义反斜杠。

如果您使用的是 Symfony,请按如下方式修改您的 Kernel

<?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([/* ... */]);
    }
}

如何添加额外的规范器?

Symfony 序列化器很容易扩展。此包注册并使用 ID 为 dunglas_doctrine_json_odm.serializer 的服务作为 JSON 类型的序列化器。这意味着我们可以在 services.yaml 中轻松覆盖它以使用额外的规范器。例如,我们注入一个自定义规范器服务。请注意,规范器的顺序可能与您使用的规范器相关。

    # 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.uid', '@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

当使用的实体命名空间更改时

对于没有 类型别名 的类,因为我们将在数据库中存储 #type 和数据,所以您必须将数据库中已存在的数据迁移以反映新的命名空间。

示例:如果我们有一个从 AppBundle 迁移到 App 的项目,我们在数据库中有一个 AppBundle/Entity/Bar 命名空间,它必须变成 App/Entity/Bar

当您使用 MySQL 时,您可以使用此查询来迁移数据

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

鸣谢

此包由 Kévin Dunglas出色的贡献者 提供。由 Les-Tilleuls.coop 赞助。

前任维护者

Yanick Witschi 帮助维护此包,谢谢!