earc/data

eArc - 显式架构框架 - 数据组件

0.0.1 2021-04-10 11:59 UTC

This package is auto-updated.

Last update: 2024-09-10 19:34:04 UTC


README

作为eArc库的强解耦数据组件。

如果您希望对持久化过程有最大控制权,请使用此库。在项目确定的最佳顺序中更新/请求数据库、缓存、搜索索引等,以优化实体持久化/加载处理。您甚至可以轻松地交换它们、堆叠它们或删除部分,而无需触及业务逻辑。

它与基于键值持久化的持久化方式配合最好,例如使用由redis服务器支持并带有搜索索引服务器的elasticsearch,以及基于内存的实体缓存。查看eArc组件earc/data-redis和earc/data-elasticsearchearc/data-elasticsearch以轻松集成。

也可以使用传统的sql数据库,但您需要付出更多努力。请查看编写自己的桥接器部分以获取更深入的见解。

目录

优点/缺点

优点

  • 强解耦 - 首先编写业务逻辑,然后决定持久化层(数据库、缓存、与实体相关的搜索索引)
  • 无配置开销 - 没有需要与数据库关系同步的属性。只需使用抽象类(或接口,如果您需要更多控制)。
  • 使用全局函数 - 初始化后,无需在任何类中注入存储库或管理器。
  • 无处不在的使用 - 保存和加载数据即使在纯函数和闭包中也有效。
  • 架构优化代码 - 处理以线性时间运行,时间取决于您的实体的大小和数量。无需担心实体管理器在持久化几个实体后变慢。
  • 支持所有标准依赖注入技术 - 如预/后加载/持久化/移除事件、唤醒/睡眠事件、级联删除/更新。
  • 支持不可变对象
  • 为完整的持久化基础设施编写事务
  • 可扩展的 - 集成几乎所有类型的持久化处理。

缺点

  • 对数据库的依赖 - 虽然它的耦合性比例如doctrine提供的耦合性要软得多。
  • 轻微的开销 - 实体、集合和事件处理需要一些编程逻辑

安装

通过composer安装earc数据库。

$ composer require earc/data

基本使用

需要导入data_*函数。请在您的index.php、引导程序或配置脚本中使用它。

use eArc\Data\Initializer;

Initializer::init();

然后将持久化基础设施注册到 onLoadonPersitonRemoveonFindonAutoPrimaryKey 事件。

use eArc\Data\ParameterInterface;

di_tag(ParameterInterface::TAG_ON_LOAD, MyCacheBridge::class);
di_tag(ParameterInterface::TAG_ON_LOAD, MyDataBaseBridge::class);

di_tag(ParameterInterface::TAG_ON_PERSIST, MyCacheBridge::class);
di_tag(ParameterInterface::TAG_ON_PERSIST, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_PERSIST, MySearchIndexBridge::class);

di_tag(ParameterInterface::TAG_ON_REMOVE, MyCacheBridge::class);
di_tag(ParameterInterface::TAG_ON_REMOVE, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_REMOVE, MySearchIndexBridge::class);

di_tag(ParameterInterface::TAG_ON_FIND, MySearchIndexBridge::class);
di_tag(ParameterInterface::TAG_ON_FIND, MyDataBaseBridge::class);

di_tag(ParameterInterface::TAG_ON_AUTO_PRIMARY_KEY, MyDataBaseBridge::class);

之后,可以通过 data_* 函数加载和保存实体。

$entity = new MyEntity('some arguments');

data_persist($entity); // saves the entity
$pk = $entity->getPrimaryKey(); // yields the primary key - may be set prior persistence
data_load(MyEntity::class, $pk); // returns the entity of MyEntity::class with the primary key $pk
data_delete($entity); // removes the entity
data_remove(MyEntity::class, $pk); // removes the entity without the need of being loaded before
data_find(MyEntity::class, ['name' => ['Anton', 'Max', 'Simon'], 'age' => 42]); // returns the primary keys for all MyEntity instances where the name property is equal to 'Anton', 'Max' or 'Simon' and the age property is 42

