troelskn/pdoext

此包最新版本(dev-master)没有可用的许可证信息。

基于PDO的简单而强大的PHP对象关系映射(ORM)。

dev-master 2015-01-22 22:34 UTC

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";
}

作用域应始终以 wherewith 开头;约定是 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";
    }
  }
}

如果记录中存在任何错误,则 insertupdate 操作将不会继续进行。如果你只想为 insertupdate 之一运行验证,请使用专门的版本

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 位的哈希值,这是进程唯一的。这允许你在存在并发请求处理时跟踪日志行。