mixteer / persistor
一个用于快速开发的极简 ORM 库。
Requires
- php: >=5.3.0
- doctrine/dbal: 2.5.*@dev
This package is not auto-updated.
Last update: 2024-09-28 17:20:16 UTC
README
此库尚未准备好生产使用,因为它尚未经过测试。请勿在生产环境中使用它,但请尽情尝试!这是一个概念验证。
Persistor 是一个极简的 ORM,它帮助进行对象持久化,而不会妨碍你的工作。基本思想是,你可以在它不能满足你的要求时覆盖其任何行为,至少这是最终目标。
目前,它处于 0.1.0 版本,因此尚未准备好生产使用,但如果您认为这个想法值得,请提交拉取请求以进行贡献,如果您正在尝试使用,请提出问题。
安装
此库可在 packagist 上找到,并通过 composer 安装。
{ "require": { "mixteer/persistor": "dev-master" } }
概念
Persistor 是围绕几个设计模式构建的,这样你就不必每次都需要它们。
-
元数据映射器 - 这是一个将对象的属性映射到相应的数据库表字段的类。Persistor 提供的元数据映射器接口需要更多一点,但 nothing out of the ordinary。
-
标识符映射 - 一个保存所有已从数据库中加载的对象以供将来快速访问的对象。这将对您保持透明,因此您通常不需要担心。但很多时候,我们希望对象在请求结束时被缓存而不是删除,随着库的成熟,API 将得到稳定,因此您可能需要针对提供的接口进行编码。
-
工作单元 - 一个协调对数据库更改的写入并管理并发问题的对象。在 Persistor 中,工作单元与依赖管理器耦合,因此您可以将对象依赖项声明为处理引用完整性。
-
懒加载 - 一个不包含您所需所有数据的对象,但知道如何获取它。Persistor 使用的懒加载器相当简单。您指定在请求对象时如何加载对象,然后您将获得一个匿名函数,该函数将在数据请求时执行。
这些都是使 Persistor 工作的主要设计模式。为了使开发者完全控制,一切都保持简单。
使用方法
要开始使用 persistor,你需要一个元数据映射器类,它告诉 Persistor 关于你的对象的信息。让我们假设你有一个 Progeny
类,你希望持久化其对象。
<?php namespace Test; class Progeny { protected $userId = 0; protected $name; protected $age; protected $father = null; public function __construct($name, $age) { $this->name = $name; $this->age = $age; } public static function build($data) { $user = new self($data["name"], $data["age"]); $user->setId($data["userId"]); $user->father($data["father"]); return $user; } public function setId($id) { $this->userId = $id; } public function getId() { return $this->userId; } public function changeName($name) { $this->name = $name; } public function getName() { return $this->name; } public function getAge() { return $this->age; } public function father($father = null) { if ($father !== null) { $this->father = $father; } if (is_callable($this->father)) { $that = $this->father; return $that(); } return $this->father; } }
现在,我们想要持久化这个类。要开始,我们声明一个元数据映射器。这是一个实现 Persistor\Interfaces\MetadataMapperInterface
的类,该接口由 7 个方法组成,完全告知如何持久化和加载对象。
这里是一个入门级元数据映射器
<?php use Persistor\Interfaces\MetadataMapperInterface; class ProgenyMapper implements MetadataMapperInterface { public function getClass() { return "Test\Progeny"; } public function getTable() { return "users"; } public function getMapping() { return array( "userId" => "user_id", "name" => "name", "age" => "age", "father:getId" => "father_id" ); } public function getKeys() { return array("primary" => "user_id"); } public function getConstructMethod() { return "build"; } public function getIdSetter() { return "setId"; } public function getLoadersMapping() { return array( "father" => "Test\ProgenyPersistor:findById" ); } }
以下是元数据映射器实现的方法说明
-
getClass - 此方法返回完整的命名空间类。内部用于确保始终将正确的类映射到正确的表。
-
getTable() - 它返回映射到上述类的表名。
-
getMapping() - 返回一个数组,将对象的属性映射到数据库字段。映射方式是
对象属性 => 表字段
,而不是相反。注意,有一个例外:对于非标量值,主要是对象,需要转换为数据库等效值。在这种情况下,当在数据库中保存父代父亲时,我们更需要父代ID。因此,除了将father
属性传递给持久化器外,我们还告诉持久化器如何以propert:method
格式获取父代ID,其中method
是调用以获取父代ID的方法。在这种情况下,父亲也是一个子代对象。如果没有父亲,默认为 null。 -
getKeys() - 返回一个数组,其中包含所选表的字段,这些字段被认为是唯一的。如果你在表中有一个主键,你可以在这里将其标记为主键,但这不是必需的。在我们的例子中,我们有一个名为
user_id
的主键。注意,这些键由身份映射用于跟踪已加载的对象。每次通过已注册的键之一加载对象时,身份映射都会保存该对象以供未来通过任何其他键访问。 -
getConstructMethod() - 返回一个表示“对象构造”期间使用的 静态工厂 方法的字符串。基本上,当要从数据库中提取新对象时,持久化器将调用此方法,并传递它作为数组接收的所有数据,该数组将注册的属性(作为键)映射到它们的表数据(作为值)。
-
getIdSetter() - 返回用于设置此对象ID的方法。如果您正在处理没有ID的数据库中的值对象,请返回一个空字符串。
-
getLoadersMapping() - 返回一个数组,将对象属性映射到其持久化器。这用于懒加载。附加到属性的值的形式为
class:[静态查找器]
。class
表示负责加载对象(如本例中的Progeny
对象)的类,而可选的静态查找器
是一个将被调用来从数据库中检索对象的静态方法。在我们的例子中,为了懒加载子代的父亲,我们将使用ProgenyPersistor
类,并将父亲ID传递给其findById
静态方法。持久化器将返回一个匿名函数,您可以使用它来获取父亲。请参阅Progeny
类中的father
方法,以了解如何实现。
对象持久化
持久化对象有两种方式:直接或间接使用工作单元。
- 直接 - 在这种情况下,您直接在
Persistor
对象上调用insert
、update
和delete
方法。
<?php namespace Test; use Persistor\Persistor; use Test\ProgenyMapper; class ProgenyPersistor { protected $persistor = null; protected static $_persistor = null; public function __construct() { $credentials = array( 'driver' => 'pdo_mysql', 'host' => "localhost", 'dbname' => "test", 'user' => "root", 'password' => "" ); $this->persistor = Persistor::factory(new ProgenyMapper, $credentials); self::$_persistor = $this->persistor; } public function persist($progeny) { $progeny = $this->persistor->insert($progeny); } public function update($progeny) { $rowCount = $this->persistor->delete($progeny); } public function delete($progeny) { $rowCount = $this->persistor->delete($progeny); } public static function findById($id) { $progeny = self::$_persistor->getBy("user_id", $id); return $progeny; } }
基本上,您只需在持久化器对象上调用相应的插入、更新和删除方法。插入方法将返回带有设置ID的新对象(如果您提供了主键),如果您将第二个参数传递给 insert
方法并设置为 true
,它将返回最后一个插入ID而不是对象。更新和删除方法将返回受影响行的行数(在失败的情况下为 0,在成功的情况下为 1)。
直接模式中执行的所有查询都在自动提交模式下执行。
- 工作单元 - 在这种情况下,您注册要持久化的对象,然后提交它们。如果失败,事务将自动回滚,并引发包含详细信息的异常。工作单元执行的所有查询都在事务内执行。在完成每个事务后,提交模式将重置为自动提交。
以下是使用工作单元执行相同代码的示例。
<?php namespace Test; use Persistor\Persistor; use Test\ProgenyMapper; class ProgenyPersistor { protected $persistor = null; protected static $_persistor = null; public function __construct() { $credentials = array( 'driver' => 'pdo_mysql', 'host' => "localhost", 'dbname' => "test", 'user' => "root", 'password' => "" ); $this->persistor = Persistor::factory(new ProgenyMapper, $credentials); self::$_persistor = $this->persistor; } public function persist($progeny) { $progenyDependency = $this->persistor->registerNew($progeny); } public function update($progeny) { $progenyDependency = $this->persistor->registerDirty($progeny); } public function delete($progeny) { $progenyDependency = $this->persistor->registerDelete($progeny); } public function flush() { $unitOfWork = $this->persistor->unitOfWork(); $unitOfWork->commit(); } public static function findById($id) { $progeny = self::$_persistor->getBy("user_id", $id); return $progeny; } }
您可以使用注册方法 registerNew
来插入对象,registerDirty
来更新对象,以及 registerDelete
来删除对象,以实现对对象的持久化注册。通常情况下,当对象的属性发生变化时,会从对象自身调用 registerDirty
方法,但您也可以在检测到应用服务中的领域事件时调用它,以避免将基础设施代码与领域对象混淆。
所有注册方法都将返回一个依赖对象,您可以使用它来声明依赖。这可以通过以下方式进行:
<?php public function persist($progeny) { $progenyDependency = $this->persistor->registerNew($progeny); // You can declare a depency by passing the actual object or it's dependency object $progenyDependency->dependsOn($object); // Or $progenyDependency->dependsOn($objectDependency); }
一个前提条件是,所有声明的依赖项必须首先进行注册。这意味着在其它对象可以依赖它之前,$object
必须作为新对象、脏对象或删除对象进行注册。
此外,一个对象不能以两种不同的方式注册:例如,既作为新对象又作为脏对象。它只能是新对象、脏对象或删除对象(或为排他性。)
当涉及到提交对象时,您有三个选择:您可以提交单个对象(这也会提交其依赖项),或者可以提交属于特定持久化程序的所有对象(及其所有依赖项),或者可以提交注册到工作单元的所有对象。
以下示例说明了这一点:
<?php public function flush() { $unitOfWork = $this->persistor->unitOfWork(); // Option 1: commit a single object $this->persistor->commit($progeny); // Using the persistor $unitOfWork->commit(UnitOfWork::CoMMIT_OBJECT, $progeny); // Directly using the unit of work // Option 2: commit this persistor objects $this->persistor->commit(); // Directly using the persistor $this->persistor->commit(UnitOfWork::COMMIT_PERSISTOR, $this); // Directly using the unit of work // Option 3: commit all the objects registered to the unit of work $unitOfWork->commit(); }
目前,工作单元返回一个空数组,这并不实用,但在下一个版本中,它将返回一个数组,该数组包含与每个对象对应的结果。
获取对象
获取对象相当简单。提供了一些辅助方法以简化操作。目前提供了两个方法:getBy($key, $value)
和 getLike($field, $value)
。第一个方法返回一个表键与给定值匹配的对象,第二个方法返回一个数组,其中包含表字段类似于提供值的对象。
以下是一个示例:
<?php public function getProgenyById($id) { $progeny = $this->persistor->getBy("user_id", $id); return $progeny; } public function getProgenysLike($name) { $progeniess = $this->persistor->getLike("name", $name); return $progeniess; }
将来还会添加更多方法。
运行自己的查询
您还可以使用查询执行器运行自己的查询。对于插入操作,它将返回最后一个插入 ID;对于更新和删除操作,它将返回行数;对于读取操作,它将返回一个表示单行的数组和一个表示多行的数组数组(取决于您想要的方式。)
以下是一个示例:
<?php $query = "SELECT name, age FROM users WHERE user_id = :user_d"; $parameters = array( ":user_id" => 1 ); $queryRunner = $this->persistor->queryRunner(); // Read $queryRunner->read($parameters, QueryRunner::FETCH)->using($query)->run(); // If you do have parameters $queryRunner->read(null, QueryRunner::FETCH)->using($query)->run(); // If you do not have parameters // Insert, update and delete follow the same pattern as well. But the parameter is mandatory $queryRunner->insert($paramters)->using($insertQuery)->run();
注意 1) 使用 QueryRunner::FETCH_ALL
获取多个对象。2) QueryRUnner::FETCH
不会 关闭游标,因此如果您希望循环并获取更多数据,可以这样做。3) 获取模式始终为 PDO::FETCH_ASSOC
。
关于
Persistor 是在快速原型化领域对象的需求下诞生的,无需每次都编写相同的 PDO 模板代码,也无需使用具有学习曲线和怪癖的完整 ORM。本质上,从 Persistor,您可以选择将其缩减并使用裸 PDO 代码进行代码重用,或者选择使用 ORM。
作者
Ntwali Bashige - ntwali.bashige@gmail.com - http://twitter.com/nbashige
许可证
Reshi 在 MIT
许可证下发布,请参阅 LICENSE 文件。
致谢
内部,Persistor 使用了 Doctrine DBAL,这是一个伟大的工具!如果您还没有尝试过,请试试看。
下一步
编写单元测试。欢迎贡献力量!