// expert functions - use only if you really know what you are doing
data_schedule($entity); // schedules the saving process until `data_persist` is called with any argument
data_detach(get_class($entity), [$pk]); // removes the entity from the earc/data entity cache

您可以从任何地方调用 data_* 函数(例如构造函数、方法、纯函数或普通代码)。

存在五种与实体相关的对象类型: 实体纯实体的集合嵌入式实体嵌入式集合不可变实体。相关的接口用于持久化/加载过程中。

实体

实体是与主键相关联的对象,用于标识它们(结合其类名)以进行加载。它们实现了 EntityInterfacePrimaryKeyInterface。要创建实体类,只需扩展 AbstractEntity 类并连接 primaryKey 属性。

要在其他实体中引用实体,必须使用主键作为属性(就像在 SQL 数据库中做的那样)。

use eArc\Data\Entity\AbstractEntity;

class MyReverencedEntity extends AbstractEntity {/*...*/}

class Entity extends AbstractEntity
{
    protected string $myReverencedEntityPK;
    
    public function setMyReverencedEntity(MyReverencedEntity $myReverencedEntity)
    {
        $this->myReverencedEntityPK = $myReverencedEntity->getPrimaryKey();
    }
    
    public function getMyReverencedEntity(): MyReverencedEntity
    {
        return data_load(MyReverencedEntity::class, $this->myReverencedEntityPK);
    }
}

这带来了优势,即实体之间的引用不需要由 earc/data 管理,并且您的引用实体是懒加载的。这大大节省了开销,并且无需进行更多编程即可提供更多控制。

集合

引用许多实体遵循相同的原理。为了使其容易,请使用 Collection 类。持久化/加载过程必须存在 CollectionInterface

use eArc\Data\Collection\Collection;
use eArc\Data\Entity\AbstractEntity;

class MyReverencedEntity extends AbstractEntity {/*...*/}

class EntityWithCollection extends AbstractEntity
{
    protected Collection $myReverencedEntityCollection;
    
    public function __construct()
    {
        $this->myReverencedEntityCollection = new Collection($this, MyReverencedEntity::class);
    }
    
    public function getMyReverencedEntityCollection(): Collection
    {
        return $this->myReverencedEntityCollection;
    }
}

$entityWithCollection = new EntityWithCollection();
$myReverencedEntity = new MyReverencedEntity();
$collection = $entityWithCollection->getMyReverencedEntityCollection();
$collection->add($myReverencedEntity->getPrimaryKey());
$collection->remove($myReverencedEntity->getPrimaryKey());
foreach ($collection as $primaryKey) {
    echo $primaryKey;
}
foreach ($collection->asArray() as $entity) {
    echo $entity->getPrimaryKey();
}

集合始终由一种类型的实体组成。

提示:如果您扩展了集合中使用的实体类型,则可以将此类型的实例添加到集合中,但 earc/data 既不保证保存额外数据,也不保证不保存。

嵌入式实体

如果两个实体紧密耦合(一个实体在处理另一个实体时几乎总是需要,反之亦然),例如在 XML 文档结构或产品及其属性中,您应考虑使用嵌入式实体。它们在加载和保存时的性能可能更好,因为底层数据结构只被访问一次。

嵌入式实体与根实体一起保存和加载。根实体始终是纯实体,具有主键。嵌入式实体没有主键。它们始终持有对其父(嵌入)实体的引用。嵌入式实体可以嵌入到其他嵌入式实体中。它们可以形成一个实体树。

它们实现了 EmbeddedEntityInterface。要创建嵌入式实体类,只需扩展 AbstractEmbeddedEntity 类并连接 ownerEntity 属性。

use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\AbstractEmbeddedEntity;

class MyEmbeddedEntity extends AbstractEmbeddedEntity
{
    //...
    public function setParent(MyRootEntity $parent)
    {
        $this->ownerEntity = $parent;    
    }
}

