欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Drupal 模块的 Hooks(钩子)。module_invoke_all, module_invoke, module_invoke

shiping1 的头像

Drupal 一个CMS(Content Management System),Drupal的模块系统实现了一个钩子系统,什么是钩子?我已经开始了解钩子是允许您在多个地方调用。让我们先来看看一个PHP函数的编写和应用:

1
2
3
4
5
6
7
8
9
10
11
function do_something($thing)
{
    //这里写一些代码实现一些功能
    //例如插入数据库资料
    //标记一个物体
    //唱一首歌。。。。        
    return $something
}
 
... 程序的其他地方  ...
do_something();

一旦我们编写函数和部署代码,我们不能轻易改变它。也就是说,它虽然很容易被编辑,但是其他程序员不敢乱改,因为他们不懂这个函数的工作原理。所以要很好的跟其他程序员沟通,就必须通过很详细的代码注释。即使你有一个团队并且拥有完善的沟通,也有问题因为修改函数而产生副作用。所以就有一个概念:永远不改现有代码,除非你已经完全了解代码所做的任何事情。

我们试图解决问题,当“一些事情”发生,我们想采取额外的动作,需求的变化,就需要新的功能加入,这对于一个开发人员来说,我们得要随时应付这些新功能的加入。那么,我们如何不改变核心代码,但又能实现这些功能呢?

 

钩子系统很好的解决了这个问题。当你想做些什么,不是直接调用一个函数,而是调用一个钩子。

1
2
3
4
5
//老方法,直接call do_something函数
//do_something();
 
//新方法,调用do_something的 hook
module_invoke_all('do_something');

当一个hook 被调用了,Drupal 会

  1. 获得所有安装并启用模块列表
  2. 问每一个模块:你有do_something 这个 hook吗?
  3. 如果有,那么Drupal在这些模块实现了钩子调用函数

然而如果作为一个核心的开发者,你可以达到 “你想要”的同时,还能让其他开发者通过钩子实现“他们想要”

举个例子?

这些都是抽象的概念,我们需要举个例子说明

  1. 编写代码来调用一个钩名叫helloworld
  2. 在module_a中编写代码以实现helloworld的钩子
  3. 在module_b中编写代码以实现helloworld的钩子

Drupal是单入口的结构,为了不影响正常访问,我们把index.php 复制一份为drupalla.php,并且改一下代码

1
2
3
4
5
6
define('DRUPAL_ROOT', getcwd());
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
module_invoke_all('helloworld');
echo '<p>Done</p>';
###menu_execute_active_handler();

这里我们注释了menu_execute_active_handler函数,因为这会呈现出一个Drupal页面,对于测试页面,我们不希望有过多的元素。这是为什么我注释了它。

只要我们有调用drupal_bootstrap 就可以,这将会载入了Drupal系统,包括所有为Drupal运行必要的代码。

在这个基础上,我们加了两行简单的代码:

1
2
module_invoke_all('helloworld');
echo '<p>Done</p>';

将会输出 Done,而module_invoke_all比较有趣,虽然我们已经调用了一个钩子,但没有一个模块实现这个钩子,所以这个调用,不会有任何事情发生。

实现一个钩子:

在本文的前面,我们创建了两个模块。模块A和模块B。确保这些模块是开启状态的。

然后打开 模块 A的 .module 文件,增加

1
2
3
4
5
6
#File: sites/all/modules/module_a/module_a.module
<?php
function module_a_helloworld()
{
    echo "<p>Our friends at ".__FUNCTION__." want to say Hello World</p>";
}

清空缓存

然后你会发现输出中多了一行

1
Our friends at module_a_helloworld want to say Hello World

 

现在你已经实现了第一个钩子,所有需要有:

  1. 在.module文件创建一个函数
  2. 函数名必须是你的模块名开头,如模块A的模块名是module_a,所以它的hook必须是module_a_xxx(),例子:module_a_helloworld()
  3. 在你想调用的地方,引用这个钩子。

让我们在模块B也做同样的操作,在module_b.module 里面增加

1
2
3
4
5
6
#File: sites/all/modules/module_b/module_b.module
<?php
function module_b_helloworld()
{
    echo "<p>Our friends at ".__FUNCTION__." want to say Hello World</p>";
}

清空缓存,再用浏览器打开drupalla.php这个时候,会看到输出了两条信息

1
2
3
Our friends at module_a_helloworld want to say, Hello World
 
