Drupal专业开发指南 第5章 Drupal 数据库层(2)

使用hook_db_rewrite_sql()将查询暴露给其它模块

     译者:老葛 Eskalate科技公司

 

这个钩子用来修改Drupal中任何地方的查询,这样你就不用直接修改相关模块了。如果你将一个查询传递给db_query(),而且你相信其他人可能想修改它,那么你就需要把它包装到db_rewrite_sql()里面,这样其他的开发者就可以访问它了。当执行一个这样的查询时,它首先检查所有实现了db_rewrite_sql钩子的模块,给它们一个修改查询的机会。例如,节点模块修改了节点列表查询,从而将受到节点访问规则保护的节点排除在外。

 

 

警告 如果你执行一个节点列表查询(例如,你直接对node表查询来获取所有节点的一个子集),但是你没有使用db_rewrite_sql()来包装你的查询,那么节点访问规则将被忽略,这是由于节点模块没有机会修改你的查询来排除受保护的节点。

 

如果发出查询的人,但是你想在你的模块中有机会修改他人的查询,那么需要在你的模块中实现这个钩子。

 

 

5-2 使用db_rewrite_sql()的两种方式的总结

 

5-2.什么时候使用db_rewrite_sql()函数VS使用db_rewrite_sql()钩子

名称                           什么时候使用

db_rewrite_sql()                     当编写节点列表查询或者其他查询时,你想让别人能够修改它的时候

hook_db_rewrite_sql()       当你修改其它模块中的查询时

 

包装查询

 

下面是函数签名:

function hook_db_rewrite_sql($query, $primary_table = 'n', $primary_field = 'nid',

$args = array())

 

参数如下:

$query:可被覆写的SQL查询。

$primary_table: 在该查询中,包含主键字段的表的别名。例如,其值可位n或者 c (例如., 对于 SELECT nid FROM {node} n, 该值应为 n)。

$primary_field:在该查询中主键字段的名称。它的值可为nid, tid,vid, cid,等等。(例如,如果你的查询要得到一列节点ID,那么主字段应该为nid)。

$args:传递给hook_db_rewrite_sql()实现的一个包含参数的数组。

 

修改其它模块的查询

 

让我们看一个该钩子的一个实现。下面的例子利用了节点表中moderate(适度的)列来覆写节点查询。在我们修改了查询以后,处于moderated状态的节点(例如,moderate列为1),对于不具有“administer content”权限的用户将被隐藏起来。

 

/**

* Implementation of hook_db_rewrite_sql().

*/

function moderate_db_rewrite_sql($query, $primary_table, $primary_field, $args) {

switch ($primary_field) {

case 'nid':

// Run only if the user does not already have full access.

if (!user_access('administer content')) {

$array = array();

if ($primary_table == 'n') {

// Node table is already present;

// just add a WHERE to hide moderated nodes.

$array['where'] = "(n.moderate = 0)";

}

// Test if node table is present but alias is not 'n'.

elseif (preg_match('@{node} ([A-Za-z_]+)@', $query, $match)) {

$node_table_alias = $match[1];

// Add a JOIN so that the moderate column will be available.

$array['join'] = "LEFT JOIN {node} n ON $node_table_alias.nid = n.nid";

// Add a WHERE to hide moderated nodes.

$array['where'] = "($node_table_alias.moderate = 0)";

}

return $array;

}

}

}

 

注意我们检查任何主键为nid的查询,并向这些查询中插入一些额外信息。让我们看一下实际效果。

 

下面是最初的查询,在moderate_db_rewrite_sql()处理以前的:

SELECT * FROM {node} n WHERE n.type = 'blog' and n.status = 1

 

下面是moderate_db_rewrite_sql()处理过后的查询:

SELECT * FROM {node} n WHERE n.type = 'blog' and n.status = 1 AND n.moderate = 0

 

调用moderate_db_rewrite_sql()后,它向输入的查询中追加了AND n.moderate = 0。这个钩子通常用来限制对查看节点、词汇表、词语、或者评论的访问。db_rewrite_sql()仅限于它能够理解的SQL语法。当你需要使用JOIN语法时,千万不要用FROM语句来代替。

 

下面的不正确:

SELECT * FROM node AS n, comment AS c WHERE n.nid = c.nid