class MyRootEntity extends AbstractEntity
{
    protected MyEmbeddedEntity $myEmbeddedEntity;
    
    public function setMyReverencedEntity(MyEmbeddedEntity $myEmbeddedEntity)
    {
        $this->myEmbeddedEntity = $myEmbeddedEntity;
        $myEmbeddedEntity->setParent($this);
    }
    
    public function getMyEmbeddedEntity(): MyEmbeddedEntity
    {
        return $this->myEmbeddedEntity;
    }
}

嵌入式集合

嵌入式集合是嵌入在其他实体中的嵌入式实体的集合。对于它们,请使用 EmbeddedCollection 类。持久化/加载过程必须存在 EmbeddedCollectionInterface

use eArc\Data\Collection\EmbeddedCollection;
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\AbstractEmbeddedEntity;

class MyEmbeddedEntity extends AbstractEmbeddedEntity {/*...*/}

class MyRootEntity extends AbstractEntity
{
    protected EmbeddedCollection $myEmbeddedEntityCollection;
    
    public function __construct()
    {
        $this->myEmbeddedEntityCollection = new EmbeddedCollection($this, MyEmbeddedEntity::class);
    }
    
    public function getMyEmbeddedEntityCollection(): EmbeddedCollection
    {
        return $this->myEmbeddedEntityCollection;
    }
}

$rootEntity = new MyRootEntity();
$myEmbeddedEntity = new MyEmbeddedEntity();
$collection = $rootEntity->getMyEmbeddedEntityCollection();
$collection->add($myEmbeddedEntity);
$collection->remove($myEmbeddedEntity);
foreach ($collection as $entity) {
    echo $entity::class;
}
foreach ($collection->asArray() as $entity) {
    echo $entity::class;
}

嵌入式集合始终由一种类型的实体组成。

提示:如果您扩展了集合中使用的实体类型,则可以将此类型的实例添加到集合中,这一次 earc/data 保证保存额外数据。

嵌入式集合公开了一个 findBy() 方法,用于基于键值对方式搜索集合。

$rootEntity = new MyRootEntity();
$collection = $rootEntity->getMyEmbeddedEntityCollection();
$entities = $collection->findBy(['name' => 'Claudia', 'age' => [31,32,33]]);
foreach ($entities as $entity) {
    echo $entity::class === MyEmbeddedEntity::class
        && $entity->getName() === 'Claudia'
        && in_array($entity->getAge(), [31, 32, 33]) ? 'true' : 'something went wrong';
}

提示:如果您通过非嵌入式实体和非嵌入式集合的实体属性引用对象,earc/data 既不保证保存属性数据,也不保证不保存。应避免这种情况,但它是允许的。如果您使用自己的桥接器和序列化器,这可能是合理的。

函数

earc/data 使用函数而不是服务来为您的代码提供最大自由度。

数据持久化

data_persist 函数接受一个实体作为参数。将保存实体的数据。如果 getPrimaryKey() 方法返回 null,则抛出异常。只有一个例外:如果实体实现了 OnAutoPrimaryKeyInterface 并且至少一个注册到 OnAutoPrimaryKeyInterface 事件的调用返回字符串结果。那么这个结果被用作主键。

调用 data_persist 时,所有通过 data_schedule 安排的实体都将首先保存。可以不带参数调用 data_persist,以触发安排的实体的保存,而无需显式保存任何实体。

要保存多个实体,有一个 data_persist_batch 函数。

数据加载

data_load 函数接受类名和主键作为参数。它返回实体。

当提供相同的参数时,data_load 返回相同的实例。

您可以通过在中间调用 data_detach 来更改此行为。请注意,对于粗心的开发者来说,这种结果行为可能会出乎意料。请谨慎使用。

