jasny/db

此包已被废弃,不再维护。作者建议使用 jasny/persist 包。

通过扩展 PHP 扩展,添加现代面向对象数据库模式

v2.4.17 2020-07-28 10:49 UTC

README

Build Status Coverage Status

Jasny DB 将面向对象设计模式添加到 PHP 的数据库扩展中。

Jasny DB 是一个 PHP 的数据访问层 (不是 数据库抽象层)。它允许您正确地构建模型,同时仍然使用 PHP 原生数据库扩展的方法和功能。

安装

该库不是直接安装的。Jasny DB 库包含设计模式定义和实现。它作为为特定 PHP 扩展实现的实际库的抽象基类。

实现

服务定位器

静态 Jasny\DB 是一个 服务定位器。它提供对 工厂注册表构建器 的访问。

  • connectionFactory() - 数据库连接工厂
  • connectionRegistry() - 数据库连接注册表
  • entitySetFactory() - 实体集工厂

连接

连接对象用于与数据库交互。其他对象必须使用连接对象执行获取、保存和删除数据库中的数据等操作。

注册表

要注册连接,请使用 Jasny\DB::connectionFactory()->register($name, $connection)。要获取已注册的连接,请使用 Jasny\DB::connectionFactory()->get($name) 或快捷方式 Jasny\DB::conn($name)。可以通过 Jasny\DB::connectionFactory()->unregister($name|$connection) 从注册表中删除连接。

$db = new Jasny\DB\MySQL\Connection();
Jasny\DB::connectionFactory()->register('foo');

Jasny\DB::conn('foo')->query();

Jasny\DB::connectionFactory()->unregister('foo');

相同的连接可以在不同的名称下注册多次。

命名连接

实现 Namable 接口的连接可以使用 useAs($name) 方法向 Jasny\DB 注册自己。通过 getConnectionName() 可以获取连接的名称。

$db = new Jasny\DB\MySQL\Connection();
$db->useAs('foo');

Jasny\DB::conn('foo')->query();

如果只有一个数据库连接名称,默认为 'default',因为 $name 默认为 'default'。

$db = new Jasny\DB\MySQL\Connection();
$db->useAs('default');

Jasny\DB::conn()->query();

配置

您可以使用 Jasny\DB::configure() 来配置连接,而不是手动创建和配置。这个静态属性可以保存每个连接的配置。当使用 conn() 方法时,Jasny DB 会根据配置设置自动创建一个新的连接。

Jasny\DB::configure([
    'default' => [
        'driver'    => 'mysql',
        'database'  => 'database',
        'host'      => 'localhost',
        'username'  => 'root',
        'password'  => 'secure',
        'charset'   => 'utf8'
    ],
    'external' => [
        'driver'    => 'rest',
        'host'      => 'api.example.com',
        'username'  => 'user',
        'password'  => 'secure'
    ]
]);

Jasny\DB::conn()->query();
Jasny\DB::conn('external')->get("/something");

Jasny\DB::$drivers 包含了带有驱动名称的 Connection 类列表。createConnection($settings) 方法使用 driver 设置来选择连接类。其他设置会被传递给连接构造函数。

实体

实体是在数据库或其他数据存储中想要表示的“事物”。它可以是博客上的新文章、论坛中的用户或权限管理系统中的权限。

实体对象的属性是数据的表示。实体通常也携带业务逻辑。

设置值

setValues() 方法是一个设置所有属性从数组中的辅助函数,它的工作方式类似于 流畅接口

$foo = new Foo();
$foo->setValues(['red' => 10, 'green' => 20, 'blue' => 30])->doSomething();

实例化

使用 new 关键字是为创建新实体预留的。

当获取实体的数据时,使用 __set_state() 方法来创建实体。该方法在调用构造函数之前设置实体对象的属性。

活动记录

实体可以实现 活动记录模式。活动记录将数据和数据库访问合并到一个单独的对象中。

获取

可以使用 fetch($id) 从数据库中加载实体。

$foo = Foo::fetch(10); // id = 10
$foo = Foo::fetch(['reference' => 'myfoo']);

保存