这个正确:

SELECT * FROM node n INNER JOIN comment c on n.nid = c.nid

 

Drupal中连接多个数据库

数据库抽象层使得函数名更容易记忆,它同时还在查询中添加了内置的安全特性。有时,我们需要连接到第3方或者遗留的数据库上,如果Drupal数据库API能满足这些需要并同时提供安全特性的话,那该多好啊。幸好,我们可以!

 

settings.php文件中,$db_url既可以是一个字符串(通常是这样的)也可以是包含多个数据库连接字符串的数组。下面是默认的语法,声明了一个单独的连接字符串:

$db_url = 'mysql://username:password@localhost/databasename';

 

当使用一个数组时,它的键是一个在激活数据库连接时所引用的简洁名称,而它的值就是连接字符串本身。下面是一个例子,在这里我们声明了两个连接字符串,默认的(default)和遗留的(legacy):

 

$db_url['default'] = 'mysql://user:password@localhost/drupal5';

$db_url['legacy'] = 'mysql://user:password@localhost/legacydatabase';

 

注意 Drupal本身使用的数据库一定要以default为键。

 

当你需要连接到Drupal中其它的数据库上时,你首先使用它的键名激活该连接,当你使用完连接时,将它切换回到默认的连接上。

 

// Get some information from a non-Drupal database.

db_set_active('legacy');

$result = db_query("SELECT * FROM ldap_user WHERE uid = %d", $user->uid);

// Switch back to the default connection when finished.

db_set_active('default');

 

注意 切记一定要切换回到默认的连接上,这样Drupal可以干净的完成整个请求生命周期并将它写入到自己的表中。

 

由于数据库抽象层设计的是每个数据库使用同样的函数名,所以多个数据库后台(比如,MySQL 和 PostgreSQL)不能够同时使用。然而,在同一个站点如何同时使用MySQL 和 PostgreSQL连接呢,相关信息请参看http://drupal.org/node/19522

 

使用模块的.install文件

 

在第2章我们已经看到了,当我们编写一个模块,它需要创建一个或多个数据库表来存储信息时,创建和维护表结构的SQL放在.install文件中,该文件与模块一同发布。通常包括的SQL主要是针对最常用的数据库系统的(MySQL 和 PostgreSQL)。

 

创建表

一个名为$db_type的全局变量决定了当前使用的数据库类型。在下面的例子中,一个hook_install函数分别为MySQL 和PostgreSQL包含了相应的CREATE TABLE语句。下面的例子来自于:

 

/**

* Implementation of hook_install().

*/

function book_install() {

switch ($GLOBALS['db_type']) {

case 'mysql': // Use same as mysqli.

case 'mysqli':

db_query("CREATE TABLE {book} (

vid int unsigned NOT NULL default '0',

nid int unsigned NOT NULL default '0',

PRIMARY KEY (vid),

KEY nid (nid),

) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");

break;

case 'pgsql':

db_query("CREATE TABLE {book} (

vid int_unsigned NOT NULL default '0',

nid int_unsigned NOT NULL default '0',

PRIMARY KEY (vid)

)");

db_query("CREATE INDEX {book}_nid_idx ON {book} (nid)");

break;

}

}

 

注意前面用于MySQL的表创建语句中下面的有点古怪的代码:

/*!40100 DEFAULT CHARACTER SET UTF8 */

 