data_load 还接受一个可选的第三个标志。如果它设置为 DataStoreInterface::LOAD_FLAG_USE_FIRST_LEVEL_CACHE_ONLY,则只返回已加载的实体。如果设置为 DataStoreInterface::LOAD_FLAG_SKIP_FIRST_LEVEL_CACHE,earc/data 不会查找已加载的实体,也不会将加载的实体添加到已加载的实体中 - 获取的实体处于分离状态,将不同于之前或之后加载的相同实体。

有一个 data_load_batch 函数可以一次性加载多个实体。

数据删除

data_delete 函数接受一个实体作为参数。它删除实体数据。

实现 ImmutableEntityInterface 的实体只有在设置了强制标志的情况下才能被删除。

此函数有一个多用途对应函数 data_delete_batch

数据移除

data_remove 函数接受实体类名和主键作为参数。它的工作方式与 data_delete 相同,但不需要在删除之前加载实体。

要删除同一类的多个实体,请使用 data_remove_batch 函数。

数据查找

data_find 函数接受实体类名作为参数。如果未提供进一步参数,它返回相应持久实体的所有主键。

第二个可选参数接受键值对,键等于实体的属性名称。每个键的值必须是数据值或数据值数组。键值对通过逻辑 AND 联合。值数组被解释为 IN。并非所有键值对或值数组都受支持。这取决于使用的基础设施、设置(例如可用的 sql 索引)和桥接器的实现。如果某个或多个键值对不受支持,则抛出 QueryException

