troelskn / pdoext
基于PDO的简单而强大的PHP对象关系映射(ORM)。
This package is not auto-updated.
Last update: 2024-09-14 14:52:08 UTC
README
pdoext 是一个针对PHP的数据库抽象层。其主要特性是 零配置 和一个 优雅的API。
pdoext 扩展了 pdo 并增加了缺失的功能,如 日志记录 和 元数据;方便的功能,如 字段引用 和 事务确认;以及提供了一些兼容性问题的解决方案(主要与 sqlite 相关)。
特别值得一提的是,它提供了一个 表网关,该网关将大部分基本SQL抽象成一个直观易读的接口。下面是示例。
它不是一个完整的ORM;您仍然需要理解底层数据库才能高效使用它。这有助于 降低复杂性,使pdoext相对简单易懂且易于扩展。特别是,pdoext不管理 对象标识 或 继承。它也不完全将您的应用程序代码与数据库的 关系范式 隔离。
连接
由于pdoext扩展了PDO,连接对象遵循相同的接口。您可以查看 pdo构造函数 的文档,以了解它需要哪些参数。以下是一个连接本地MySQL数据库的简单示例
$db = new pdoext_Connection("mysql:dbname=testdb;host=127.0.0.1", "root", "secret");
pdo和pdoext之间松散关系的一个好处是,pdoext可以在任何期望pdo的地方使用。
表网关
表网关为您提供对简单 CRUD操作 和查询行的访问。它以 活动记录 风格的包装返回行,该包装可以在用户代码中扩展。以下是您在应用程序中使用表网关的典型方式
foreach ($db->articles->whereStatusIs('published') as $article) {
print $article->title . " - " . $article->author()->name . "\n";
}
使用此功能不需要配置和用户代码。 pdoext 将使用元数据来确定您的表是如何通过外键约束连接在一起的。
条件
从网关中选择时,您可以使用各种条件。假设有一个列 name,以下条件是内置的
您可以添加自己的条件(有关详细信息,请参阅 作用域 部分)。
分页
表网关内置了对 分页 的支持
$selection = $db->users->whereNameLike('jim%')->paginate($page_number);
echo "Viewing page " . $selection->currentPage() . " of " . $selection->totalPages() . "\n";
foreach ($selection as $user) {
echo "id: " . $user->id . ", name: " . $user->name . "\n";
}
获取单条记录
如果您期望只有一个记录,则可以在选择前添加 ->one()
来获取第一个结果。如果有多个行,它将抛出异常。例如。
$jim = $db->users->whereNameIs('jim')->one();
条件链
您可以应用任意多的条件到一个查询中
$selection = $db->users->whereNameLike('jim%');
$selection->whereAgeGreaterThan(27);
foreach ($selection as $user) {
echo "id: " . $user->id . ", name: " . $user->name . "\n";
}
自定义
您可以创建自己的表网关来扩展功能。例如
class ArticlesGateway extends pdoext_TableGateway {
}
Pdoext知道根据约定使用 ArticlesGateway
。如果一个类存在,并且遵循 tablename+"gateway" 的模式,它将使用这个类而不是通用的 pdoext_TableGateway
。
最常见的用法是创建 作用域(请参阅下一节)和进行 验证。
同样,如果您创建了一个以表名称单数形式命名的类,pdoext 将使用此类而不是通用的 pdoext_DatabaseRecord
。
class Article extends pdoext_DatabaseRecord {
}
记录
您可以在您的记录上创建自定义访问器(获取器/设置器)。如果存在一个名为 "get" 加上 列名 的方法,则将调用此方法而不是更新内部数组。例如
class Article extends pdoext_DatabaseRecord {
function getAge() {
return time() - $this->createdAt;
}
function setTitle($title) {
$this->_data['title'] = $title;
$this->_data['slug'] = preg_replace('[^a-z]', '-', strtolower($title));
}
}
外键
如果表定义了任何外键,您可以在记录上访问它们。例如
$article = $db->articles->whereTitleIs("Lorem Ipsum")->one();
$author = $article->author();
这也适用
$articles = $author->articles();
请注意,默认情况下,不会尝试管理行的标识性。每次调用这些方法时,都会针对数据库执行新的查询。换句话说
$authorOne = $article->author();
$authorTwo = $article->author();
assert($authorOne !== $authorTwo); // yields true
在最近版本中,pdoext 包含一个可选的对象缓存(见下文)。启用缓存后,语义会发生变化,数据库只会被查询一次
$db->enableCache();
$authorOne = $article->author();
$authorTwo = $article->author();
assert($authorOne === $authorTwo); // yields true
除了语义外,使用对象缓存/标识映射还有性能影响,并且哪种更好并不明显,因为它是一种权衡。
外键的另一个限制是您不能直接分配一个对象
// NOTE: Won't work!
$article->author = $db->authors->whereNameIs("Jim")->one();
请理解,这是设计上的,因为它让我们免受与 对象关系阻抗不匹配 相关的复杂性困扰。如果您想使用这种功能,请使用完整的 ORM,例如 Doctrine。
缓存
pdoext 具有一个可选的对象缓存/标识映射,它对主键进行缓存。默认情况下不启用缓存,因为它是在内存使用量和数据库查询次数之间的权衡。然而,如果您有很多对主键的查找,启用缓存可能会提高您的性能。要启用缓存,请在连接对象上调用 enableCache
。缓存位于表网关上,您也可以通过在表网关上调用 purgeCache
来清除它。例如
$db->enableCache(); // Enable object caching for all gateways
$db->authors->purgeCache(); // Clear cache for the authors gateway
当您在主键上 fetch
或使用 load
加载记录时,将使用缓存。请注意,这意味着记录具有引用语义,而不是pdoext默认的值语义。
命名
假设所有数据库列都遵循 小写下划线 的一致性。记录将自动在PHP风格的 camelCase 和数据库命名风格之间进行转换。因此,您可以从PHP代码中访问camelCase版本的列。两种方式都适用。例如
// Recommended style
echo $article->createdAt;
echo $article['created_at'];
// Will also work
echo $article->created_at;
echo $article['createdAt'];
对于访问器,您 必须 使用 camelCase 编写方法。
定制网关
作用域
为了帮助保持代码优雅和可读,您可以创建自定义的 作用域。例如
class ArticlesGateway extends pdoext_TableGateway {
function scopeWherePublished($selection) {
$selection->where('status', 'published');
}
}
现在您可以使用作用域像这样
foreach ($db->articles->wherePublished()->limit(10) as $article) {
print $article->title . " - " . $article->author()->name . "\n";
}
作用域应始终以 where 或 with 开头;约定是 where 添加条件,而 with 与其他表连接。以下是一个连接侧表的示例
class ArticlesGateway extends pdoext_TableGateway {
function scopeWherePublished($selection) {
$selection->where('status', 'published');
}
function scopeWithAuthor($selection) {
$selection->addColumn('articles.*');
$selection->addColumn('authors.name', 'author_name');
$join = $selection->addJoin('authors', 'LEFT JOIN');
$join->addConstraint('authors.id', 'articles.author_id');
}
}
现在我们可以这样使用
foreach ($db->articles->withAuthor()->wherePublished()->limit(10) as $article) {
print $article->title . " - " . $article->authorName . "\n";
}
幕后,这只会执行一个 SQL 查询,在文章上左连接作者。否则,我们会在每次迭代时对作者发出新的查询。
复杂查询
对于复杂查询,您可以使用面向对象的查询 API,或者如果您更喜欢手动编写 SQL,则可以使用参数化查询。如果您只想添加到 "where" 部分,请使用此格式
$db->articles->where('status = ?', 'published');
或者如果您想自己编写整个 SQL
$db->articles->query("SELECT * FROM articles");
带有参数
$db->articles->pexecute("SELECT * FROM articles WHERE status = :status", array(':status' => 'published'));
查询 API
pdoext 具有一个面向对象的查询构建 API,可以用于构建复杂查询。这样做的好处是,与手动编写 SQL 相比,它比较容易逐步构建查询。这可以用于根据用户输入(如搜索)创建查询,并且在作用域内也很有用。该界面在很大程度上受到了 Hibernate 的启发。
最常用的用法是添加一个标准(条件)。这以以下方式完成
$selection->addCriterion('name', 'Jim'); // WHERE name = 'Jim'
addCriterion
接收第三个参数,它是比较运算符。因此您可以这样做
$selection->addCriterion('name', 'Jim', '!='); // WHERE name != 'Jim'
如果您想比较两个字段而不是字段到值,请使用 addConstraint
代替
$selection->addCriterion('name', 'first_name'); // WHERE name = first_name
这主要用于进行连接操作。你可以这样连接一个表
$selection->addJoin('other_table'); // JOIN other_table
addJoin
返回一个连接对象,你可以添加条件到这个对象。使用上面的 addConstraint
,这是一个典型的连接
$join = $selection->addJoin('authors', 'LEFT JOIN'); // LEFT JOIN authors
$join->addConstraint('authors.id', 'articles.author_id'); // ON authors.id = articles.author_id
还有更多选项可用 - 看看测试和源代码。
CRUD
表网关提供了在单行上执行 插入、更新 和 删除 的功能。你可以将这些函数的参数传递为关联数组或记录。
要 插入
$article_id = $db->articles->insert(array('name' => "Jim"));
在 更新 时,使用第一个参数的 主键 值
// Rename to John where id = 42
$db->articles->update(array('id' => 42, 'name' => "John"));
你可以选择传递第二个参数,其中包含更新的条件
// Rename all Jim's to John
$db->articles->update(array('name' => "John"), array('name' => "Jim"));
为了完整性,以下是删除行的方法
// Delete record with id = 42
$db->articles->delete(array('id' => 42));
验证
验证是回调函数,用于在执行 CRUD 操作之前检查记录的有效性。要使用验证,请在自定义表网关上实现它们
class ArticlesGateway extends pdoext_TableGateway {
protected function validate($data) {
if (!preg_match('~^[A-Z][a-zA-Z]+$~', $data->name)) {
$data->_errors[] = "You must enter a valid name";
}
}
}
如果记录中存在任何错误,则 insert
和 update
操作将不会继续进行。如果你只想为 insert 或 update 之一运行验证,请使用专门的版本
class ArticlesGateway extends pdoext_TableGateway {
// Will not run on UPDATE
protected function validateInsert($data) {
if (!preg_match('~^[A-Z][a-zA-Z]+$~', $data->name)) {
$data->_errors[] = "You must enter a valid name";
}
}
}
日志记录
连接类支持将所有 SQL 记录到一个文件中。这主要用于开发期间,用于调试和性能调整。要启用日志记录,只需在连接对象上调用 setLogging
$db->setLogging('/var/log/pdoext_queries.log');
如果你从 cli 运行 php,你可能希望将输出输出到那里。只需调用 setLogging
而不带任何参数,它将写入 stdout。
你可以选择指定 log_time
。只有查询速度比这个值慢的才会被记录。这可以用来识别代码中的性能瓶颈。例如。
$db->setLogging('/var/log/pdoext_slow.log', 0.5);
在这种情况下,只有查询时间超过半秒的才会被记录。
日志将显示查询是从哪里开始的。这是通过检查调用堆栈并找到第一个不属于 pdoext 的类来完成的。这通常会使确定调用来源更容易。每条日志行也包含一个 6 位的哈希值,这是进程唯一的。这允许你在存在并发请求处理时跟踪日志行。