Our friends at module_b_helloworld want to say, Hello World

所以module_invoke_all('helloworld'); 会循环的读取所有模块的 helloworld 这个hook。

 

钩子返回值

让我们稍微改一下drupalla.php文件

1
2
$results = module_invoke_all('helloworld');
var_dump($results);

输出结果是

1
2
3
4
5
6
Our friends at module_a_helloworld want to say, Hello World
 
Our friends at module_b_helloworld want to say, Hello World
 
array
    empty

 

module_invoke_all返回的是一个空数组。让我们稍微再改改两个hook的代码,将里面的echo改为return

1
2
3
4
5
6
7
8
9
10
11
12
#File: sites/all/modules/module_a/module_a.module
...
function module_a_helloworld()
{
    return "<p>Our friends at ".__FUNCTION__." want to say, Hello World</p>";
}
...
#File: sites/all/modules/module_b/module_b.module
function module_b_helloworld()
{
    return "<p>Our friends at ".__FUNCTION__." want to say, Hello World</p>";
}

再看看drupalla.php,输出结果是

1
2
3
array
    0 => string '<p>Our friends at module_a_helloworld want to say, Hello World</p>' (length=65)
    1 => string '<p>Our friends at module_b_helloworld want to say, Hello World</p>' (length=65)

调用一个钩将总是返回一个数组,而且那个数组将包含值的每个模块的钩实现返回值。如果一个钩子没返回值,那数组就会是空数组。

我们再次改改模块A的代码如下,看看结果会是怎样

1
2
3
4
function module_a_helloworld()
{
    return array("one","two","three");
}

这时候返回的结果是

1
2
3
4
5
array
    0 => string 'one' (length=3)
    1 => string 'two' (length=3)
    2 => string 'three' (length=5)
    3 => string '<p>Our friends at module_b_helloworld want to say, Hello World</p>' (length=65)

可以看出,Drupal有合并钩子实现的返回值到调用返回数组,而不是返回一个嵌套数组,而如果你数组使用一个非顺序结构,如

1
2
3
4
function module_a_helloworld()
{
    return array("foo"=>"one","baz"=>"two","bar"=>"three","function"=>__FUNCTION__);
}

返回的结果变成:

1
2
3
4
5
6
array
    'foo' => string 'one' (length=3)
    'baz' => string 'two' (length=3)
    'bar' => string 'three' (length=5)
    'function' => string 'module_a_helloworld' (length=18)
    0 => string '<p>Our friends at module_b_helloworld want to say, Hello World</p>' (length=65)

它返回一个关联数组的键合并到我们调用的返回值。所以如果同时调用了两个hook,而两个hook中有一个key是一样的,会是怎么的结果?让我们试一下

1
2
3
4
5
6
7
8
9
10
11
12
#File: sites/all/modules/module_a/module_a.module
...
function module_a_helloworld()
{
    return array ("foo"=>"one","baz"=>"two","bar"=>"three","function"=>__FUNCTION__);
}
...
#File: sites/all/modules/module_b/module_b.module
function module_b_helloworld()
{
    return array('function'=>__FUNCTION__);
}

两个钩实现是返回一个数组和一个关键的命名“function”,重载页面,结果如下

1
2
3
4
5
6
7
8
array
    'foo' => string 'one' (length=3)
    'baz' => string 'two' (length=3)
    'bar' => string 'three' (length=5)
    'function' =>
        array
        0 => string 'module_a_helloworld' (length=18)
        1 => string 'module_b_helloworld' (length=18)

 

因此,如果有一样的关键词命名,Drupal会合并成一个嵌套数组,当你需要调用钩子,这些返回值的结构,是非常好重要的。

Hooks(钩子) vs. Events(事件)

返回值是钩子与传统event/observer系统的不同之处,一个比较简单的句子区分两者的区别

Event/Observers :嘿,只是让你知道,事情发生了。

Hooks :嘿,我们正在做这件事,你想成为它的一部分吗?

 

调用钩子

有些时候,你不想在所有模块中寻找所有相关的钩子,这样对性能有一定的影响,我们只想要直接调用其中一个钩子就好了,这个时候我们可以用module_invoke

1
$results = module_invoke('module_a','helloworld');

这里只调用了module_a的钩子,而module_b的没有调用。

 

接下来是带参数的钩子,module_invoke 跟 module_invoke_all 都支持带参数的钩子,