实现 ActiveRecord 接口的对象有一个 save() 方法,用于将实体存储在数据库中。

$foo->save();
$foo->setValues($data)->save();

删除

可以使用 delete() 方法从数据库中删除实体。

$foo->delete();

可选地可以实施 软删除,这样被删除的实体可以恢复。

$foo->undelete();

数据映射器

您可以选择通过使用 数据映射器 将数据库逻辑与业务逻辑分离。数据映射器负责从数据库加载实体并将它们存储在数据库中。

您应该使用数据映射器或活动记录,但不能同时使用两者。当使用数据映射器时,实体不应了解数据库,并且不应包含任何数据库代码(例如 SQL 查询)。

获取

可以使用 fetch($id) 从数据库中加载实体。

$foo = FooMapper::fetch(10); // id = 10
$foo = FooMapper::fetch(['reference' => 'myfoo']);

保存

数据映射器实现 save($entity) 方法来存储实体。

FooMapper::save($foo);
FooMapper::save($foo->setValues($data));

删除

可以使用 delete($entity) 方法从数据库中删除实体。

FooMapper::delete($foo);

可选地可以实施 软删除,这样被删除的实体可以恢复。

FooMapper::undelete($foo);

数据集

实体往往是数据集中的一部分,如表格或集合。如果可以从该集中加载多个实体,则活动记录或数据映射器实现 Dataset 接口。

fetch() 方法返回单个实体。 fetchAll() 方法返回多个实体。fetchList() 以键/值对的形式加载列表,其中键是 ID 和描述。 count() 方法计算集合中实体的数量。

fetch 方法仅支持简单情况。对于特定情况,应添加特定方法,而不是覆盖基本 fetch 方法。

过滤器

fetch 方法接受一个 $filter 参数。过滤器是一个关联数组,包含字段名和相应的值。请注意,fetch() 方法要么接受一个唯一 ID,要么接受过滤器。

过滤器应始终返回与调用不带过滤器的方法相同或更少的结果。

$foo   = Foo::fetch(['reference' => 'zoo']);
$foos  = Foo::fetchAll(['bar' => 10]);
$list  = Foo::fetchList(['bar' => 10]);
$count = Foo::count(['bar' => 10]);

过滤器键可以包含指令。以下指令受到支持

描述
"field" 标量 字段是值
"field (not)" 标量 字段不是值
"field (min)" 标量 字段等于或大于值
"field (max)" 标量 字段等于或小于值
"field (any)" 数组 字段是数组中的其中一个值
"field (none)" 数组 字段不是数组中的任何一个值

如果字段是数组,可以使用以下指令

描述
"field" 标量 值是字段的一部分
"field (not)" 标量 值不是字段的一部分
"field (any)" 数组 任何一个值都是字段的一部分
"field (all)" 数组 所有值都是字段的一部分
"field (none)" 数组 没有值是字段的一部分

过滤器应与业务逻辑保持一致,这可能不会直接与检查字段值相一致。记录集应实现一个名为 filterToQuery 的方法,该方法将过滤器转换为依赖于数据库的查询语句。您可以覆盖此方法以支持自定义过滤器键。

直接使用查询参数($_GET)和输入数据($_POST)是安全的。

// -> GET /foos?color=red&date(min)=2014-09-01&tags(not)=abandoned&created.user=12345

$result = Foo::fetchAll($_GET);

实体集

每当返回实体数组时,Jasny DB 将返回一个 EntitySet 对象。实体集可以用作数组或对象。

需要更多文档

元数据

实体表示模型中的一个元素。元数据(Metadata)包含有关实体结构的详细信息。元数据应被视为静态的,因为它描述了特定类型的所有实体。

类的元数据可能包含应存储数据的数据表名称。属性的元数据可能包含数据类型、是否必需以及属性描述。

Jasny DB 支持通过使用 Jasny\Meta 通过注释定义元数据。

/**
 * User entity
 *
 * @entitySet UserSet
 */
class User
{
   /**
    * @var string
    * @required
    */
   public $name;
}

类注解

* @entitySet - Default entity set for this class of Entities

特定的Jasny DB驱动器可能使用额外的类注解。

属性注解

