innmind/neo4j-onm

此包已废弃,不再维护。未建议替代包。

Neo4j 对象节点映射

7.1.0 2021-02-14 15:20 UTC

README

Build Status codecov Type Coverage

这是一个ORM,用于Neo4j图形数据库,强调领域驱动设计(DDD)。它允许您轻松构建实体仓库并通过规范查询它们。另一个重要方面是,本库的每个模块都可以完全替换。

安装

运行以下命令通过composer将此库添加到您的项目中

composer require innmind/neo4j-onm

文档

结构

此库旨在持久化两种类型的对象:聚合关系(两者都是实体)。第一种代表Neo4j中的一个节点,可以有一组子节点与之关联。只有主节点包含一个标识符,子节点不能在它们的聚合之外进行查询。关系代表Neo4j中的一个关系。它始终包含一个标识符,以及表示关系的起始和结束聚合的标识符

根据DDD的描述,实体对象不是直接相互链接的;相反,它们包含它们所指向的实体的标识符。然而,当这些实体在图中持久化时,关系被正确地设置,正如您所期望的(允许任何其他脚本正常查询您的图)。例如,如果您想将两个聚合连接在一起,您将创建一个新的包含两个聚合标识符的关系;因此,您将需要持久化3个对象。

每个实体都由其自己的仓库完全管理,这意味着它用于添加删除获取和查询实体。

注意:出于性能考虑,当您将实体添加到其仓库时,它不会直接插入到图中。

要访问实体仓库,您将使用一个Manager,它只包含4个方法:connectionrepositoryflushidentities。第一个提供对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)
);

对象图