您可以使用简写 data_find_entities($fQCN, $keyValuePairs) 来替代调用 data_load_batch($fQCN, data_load($fQCN, $keyValuePairs)

提示:除了这个函数之外,可能还有更多查找实体的方式。这些不是 earc/data 抽象的一部分。

数据调度

服务器响应应该尽可能快。为了支持这个目标,earc/data 有 data_schedule 函数。提供的实体的持久化将被安排,直到调用 data_persist。如果在关闭函数中调用 data_persist,您可以在不因将数据写入数据库或索引而延迟的情况下构建和发送服务器响应。

数据分离

earc/data 保留对每个加载实体的引用,以确保每个实体只存在一次。为了绕过这种行为,可以调用 data_detach。它会删除引用。

data_detach 无参数将删除所有引用。以类名作为参数的 data_detach 将删除对该类实体所有引用。带有类名和主键的 data_detach 将删除对单个实体的引用。

调用 data_detach 的有三个原因:

  1. 在不删除实体的情况下更改主键。
  2. 从相同的数据生成不同的实例。
  3. 垃圾收集。

提示:如果您在未先调用 data_detach 的情况下更改持久实体的主键,可能会遇到意外行为。

生命周期事件

存在六个生命周期事件。它们由六个接口表示(PreLoadInterfacePostLoadInterfacePrePersistInterfacePostPersistInterfacePreRemoveInterfacePostRemoveInterface)。每个事件都作为实体事件和服务事件存在。服务事件在实体事件之前触发。每次事件触发时,都会调用已注册服务的接口方法,如果实体受到事件的影响,则仅调用实体的接口方法。

任何接口方法都会带有实体(如果方法是静态的)或类名和主键(如果方法是静态的)。不评估返回值,因此所有接口方法都具有void返回类型。

通过实体

要通过实体使用生命周期事件,实体必须实现相应的接口。

用例包括唤醒/睡眠程序或级联删除/持久化。

级联持久化的实现方式如下

use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\Interfaces\EntityInterface;
use \eArc\Data\Entity\Interfaces\Events\PrePersistInterface;

class MyReverencedEntity extends AbstractEntity {/*...*/}

class SomeEntity extends AbstractEntity implements PrePersistInterface
{
    protected string $myReverencedEntityPK;
    
    public function setMyReverencedEntity(MyReverencedEntity $myReverencedEntity)
    {
        $this->myReverencedEntityPK = $myReverencedEntity->getPrimaryKey();
    }
    
    public function getMyReverencedEntity(): MyReverencedEntity
    {
        return data_load(MyReverencedEntity::class, $this->myReverencedEntityPK);
    }
    
    public function prePersist(EntityInterface $entity): void
    {
        if ($entity = data_load(MyReverencedEntity::class, $this->myReverencedEntityPK, true)) {
            data_persist($entity);        
        }
    }
}

通过引用服务方法和属性实现可能更结构化,但可能也会慢一些。earc/data为您提供了根据需求调整或使用可用的第三方插件(如果有的话)的自由。

通过服务

警告:只有在真正需要全局处理事件或您的应用程序被不必要地减慢时才使用服务事件。

use eArc\Data\Entity\Interfaces\Events\PreRemoveInterface;

di_tag(PreRemoveInterface::class, MyPreRemoveService::class); // <- this comes in your bootstrap section


class MyPreRemoveService implements PreRemoveInterface 
{
    public static function preRemove(string $fQCN, string $primaryKey): void
    {
        // pre remove logic goes here...
    }
}

自动生成键

对于实现AutoPrimaryKeyInterface的纯不可变实体,如果调用persist_entity而没有合适的实体主键,主键将自动设置。

要处理密钥生成,存在OnAutoPrimaryKeyInterface。任何通过此接口名称标记为di_tag的类都将通过di_get被调用(有关更多信息,请参阅earc/di)。它必须实现OnAutoPrimaryKeyInterface。这种方式接收到的可调用对象将被调用(以实体作为参数),直到返回一个字符串。此字符串被设置为主键。

您完全负责密钥生成。例如,您可以使用属性驱动库或编写自己的生成器。某些桥接器也可能处理这一点。

桥接器

earc/data是一个抽象层。它负责两个主要API:1. 这是您保存/加载以及以有限方式查找实体时调用的API,而不必担心持久化媒体(文件系统、数据库、内存、搜索索引等)。2. 这是持久化基础设施插入的API。

到目前为止,所有这些都关于第一个API。桥接器章节是关于第二个API的。它告诉您如何插入持久化基础设施。

有五个生命周期事件调用持久化媒体,每个事件都有一个标签和接口

  • 数据持久化 -> OnPersistInterface -> ParameterInterface::TAG_ON_PERSIST
  • 数据加载 -> OnLoadInterface -> ParameterInterface::TAG_ON_LOAD
  • 数据删除 -> OnPersistInterface -> ParameterInterface::TAG_ON_PERSIST
  • 数据查找 -> OnFindInterface -> ParameterInterface::TAG_ON_FIND
  • 主键生成 -> OnAutoPrimaryKeyInterface -> ParameterInterface::TAG_ON_AUTO_PRIMARY_KEY

任何实现这五个接口的库都是earc/data的有效桥接器。

某些库可能只实现子集。例如,对于搜索索引,只有数据持久化/删除/查找事件与其相关。

存在一些预构建的桥接器

插入桥接器

要激活桥接器,您必须标记接口。

use eArc\Data\ParameterInterface;

di_tag(ParameterInterface::TAG_ON_PERSIST, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_LOAD, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_REMOVE, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_FIND, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_AUTO_PRIMARY_KEY, MyDataBaseBridge::class);

您可以注册任意数量的服务。标记的顺序就是它们被调用的顺序。

必须在初始化依赖注入组件earc/di之后,并在调用任何data_*函数之前进行标记。

use eArc\DI\DI;

DI::init();

如果您首先初始化earc/data,则可以跳过初始化earc/di。

use eArc\Data\Initializer;

Initializer::init();

这也会初始化earc/di。

高级使用

不可变实体

不可变实体是纯实体的特殊情况。它们实现了ImmutableEntityInterface。不可变实体不能更新,只能使用强制标志删除。

如果不可变实体实现了AutoPrimaryKeyInterface,则在持久化时将生成新主键,如果已存在具有此主键的数据集。

可变引用

不可变实体的常见用例是通过一系列不可变实体跟踪一个通用实体的变化。您不能通过主键引用由链表示的实体,因为每个不可变实体都有自己的主键。您也不能使用另一个键,因为您不能从旧的不变实体中删除它。您可能可以附加一个计数器并查找最大值,但这可能会显著减慢实体加载速度,因为链的增长。

解决方案是第二个可变实体,它跟踪不可变实体的链并更新其引用。在您的代码中管理这可能会有些费力和容易出错。通过在不可变实体中实现MutableReverenceKeyInterface,earc/data将自动完成可变实体(必须实现MutableEntityReferenceInterface)的更新。

对此概念有两个限制

  1. 当从不可变实体到可变实体的可变引用建立时,可变实体必须有一个主键。
  2. 只能有一个可变引用实体。如果您需要来自其他实体的更多对不可变的引用,它们必须使用可变引用实体作为代理。
use eArc\Data\Entity\Interfaces\MutableEntityReferenceInterface;
use eArc\Data\Entity\Interfaces\PrimaryKey\MutableReverenceKeyInterface;
use eArc\Data\Entity\Interfaces\ImmutableEntityInterface;
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Exceptions\DataException;

class MyClassReverencingAImmutable extends AbstractEntity implements MutableEntityReferenceInterface
{
    protected string|null $myImmutablePK;

    //...

    public function setMutableReverenceTarget(MutableReverenceKeyInterface $entity): void
    {
        $this->myImmutablePK = $entity->getPrimaryKey();
    }
    
    public function getMyImmutable(): MyImmutable|null
    {
         return is_null($this->myImmutablePK) ? null : data_load(MyImmutable::class, $this->myImmutablePK);
    }
    
    public function setMyImmutable(MyImmutable $immutable)
    {
        // If the current class does not have a primary key yet, we have to persist it first.
        // A post persist event does not reduce the effort and may lead into a infinity loop. 
        // You may use the solution for reverences between two immutables to circumvent this.
        if (is_null($this->primaryKey)) {
            throw new DataException('Primary key is missing. Try to persist this entity first.');
        }
        
        // The reverse key is set on persist.
        $immutable->setMutableReverenceKey($this->primaryKey);
    }
    
    //...
}

class MyImmutable extends AbstractEntity implements MutableReverenceKeyInterface, ImmutableEntityInterface
{
    protected string|null $mutableReferencePrimaryKey;

    //...
    
    public function getMutableReverenceKey(): string
    {
        return $this->mutableReferencePrimaryKey;
    }

    public function setMutableReverenceKey(string $mutableReferencePrimaryKey): void
    {
        $this->mutableReferencePrimaryKey = $mutableReferencePrimaryKey;
    }

    public function getMutableReverenceClass(): string
    {
        return MyClassReverencingAImmutable::class;
    }
    
    //...
}

class ReferenceUsingProxy extends AbstractEntity
{
    protected string $myImmutableProxyFQCN;
    protected string $myImmutableProxyPK;
    
    //...
    
    public function setMyImmutable(MyImmutable $immutable) 
    {
        $this->myImmutableProxyFQCN = $immutable->getMutableReverenceClass();
        $this->myImmutableProxyPK = $immutable->getMutableReverenceKey();
    }
    
    public function getMyImmutable(): MyImmutable
    {
        $proxy = data_load($this->myImmutableProxyFQCN, $this->myImmutableProxyPK)->getMyImmutable();
        
        return $proxy->getMyImmutable();
    }
    //...
}

要实现两个不可变实体链之间的可变引用,您可以使用GenericMutableEntityReference作为它们之间的链接。

use eArc\Data\Entity\Interfaces\PrimaryKey\MutableReverenceKeyInterface;
use eArc\Data\Entity\Interfaces\ImmutableEntityInterface;
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\GenericMutableEntityReference;

class MyImmutableClassReverencingAImmutable extends AbstractEntity
{
    protected string|null $myImmutableLinkPK;

    //...
    
    public function getMyImmutable(): MyImmutable|null
    {
         return is_null($this->myImmutableLinkPK) ? null : 
            data_load(GenericMutableEntityReference::class, $this->myImmutableLinkPK)->getMutableReverenceTarget();
    }
    
    public function setMyImmutable(MyImmutable $immutable)
    {
        $this->myImmutableLinkPK = $immutable->getMutableReverenceKey();
    }

        
    //...
}

class MyImmutable extends AbstractEntity implements MutableReverenceKeyInterface, ImmutableEntityInterface
{
    protected string|null $mutableReferencePrimaryKey;

    public function __construct(GenericMutableEntityReference|null $mutableReference)
    {
        if (is_null($mutableReference)) {
            $mutableReference = new GenericMutableEntityReference();            
        }
        
        if (is_null($mutableReference->getPrimaryKey())) {
            data_persist($mutableReference);        
        }
        
        $this->mutableReferencePrimaryKey = $mutableReference->getPrimaryKey();
    }
    
    //...
    
    public function getMutableReverenceKey(): string
    {
        return $this->mutableReferencePrimaryKey;
    }

    public function getMutableReverenceClass(): string
    {
        return GenericMutableEntityReference::class;
    }

    //...
}

编写自己的桥接器

如果没有可用于您的持久化基础设施的桥接器,或者您需要最大程度地控制持久化过程,您可以编写自己的桥接器。

要编写桥接器,您只需要实现数据持久/加载/删除/查找和主键生成事件接口。

在数据持久化时

OnPersistInterfaceonPersist()方法将带有一个要持久化的实体作为参数调用。持久化不得静默失败。

在数据加载时

OnLoadInterfaceonLoad()方法将带有一个完全限定的类名和实体的主键作为参数调用。在成功时必须返回实体对象。如果实体找不到,可调用对象不得抛出错误,而应简单地不返回。之后其他服务可以返回实体对象。

接口将作为第三个参数传递一个可调用数组。如果服务无法加载实体,它可以将回调添加到数组中。当所有实体加载完毕时,将调用这些可调用对象,并将实体作为参数传递。因此,缓存可以保存缺失的实体,而无需在每个后加载事件上监听。

在数据移除时

OnRemoveInterfaceonRemove()方法将带有一个完全限定的类名和实体的主键作为参数调用。删除实体不得静默失败,除非它尚未存在。

在数据查找时

OnFindInterfaceonFind() 方法将使用完全限定的类名和实体的键值对作为参数被调用。它必须返回一个主键数组。如果没有实体符合键值对,它可能为空。如果由于基础设施或数据缺失而无法处理键值对,它们必须返回 null,但如果只是配置或索引缺失,它们必须抛出 earc/data QueryExceptionInterface

必须始终支持返回实体类所有现有主键的空键值对数组。其他键值对可能不受支持或取决于配置或索引。

键值对必须通过逻辑 AND 联合。值数组必须解释为 IN

例如,callable(User::class, ['firstname' => 'Max', 'age' => [18, 19, 20, 21, 22, 23]]) 必须返回所有名为 Max 且年龄在 18 到 23 岁之间的用户。

桥梁可以扩展此语法以支持更广泛的搜索请求。

在主键生成时

OnAutoPrimaryKeyInterfaceonAutoPrimaryKey() 方法将使用需要新主键的实体作为参数被调用。它必须返回一个字符串,或者如果它不打算处理请求,则返回 null。

在保存实体之前会调用键生成。这可能与基础设施相矛盾,例如,如果实体持久化到生成键的 SQL 数据库,在这种情况下,方法必须返回空字符串。当然,on 数据持久化服务必须识别这一点。

版本

版本 0.0.1

  • 事务(beta - 仅在代码中提供文档,没有测试,接口可能更改)
  • CollectionBaseInterface 实现 EmbeddedEntityInterface
  • EmbeddedEntityInterface 添加了 setOwnerEntity() 方法
  • CollectionBaseInterface 中删除了 getOwner() 方法

版本 0.0

  • 第一个官方版本
  • PHP ^8.0 支持
  • PHPStorm 的 IDE 支持
    • data_loaddata_load_batchdata_find_entities 提供返回类型支持