/*意味着一个内嵌评论的开始,它在*/处结束(这是标准的评论语法,可用于许多语言,包括C和PHP)。这意味着,当使用的是一个不同的数据库时,注释定界符内部的代码将被忽略。如果开定界(/*)符后面紧跟了一个感叹号(!),那么MySQL将尝试解析并执行评论定界符内部的代码。如果感叹号(!)后面紧跟了一个MySQL版本号的话,只有当MySQL的版本等于或者高于给定的版本时,才会执行里面的代码。所以,前面的代码所表示的实际意思就是,“如果执行CREATE TABLE语句时,所用到的MySQL数据库的版本等于或者高于4.1,为该表使用UTF-8作为默认的文本编码”。

 

Maintaining Tables

When you create a new version of a module, you might have to change the database schema.

Perhaps you’ve added a column to support page ranking in the book module, and you have an

installed base of users. Here’s how their databases will be updated:

当你创建一个模块的新的版本时,你可能想要修改数据库的schema。可能你在你book(书)模块中添加了一列来支持页面等级,而且有许多用户使用了该模块。下面就是如何对他们的数据库进行更新:

 

1. 更新install钩子中的CREATE TABLE语句,这样你模块的最新用户将使用最新的schema来安装模块:

 

/**

* Implementation of hook_install().

*/

function book_install() {

switch ($GLOBALS['db_type']) {

case 'mysql': // use same as mysqli

case 'mysqli':

db_query("CREATE TABLE {book} (

vid int unsigned NOT NULL default '0',

nid int unsigned NOT NULL default '0',

rank int unsigned NOT NULL default '0',

PRIMARY KEY (vid),

KEY nid (nid),

) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");

break;

case 'pgsql':

db_query("CREATE TABLE {book} (

vid int_unsigned NOT NULL default '0',

nid int_unsigned NOT NULL default '0',

rank int_unsigned NOT NULL default '0',

PRIMARY KEY (vid)

)");

db_query("CREATE INDEX {book}_nid_idx ON {book} (nid)");

break;

}

}

2. 通过编写一个更新函数为已有用户提供一个更新的方法。更新函数使用序号来命名,从1开始:

 

function book_update_1() {

$items = array();

$items[] = update_sql("ALTER TABLE {book} ADD COLUMN rank int_unsigned

NOT NULL default '0'");

}

在升级了该模块以后,当用户运行http://example.com/update.php调用这个函数。

 

提示 Drupal追踪了一个模块当前使用的schema版本。该信息存放在system表中。为了让Drupal忘记它,可以使用devel模块的Reinstall Modules选项,或者直接从system表中删除该模块的记录。

 

Uninstall上删除数据库表

Administer Modules页面有一个Uninstall标签,它不仅允许禁用模块,还能够从数据库中将它们的数据删除掉。如果你想在这个页面为你模块的数据库表启用删除选项,那么你需要在你的模块的.install文件中实现uninstall钩子。你可能想同时删除你定义的所有变量。下面是我们在第2章编写的annotation模块中的例子:

 

function annotate_uninstall() {

db_query("DROP TABLE {annotations}");

variable_del('annotate_nodetypes');

}

 

 

Writing Your Own Database Abstraction Layer

编写你自己的数据库抽象层

假定你想为一个新的将来的名为DNAbase的数据库编写一个数据库抽象层,该数据库使用分子计算来提升性能。我们没有从头开始,而是将复制一份已存在的抽象层,接着修改它。我们将使用MySQL的实现,这是由于MySQL是Drupal中最常用的数据库。

 

首先,我们复制一份includes/database.mysql.inc并将其重命名为includes/database.dnabase.inc。接着我们修改每个包装函数内部的逻辑,使用DNAbase的功能来代替MySQL的功能。当我们完成了所有的这些修改以后,

那么在我们的文件中声明了下面的函数:

 

 

_db_query($query, $debug = 0)

db_affected_rows()

db_connect($url)

db_decode_blob($data)

db_distinct_field($table, $field, $query)

db_encode_blob($data)

db_error()

db_escape_string($text)

db_fetch_array($result)

db_fetch_object($result)

db_lock_table($table)

db_next_id($name)

db_num_rows($result)

db_query_range($query)

db_query_temporary($query)

db_result($result, $row = 0)

db_status_report($phase)

db_table_exists($table)

db_unlock_tables()

db_version()

 

通过更新settings.php中的$db_url,我们在Drupal中连接到DNAbase数据库来测试该系统。它看起来这样:

$db_url = 'dnabase://john:secret@localhost/mydnadatabase';

 

其中john是用户名,secret是密码,而mydnadatabase是我们将要连接的数据库名你可能还想创建一个测试模块来调用直接这些模块以保证它们正常工作。

 

总结

读完本章后,你应该能够

 理解Drupal的数据库抽象层

 进行基本的查询

 从数据库中获取单个或者多个结果

  获取一个限定范围内的结果

  使用分页器

 编写其它开发者可以修改的查询

 干净的修改其它模块中的查询

 连接多个数据库,包括遗留的数据库

 编写一个抽象层代码库

 

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值