1
2
module_invoke_all('helloworld','one','two','three');
module_invoke('module_a','helloworld','one','two','three');

这些参数,反过来,传递给钩的实现。请看下面代码

1
2
3
4
function module_a_helloworld($param, $another_param, $third)
{  
    return array('joined'=>implode('##',array($param, $another_param, $third)));
}

字符串‘one’,’two’, ‘three’是对应参数$param, $another_param, $third。

 

其实背后drupal是通过php的func_get_args 函数实现的。这个实现提出了一个问题,这就是我们所说的最后一件事。

有时,你希望你的钩实现能够改变一些你正在使用的数据结构。在我们2011 / PHP 5的世界里面,我们很可能实现这个通过传递一个对象。

1
2
3
4
$object = new SomeClass();
module_invoke('module_a','helloworld',$object);
//we've got an object that's been modified by our hook implementations
$object;

PHP 5中对象的引用,这意味着钩实现可能让一切他们想要的变化。

然而,Drupal是有10年历史了。开始时候还是一个PHP 4世界,一切都是通过复制。通过在大数组(甚至PHP 4的原始对象)的函数,然后返回他们伴随着潜在的性能问题,因为另一个副本的数组或对象需要。PHP提供了一个解决方案,允许你声明你的函数在这样一种方式,通过引用传递参数

1
2
3
4
function somefunction(&$param)
{
    //& 参数意味着如果我们在函数改变了它的值,它将影响全局。
}

& 是什么作用?下面举个例子

1
2
3
4
5
6
$a = 1;
function go(&$b) {
 $b = $b + 1;
}
go($a);
echo $a;

========系统输出2,因为函数直接修改了$a的数值

1
2
3
4
5
6
$a = 1;
function go($b) {
 $b = $b + 1;
}
go($a);
echo $a;

=========系统输出1,因为$b = $b + 1只是在函数内部修改,外部不生效

这个例子,大家应该懂得&符号的作用了吧?

然而因为because module_invoke 跟module_invoke_all 都是用func_get_args,这是不可能通过钩调用传递值的引用。
 

我理解为:

drupal_alter 是hook调用前给你最后一次参数修改的机会,达到改变钩子的调用状态效果,所以可以理解为钩子调用前的修改;
 

 

这让Drupal社区想出了两种解决方案

第一种是 drupal_alter 函数,这是其中一种替代方法来调用钩子实现。

1
drupal_alter('helloworld',$data);

将会调用hook helloworld_alter,变量$data将会引致函数外的值改变。在Drupal 7中可以通过三个参数作为引用参数。

1
drupal_alter('helloworld',$data, $foo, $bar);

另一个需要注意的是drupal_alter不返回任何值。完全是为了允许模块钩子改变变量。

 

破坏封装(Breaking Encapsulation)

因为drupal_alter的约束,这里有第二种方法调用。Drupal提供一个功能叫做module_implements,它将返回一个列表的模块,实现一个特定的钩。试试下面的

1
2
$results = module_implements('helloworld');
var_dump($results);

将会返回两个模块的数组

1
2
3
array
  0 => string 'module_a' (length=7)
  1 => string 'module_b' (length=7)

 

当有人想通过引用传递变量变成一个钩子实现和不使用alter命名语法,或者他们想钩实现来返回一些东西,你就会看到这样的事情

1
2
3
4
5
6
7
8
9
10
11
12
$hook = 'helloworld';
$etc = array('one','two');
$param_by_ref = 'four';
$all = array();
foreach(module_implements($hook) as $module)
{
    $function = $module . '_' . $hook;
    $single_hook_return = $function($param_by_ref, $etc);
    $all = array_merge($all, $single_hook_return);
}
var_dump($all);
echo '<p>Done</p>';

 

由module_invoke_all提供的功能给复制了,但没直接用module_invoke_all。

 

结语

在Drupal开发中,你大部分时间将会是用钩子开发模块和应用第三方模块,同时通过Drupal 核心api,每个Api提供很多不同的钩子实现,每个都有他们的功能。灵活运用drupal的这些,将会事半功倍。

评论

toto多背一公斤的头像

调用一个钩将总是返回一个数组,而且那个数组将包含值的每个模块的钩实现返回值。

雁无边的头像

学习中。。。

查了一下d7 drupal_alter的API是:

drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL);

来自 http://www.drupalla.com/node/1569
普通分类: