kijin / beaver

PHP 5.3+ 超轻量级 ORM

0.4.0 2014-04-27 06:24 UTC

This package is not auto-updated.

Last update: 2024-09-24 03:51:07 UTC


README

Beaver 是一个用于 PHP 5.3 及以上版本的非常简单的对象到数据库映射器。它允许您通过面向对象的接口进行 CRUD 操作,并提供基本的搜索和缓存功能。

Beaver 设计为与 PDO 一起使用。然而,由于 Beaver 使用依赖注入进行数据库交互,任何类似 PDO 的对象(例如 PDO 的自定义包装器)都可以替代它。Beaver 目前支持 MySQL、PostgreSQL 和 SQLite。其他关系型数据库尚未测试。

缓存也使用依赖注入。任何公开以下方法的类都与 Beaver 兼容

  • get($key, $value)
  • set($key, $value, $ttl)

MemcachedPHPRedis 与 Beaver 兼容。可以通过使用作者的 OOAPC 类使 APC 与 Beaver 兼容。旧的 Memcache 扩展不兼容,因为它需要在 set() 中添加额外的参数。

Beaver 采用 GNU Lesser General Public License, version 3 发布。

用户指南

配置

Beaver 不会为您管理数据库连接。因此,您首先连接到数据库,然后将 PDO 对象注入到 Beaver 中。

$pdo = new \PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);  // Recommended
Beaver\Base::set_database($pdo);

现在,对缓存连接也做同样的事情。这是可选的。Beaver 也可以在不使用缓存的情况下完美工作。

$cache = new \Memcached;
$cache->addServer('127.0.0.1', 11211);
Beaver\Base::set_cache($cache);

数据定义

Beaver 不会为您创建表,因此您需要自己创建它们。本指南假定您有一个如下定义的数据库表

CREATE TABLE users
(
    id           INTEGER      PRIMARY KEY AUTO_INCREMENT,
    name         VARCHAR(128) NOT NULL,
    email        VARCHAR(128) NOT NULL,
    password     VARCHAR(128) NOT NULL,
    age          INTEGER      NOT NULL,
    karma_points INTEGER      NOT NULL DEFAULT 0,
    group_id     INTEGER
);

现在您需要定义相应的 PHP 类。如果您更改数据库模式,则需要您自己更新此类,因为 Beaver 太轻量级,无法为您处理。

class User extends Beaver\Base
{
    protected static $_table = 'users';  // Required
    protected static $_pk = 'id';        // Optional
    public $id;
    public $name;
    public $email;
    public $password;
    public $age;
    public $karma_points = 0;
    public $group_id;
}

protected static $_table 是必需的,因为 Beaver 不自动进行词形变化。您可以将您的类指向任何表或视图。

protected static $_pk 是可选的。它默认为 id,因此除非您想使用除 id 之外的列作为主键,否则您无需重写它。

所有其他属性都应该反映数据库列。您可以指定默认值。

Beaver 将处理所有属性,无论是公共的还是受保护的,都将视为数据库列的名称。如果您定义了数据库中不存在的属性,这将导致错误。如果您需要定义其他属性,请使它们以下划线 (_) 开头。以下划线开头的属性将被 Beaver 忽略。

创建与常规类对应的集合类也是一个好主意。在 Beaver 0.2.9 及之前版本中,返回多个对象的函数,如 get_array()get_if_*()select(),将它们以数组的形式返回。在 Beaver 0.3.0 及之后版本中,它们将返回一个实现了 ArrayAccessIteratorCountable 接口集合类的实例。此类在大多数常见用例中表现得就像数组一样,例如在 foreach() 循环中。但是,您也可以编写方法来对对象集合执行各种批量操作。这比逐个对象执行相同的操作更可取。

要使用集合类,只需创建一个与常规类同名但后缀为Collection的类。这个类应该扩展Beaver\Collection。例如

class UserCollection extends Beaver\Collection
{
    public function get_average_age()
    {
        $sum = 0;
        foreach ($this->objects as $user)
        {
            $sum += $user->age;
        }
        return $sum / $this->count;
    }
}

如您所见,Beaver\Collection提供了一些方便编程的属性。这些属性的文档目前正在编写中。在此期间,请参考源代码以供参考。

