innmind/ neo4j-onm
Neo4j 对象节点映射
Requires
- php: ~7.4|~8.0
- innmind/event-bus: ~4.0
- innmind/immutable: ~3.5
- innmind/neo4j-dbal: ~6.0
- innmind/reflection: ~4.0
- innmind/specification: ~2.0
- ramsey/uuid: ^3.2
Requires (Dev)
- innmind/cli: ~2.0
- innmind/coding-standard: ^1.1
- innmind/command-bus: ~4.0
- innmind/object-graph: ~2.0
- innmind/server-control: ~3.0
- innmind/time-continuum: ~2.0
- phpunit/phpunit: ~9.0
- vimeo/psalm: ~4.4
Suggests
- innmind/command-bus: To dispatch entities domain events
- innmind/time-continuum: To be able to use point_in_time type
This package is auto-updated.
Last update: 2023-11-01 14:26:33 UTC
README
这是一个ORM,用于Neo4j图形数据库,强调领域驱动设计(DDD)。它允许您轻松构建实体
、仓库
并通过规范
查询它们。另一个重要方面是,本库的每个模块都可以完全替换。
安装
运行以下命令通过composer将此库添加到您的项目中
composer require innmind/neo4j-onm
文档
结构
此库旨在持久化两种类型的对象:聚合
和关系
(两者都是实体)。第一种代表Neo4j中的一个节点,可以有一组子节点与之关联。只有主节点包含一个标识符
,子节点不能在它们的聚合之外进行查询。关系
代表Neo4j中的一个关系。它始终包含一个标识符
,以及表示关系的起始和结束聚合的标识符
。
根据DDD的描述,实体对象不是直接相互链接的;相反,它们包含它们所指向的实体的标识符。然而,当这些实体在图中持久化时,关系被正确地设置,正如您所期望的(允许任何其他脚本正常查询您的图)。例如,如果您想将两个聚合
连接在一起,您将创建一个新的包含两个聚合标识符的关系
;因此,您将需要持久化3个对象。
每个实体都由其自己的仓库
完全管理,这意味着它用于添加
、删除
、获取
和查询实体。
注意:出于性能考虑,当您将实体添加到其仓库时,它不会直接插入到图中。
要访问实体仓库,您将使用一个Manager
,它只包含4个方法:connection
、repository
、flush
和identities
。第一个提供对DBAL Connection
的访问,以便您可以打开/提交事务。方法repository
接受实体类以返回关联的仓库。flush
将持久化您从仓库中做出的所有修改。最后,identities
允许您生成指定类型的新的标识符
当您flush
时,修改持久化的顺序如下
- 插入新的聚合
- 插入新的关系(与聚合相同的查询中)
- 更新所有实体(没有特定顺序)
- 删除关系
- 删除聚合(与关系相同的查询中)
配置
您的第一个任务是编写实体映射。以下是一个完整的示例,说明您可以指定什么
use Innmind\Neo4j\ONM\{ Metadata\Aggregate, Metadata\Aggregate\Child, Metadata\Relationship, Metadata\ClassName, Metadata\Identity, Metadata\RelationshipType, Metadata\RelationshipEdge, Type, Type\StringType, Type\DateType, Identity\Uuid, }; use Innmind\Immutable\{ Map, Set, }; $image = Aggregate::of( new ClassName('Image'), new Identity('uuid', Uuid::class), Set::of('string', 'Image'), # labels Map::of('string', Type::class) ('url', new StringType), Set::of( Child::class, Child::of( new ClassName('Description'), Set::of('string', 'Description'), # labels Child\Relationship::of( new ClassName('DescriptionOf'), new RelationshipType('DESCRIPTION_OF'), 'rel', 'description', Map::of('string', Type::class) ('created', new DateType) ), Map::of('string', Type::class) ('content', new StringType) ) ) ); $relationship = Relationship::of( new ClassName('SomeRelationship'), new Identity('uuid', Uuid::class), new RelationshipType('SOME_RELATIONSHIP'), new RelationshipEdge('startProperty', Uuid::class, 'uuid'), new RelationshipEdge('endProperty', Uuid::class, 'uuid'), Map::of('string', Type::class) ('created', new DateType) );
用法
第一步是创建一个管理器
use function Innmind\Neo4j\ONM\bootstrap; use Innmind\Neo4j\DBAL\Connection; use Innmind\Immutable\Set; $services = bootstrap( /* instance of Connection */, Set::of(Entity::class, $image, $relationship) ); $manager = $services['manager'];
现在您已经有一个工作管理器,让我们来处理我们的实体
$images = $manager->repository(Image::class); $rels = $manager->repository(SomeRelationship::class); $image1 = new Image($manager->identities()->new(Uuid::class)); $image2 = new Image($manager->identities()->new(Uuid::class)); $rel = new SomeRelationship( $manager->identities()->new(Uuid::class), $image1->uuid(), $image2->uuid() ); $rels->add($rel); $images ->add($image1) ->add($image2); $manager->flush();
上面的示例将在您的图中创建给定的路径:(:Image {uuid: "some value"})-[:SOME_RELATIONSHIP {uuid: "some value"}]->(:Image {uuid: "some value"})
。
因此,即使在你的对象中,聚合和关系之间没有直接链接,它也会在你的图中创建一个具体的路径。因此,如果你尝试下面的代码,它会抛出一个异常,告诉你不能删除你的聚合,因为它是关系的一部分,这会防止你创建不一致性。
$images->remove($image1); $manager->flush(); //throw an exception at the database level
但是,以下代码在你确实需要删除聚合时会起作用。
$images->remove($image1); $rels->remove($rel); $manager->flush();
注意:如前所述,remove
调用的顺序无关紧要,因为库总是在删除聚合之前删除关系(当然,只删除你要求删除的关系)以防止数据库出现意外异常。
查询
现在你了解了如何添加/删除,让我们学习如何从图中查询我们的实体。
$image = images->get(new Uuid($_GET['wished_image_id']));
注意:这里使用 $_GET
仅为了保持框架无关性,即使你使用它,也会非常安全,因为 Uuid
验证数据(如你所见,这里)。
但仅通过标识符访问实体是不够的,这就是为什么有一个名为 matching
的方法作为存储库,它只允许一个参数,该参数必须是 规范。
规范非常适合查询对象,因为这个模式旨在验证对象是否匹配某个特定标准,这是我们检索实体时想要实现的目标。这个优点是它消除了代码库中的重复;不再需要特定的查询语言来查询你的对象。
示例
$entities = $images->matching( $spec = (new ImageOfDomain('example.org')) ->or(new ImageOfDomain('antoher.net')) ->and((new ImageOfDomain('exclude.net'))->not()) );
在这里,ImageOfDomain
会使用图像的 url
来检查它是否是希望之一。库可以将任何规范树转换为有效的 cypher 查询。而且因为 ImageOfDomain
应该实现一个类似 isSatisfiedBy
的方法,所以你可以重用 $spec
在你的代码的其他地方验证任何 Image
。
覆盖默认值
库足够解耦,所以大多数构建块都可以轻松替换,这允许你在你的用例有限的情况下改进它。
类型
默认情况下,你可以使用 7 种类型作为你的实体属性。
ArrayType
BooleanType
DateType
FloatType
IntType
SetType
(与ArrayType
类似,但使用不可变的Set
)StringType
要添加自己的类型,你需要创建一个实现 Type.php
的类。
实体转换器
在查询图以加载你的实体时,有一个步骤是将从连接返回的结果转换为类似你实体结构的原始结构化数据集合。这些数据之后被工厂用来创建你的实体。
如果你已经构建了一种新的实体元数据,你需要创建一个新的转换器。
use Innmind\Neo4jONM\Translation\EntityTranslator; use Innmind\Immutable\Map; class MyTranslator implements EntityTranslator { // your implementation ... } $services = bootstrap( /* instance of Connection */, Set::of(Entity::class), null, null, null, null, null, Map::of('string', EntityTranslator::class) (MyEntityMetadata::class, new MyTranslator) );
实体工厂
默认情况下,库使用 2 个工厂将原始数据转换为你的实体,这两个工厂都依赖于库的 Reflection
来构建对象。
如果你的实体过于复杂,无法通过默认工具构建,你可以构建自己的实体工厂来解决你的限制。
use Innmind\Neo4j\ONM\EntityFactory; class MyEntityFactory implements EntityFactory { // your implementation } $services = bootstrap( /* instance of Connection */, Set::of(Entity::class), null, null, null, null, Set::of(EntityTranslator::class, new MyEntityFactory) );
注意:为了使你的工厂真正被使用,你需要在实体映射中指定你的工厂的类。
身份生成器
默认情况下,这个库仅使用 UUIDs 作为身份对象。但你可以轻松地添加你自己的身份对象。
您需要创建一个实现 Identity
的身份类以及相应的实现 Generator
的生成器。
use Innmind\Neo4j\ONM\{ Identity, Identity\Generator }; class MyIdentity implements Identity { // your implementation ... } class MyIdentityGenerator implements Generator { // your implementation } $services = bootstrap( /* instance of Connection */, Set::of(Entity::class), Map::of('string', Generator::class) (MyIdentity::class, new MyIdentityGenerator) );