mixteer/persistor

一个用于快速开发的极简 ORM 库。

dev-master 2015-04-28 18:41 UTC

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 是围绕几个设计模式构建的,这样你就不必每次都需要它们。

  1. 元数据映射器 - 这是一个将对象的属性映射到相应的数据库表字段的类。Persistor 提供的元数据映射器接口需要更多一点,但 nothing out of the ordinary。

  2. 标识符映射 - 一个保存所有已从数据库中加载的对象以供将来快速访问的对象。这将对您保持透明,因此您通常不需要担心。但很多时候,我们希望对象在请求结束时被缓存而不是删除,随着库的成熟,API 将得到稳定,因此您可能需要针对提供的接口进行编码。

  3. 工作单元 - 一个协调对数据库更改的写入并管理并发问题的对象。在 Persistor 中,工作单元与依赖管理器耦合,因此您可以将对象依赖项声明为处理引用完整性。

  4. 懒加载 - 一个不包含您所需所有数据的对象,但知道如何获取它。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"
        );
    }
}

以下是元数据映射器实现的方法说明

  1. getClass - 此方法返回完整的命名空间类。内部用于确保始终将正确的类映射到正确的表。

  2. getTable() - 它返回映射到上述类的表名。

  3. getMapping() - 返回一个数组,将对象的属性映射到数据库字段。映射方式是 对象属性 => 表字段,而不是相反。注意,有一个例外:对于非标量值,主要是对象,需要转换为数据库等效值。在这种情况下,当在数据库中保存父代父亲时,我们更需要父代ID。因此,除了将 father 属性传递给持久化器外,我们还告诉持久化器如何以 propert:method 格式获取父代ID,其中 method 是调用以获取父代ID的方法。在这种情况下,父亲也是一个子代对象。如果没有父亲,默认为 null。

  4. getKeys() - 返回一个数组,其中包含所选表的字段,这些字段被认为是唯一的。如果你在表中有一个主键,你可以在这里将其标记为主键,但这不是必需的。在我们的例子中,我们有一个名为 user_id 的主键。注意,这些键由身份映射用于跟踪已加载的对象。每次通过已注册的键之一加载对象时,身份映射都会保存该对象以供未来通过任何其他键访问。

  5. getConstructMethod() - 返回一个表示“对象构造”期间使用的 静态工厂 方法的字符串。基本上,当要从数据库中提取新对象时,持久化器将调用此方法,并传递它作为数组接收的所有数据,该数组将注册的属性(作为键)映射到它们的表数据(作为值)。

  6. getIdSetter() - 返回用于设置此对象ID的方法。如果您正在处理没有ID的数据库中的值对象,请返回一个空字符串。

  7. getLoadersMapping() - 返回一个数组,将对象属性映射到其持久化器。这用于懒加载。附加到属性的值的形式为 class:[静态查找器]class 表示负责加载对象(如本例中的 Progeny 对象)的类,而可选的 静态查找器 是一个将被调用来从数据库中检索对象的静态方法。在我们的例子中,为了懒加载子代的父亲,我们将使用 ProgenyPersistor 类,并将父亲ID传递给其 findById 静态方法。持久化器将返回一个匿名函数,您可以使用它来获取父亲。请参阅 Progeny 类中的 father 方法,以了解如何实现。

对象持久化

持久化对象有两种方式:直接或间接使用工作单元。

  1. 直接 - 在这种情况下,您直接在 Persistor 对象上调用 insertupdatedelete 方法。
<?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)。
直接模式中执行的所有查询都在自动提交模式下执行。

  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,这是一个伟大的工具!如果您还没有尝试过,请试试看。

下一步

编写单元测试。欢迎贡献力量!