dunglas / doctrine-json-odm
使用现代关系型数据库管理系统(RDBMS)的JSON类型,为Doctrine ORM提供对象文档映射器。
Requires
- php: >=8.1
- doctrine/orm: ^2.6.3 || ^3.0.0
- symfony/property-access: ^5.4 || ^6.0 || ^7.0
- symfony/property-info: ^5.4 || ^6.0 || ^7.0
- symfony/serializer: ^5.4 || ^6.0 || ^7.0
Requires (Dev)
- doctrine/annotations: ^1.0 || ^2.0.0
- doctrine/dbal: ^2.7 || ^3.3 || ^4.0.0
- doctrine/doctrine-bundle: ^1.12.13 || ^2.2
- symfony/finder: ^5.4 || ^6.0 || ^7.0
- symfony/framework-bundle: ^5.4 || ^6.0 || ^7.0
- symfony/phpunit-bridge: ^6.0 || ^7.0
- symfony/uid: ^5.4 || ^6.0 || ^7.0
- symfony/validator: ^5.4 || ^6.0 || ^7.0
Suggests
- scienta/doctrine-json-functions: To add support for JSON functions in DQL.
- symfony/framework-bundle: To use the provided bundle.
README
为Doctrine ORM提供一个对象-文档映射器(ODM),利用现代关系型数据库管理系统(RDBMS)的新JSON类型。
你是否梦想过一款工具,可以将传统、高效的关系映射与现代无模式以及类似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
如果您正在使用Symfony或API 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 是否支持嵌套对象和对象图?
是的。
我能否使用本地的 PostgreSQL 和 MySQL /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 帮助维护此包,谢谢!