如果没有定义自定义集合类,返回多个对象的方法将简单地返回一个Beaver\Collection实例。

日常使用

创建新对象(插入)

你觉得它会怎么工作呢?

$obj = new User;
$obj->name = 'John Doe';
$obj->email = 'john_doe@example.com';
$obj->password = hash($password.$salt);
$obj->age = 30;
$obj->save();

通常,在为新对象赋值时,你会跳过主键(id属性)。主键通常设置为AUTO_INCREMENT或等效值,在这种情况下,它们将由数据库服务器分配。但这也意味着主键(id属性)将保持为null,直到您第一次保存对象。因此,在对象保存之前,请谨慎不要引用对象的主键。

如果您的表具有不自动增长的唯一键,您可能需要手动设置它。在这种情况下,您可以将任何内容手动分配给id属性。Beaver将尽力尊重您的选择,而任何错误都将由数据库服务器捕获。

修改对象(更新)

有两种方法可以修改现有对象。这是第一种方法

$obj->name = 'John Williams';
$obj->email = 'new_email@example.com';
$obj->save();

这是第二种方法,在某些情况下可能会给您带来更好的性能

$obj->save(array('name' => 'John Williams', 'email' => 'new_email@example.com'));

使用第一种方法时,Beaver会再次保存每个属性,因为它无法检测哪些属性已更改。使用第二种方法时,只有您在数组中指定的属性才会更新。

删除对象(删除)

以下是删除对象的方法

$obj->delete();

请注意,已删除的对象可以通过再次调用save()来重新插入。

目前,Beaver无法同时删除多个对象。如果您需要同时删除多个对象,请编写自己的SQL查询——或者最好是查看您是否可以使用外键在不编写任何额外查询的情况下达到相同的效果。

查找对象(选择)

Beaver提供了几种不同的方法来检索先前保存的对象。

如果您知道您要查找的对象的主键,这是最简单的方法

$obj = User::get($id);
echo $obj->name;
echo $obj->email;

请注意,如果找不到主键,get()将返回null。如果您在它是null的情况下尝试获取属性,PHP将抛出一个“致命错误”。因此,在您的应用程序接近生产之前,您需要在某些地方添加一些检查。

如果您想一次获取多个对象,所有对象都通过主键获取

$objects = User::get_array($id1, $id2, $id3, $id4 /* ... */ );
foreach ($objects as $obj)
{
    echo $obj->name;
    echo $obj->email;
}

对象将按在参数中使用的相同主键的顺序返回。找不到的对象将作为null返回,这意味着返回数组可能在某些位置包含null

如果您有很多主键要查找,您也可以传递一个数组

$primary_keys = array($id1, $id2, $id3, $id4 /* ... */ );
$objects = User::get_array($primary_keys);

内置搜索

现在,这里开始变得有趣。

如果您想获取所有group_id为42的用户,您可以这样做

$objects = User::get_if_group_id(42);

只需调用get_if_<PROPERTY>(),Beaver将给您一个匹配查询的所有对象的数组。这适用于所有属性,除了那些以下划线(_)开头的属性(如上所述,以下划线开头的属性不映射到数据库列)。

您还可以按相同的列、另一个列或列的组合对结果进行排序

$objects = User::get_if_group_id(42, 'name+');
$objects = User::get_if_group_id(42, 'karma_points-');
$objects = User::get_if_group_id(42, 'name+', 20);
$objects = User::get_if_group_id(42, 'karma_points-,name+', 20, 40);

上述第一个例子返回组#42中的所有用户,按名称升序排序。第二个例子返回相同组中的所有用户,按 Karma 点数降序排序。第三个例子与第一个例子相同,但仅返回前 20 个结果。第四个例子先按 Karma 点数降序排序,然后按名称升序排序,跳过前 40 个结果,并返回接下来的 20 个。这正是您在分页中想要的。

如果省略了 +- 符号,则默认为升序。

Beaver 还支持基本的比较运算符进行搜索。例如,以下例子返回所有年龄为 30 或更大的用户。

$objects = User::get_if_age_gte(30, 'name+');

下一个例子返回年龄在 30 至 40 之间的前 50 个用户。

$objects = User::get_if_age_between(array(30, 40), 'id+', 50);

下一个例子返回以 "John" 开头的用户列表的第二页(每页 20 个,按年龄排序)。

