Drupal 模块的 Hooks(钩子)

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社区想出了两种解决方案

第一种是 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的这些,将会事半功倍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值