* @var - (type casting) - Value type or class name
* @type - (validation) - Value (sub)type
* @required (validation) - Value should not be blank at validation.
* @min (validation) - Minimal value
* @max (validation) - Maximal value
* @minLength (validation) - Minimal length of a string
* @maxLength (validation) - Maximal length of a string
* @options _values_ (validation) - Value should be one the the given options.
* @pattern _regex_ (validation) - Value should match the regex pattern.
* @immutable (validation) - Property can't be changed after it is created.
* @unique (validation) - Entity should be unique accross it's dataset.
* @unique _field_ (validation) - Entity should be unique for a group. The group is identified by _field_.
* @censor (redact) - Skip property when outputting the entity.

特定的Jasny DB驱动器可能使用额外的属性注解。

注意事项

元数据在泛化和抽象代码方面非常有用。然而,您可能会很快陷入通过元数据编写的陷阱。这往往会导致难以阅读和维护的代码。

仅使用元数据来抽象广泛使用的功能,并使用重载来实施特殊情况。

类型转换

实体支持类型转换。这是基于元数据进行的。类型转换由Jasny\Meta库实现。

内部类型

对于PHP内部类型,使用正常的类型转换。值不会被盲目地转换。例如,将“foo”转换为整数将触发警告并跳过转换。

对象

将值转换为支持懒加载Identifiable实体将创建一个幽灵对象。实现ActiveRecord或拥有DataMapper但不支持LazyLoading的实体将从数据库中获取。

将值转换为非可识别实体将调用Entity::fromData()方法。

将值转换为任何其他类型的对象将创建一个新的对象。例如,将“bar”转换为Foo将产生new Foo("bar")

验证

实现Validatable接口的实体在保存之前可以进行一些基本的验证。这包括检查所有必需的属性是否有值,检查变量类型是否匹配,以及检查值是否在数据库中唯一。

validate()方法将返回一个Jasny\ValidationResult

$validation = $entity->validate();

if ($validation->failed()) {
    http_response_code(400); // Bad Request
    json_encode($validation->getErrors());
    exit();
}

懒加载

Jasny DB支持通过允许它们作为幽灵创建来懒加载实体。幽灵仅持有实体数据的有限集合,通常仅是标识符。当访问其他属性时,它会加载其余的数据。

当一个值被转换为支持懒加载的实体时,会创建该实体的幽灵。

软删除

支持软删除的实体以可以恢复的方式进行删除。

可以使用undelete()恢复已删除的实体,或者可以使用purge()永久删除它们。

isDeleted()方法检查该文档是否已被删除。

获取方法不会返回已删除的实体。相反,使用fetchDeleted($filter)加载一个已删除的实体。使用fetchAllDeleted($filter)从数据库中获取所有已删除的实体。

可维护的代码

要创建可维护的代码,您应该至少遵守以下规则

  • 不要在模型类之外访问数据库。
  • 使用特性或多个类来分离数据库逻辑(例如查询)和业务逻辑。
  • 尽量减少if语句的数量。通过重载实现特殊情况。

SOLID原则

SOLID原则包含5个原则,当一起使用时,可以使代码库在长时间内更易于维护。虽然Jasny DB并不强制要求,但它支持构建符合SOLID原则的代码库。

方法保持小巧,并预期通过扩展类来实现每个方法的overloading

Jasny DB的功能通过接口定义,并在单个功能或设计模式周围使用特性定义。使用特定接口将触发行为。特性可能或不用于实现接口,而不会产生后果。

Active Record模式和SRP原则

使用Active Record模式被认为违反了SRP原则。Active Record通常将数据库逻辑和业务逻辑组合在一个类中。

然而,这种模式生成的代码更易于阅读和理解。因此,它仍然非常受欢迎。

最终,选择权在你。使用Active Record模式在Jasny DB中始终是可选的。或者,你也可以选择使用数据映射器进行数据库交互。

// Active Record
$user = User::fetch(10);
$user->setValues($data);
$user->save();

// Data Mapper
$user = DB::mapper('User')->fetch(10);
$user->setValues($data);
DB::mapper('User')->save($user);

代码生成

存在于版本1中,但尚未提供版本2