$objects = User::get_if_name_startswith('John', 'age+', 20, 20);

总共,Beaver 支持搜索 12 个运算符。

  • _gte 匹配大于或等于参数的值。
  • _lte 匹配小于或等于参数的值。
  • _gt 匹配大于但不等于参数的值。
  • _lt 匹配小于但不等于参数的值。
  • _between 匹配介于两个值之间的值,包括端点。参数必须是一个数组。
  • _xbetween 匹配介于两个值之间的值,不包括端点。参数必须是一个数组。
  • _not 匹配除参数之外的所有值。参数必须是一个数组。
  • _in 匹配在参数中列出的任何值。参数必须是一个数组。
  • _notin 匹配不在参数中列出的任何值。参数必须是一个数组。
  • _startswith 匹配以参数开头的所有字符串。
  • _endswith 匹配以参数结尾的所有字符串。
  • _contains 匹配包含参数的所有字符串。

注意,只有当完整方法名不匹配属性名时,才会评估运算符。因此,如果您有两个名为 ageage_lt 的属性,所有对 get_if_age_lt() 的调用都将解释为对 age_lt 的精确匹配,而不是对 age 的小于匹配。如果您发现自己处于这种罕见的情况,并且需要对 age 进行小于匹配,请调用 get_if_age__lt()。 (注意额外的下划线有助于区分您的查询。)

如果您用于搜索和排序的列没有索引,搜索可能会很慢。此外,_endswith_contains 总是会很慢,因此如果关心性能,请避免使用这些运算符。

字符串比较的大小写敏感性取决于数据库类型和其他设置。

自定义搜索

Beaver 不能做连接、子查询或上面例子中没有涵盖的其他操作。但是,即使 Beaver 不能为您编写这些查询,它也可以以方便和安全的方式帮助您轻松地进行各种 SELECT 查询。

$objects = User::select('WHERE group_id IN (SELECT id FROM groups WHERE name = ?)', $param);

第二个参数应该是一个参数数组,对应于查询字符串中的占位符。单独传递参数是一种非常好的防止 SQL 注入漏洞的方法。如果没有参数要传递,可以省略第二个参数。

如上所述的查询可以封装在 User 类的方法中。Beaver 不关心您是否向类中添加了自己的方法,只要您不干扰 Beaver 的核心功能即可。例如,您可以实现自己的 save() 方法,在调用 parent::save() 之前进行一些检查。

限制

上述语法仅在查询返回单个行时有效,包括主键。对于不返回单个行的查询(例如带有GROUP BY子句的查询),以及所有非SELECT查询,您需要直接与数据库连接(PDO对象)进行交互。

唯一的例外情况是您只想检查是否存在符合您条件的行。这是一种常见的查询类型,因此Beaver 0.4.0及以上版本支持exists()方法。参数的类型和顺序与select()方法完全相同。

$exists = User::exists('WHERE age > 40');

如果存在任何年龄大于40岁的用户,上述代码将返回true,否则返回false。不会从数据库中检索其他信息,并且如果查询中已经包含限制子句,则exists()将自动添加LIMIT 1。因此,它比使用get()select()来获取记录并计数要快得多。

缓存

如果您想让Beaver缓存查询结果一段时间,只需在get()get_array()get_if_<PROPERTY>select()中添加另一个参数即可。

$obj = User::get($id, 300);  // Cache for 300 seconds = 5 minutes.

请注意,当使用缓存参数调用get_array()时,您需要使用数组,因为否则Beaver无法区分缓存参数和其他所有参数。

$primary_keys = array($id1, $id2, $id3, $id4 /* ... */ );
$objects = User::get_array($primary_keys, 300);

同样,在调用带有缓存参数的get_if_<PROPERTY>()select()exists()时,请确保不要省略可选参数,例如排序、限制/偏移量或参数。您可以使用null来省略限制/偏移量参数,使用array()来表示空参数列表。

$objects = User::get_if_age_lte(30, 'name+', null, null, 300);  // OK
$objects = User::get_if_age_lte(30, 300);                       // WRONG

$objects = User::select('WHERE group_id IS NULL', array(), 300);  // OK
$objects = User::select('WHERE group_id IS NULL', 300);           // WRONG