创建你的第一个模块
这一章的焦点在于模块的创建。上一章我们学习了不少概念,现在咱们开始正式编程了。下面是一些我们即将学到的要点:
- 开始一个新模块
- 创建.info文件---给drupal提供模块信息
- 创建.module文件---drupal代码写在这里
- 增加一个block
- 掌握drupal的常用function
- 根据drupal代码格式来写代码
- 为drupal写自动化测试
在这章结束时 你应该对创建自己的模块有一个大体认识。
我们的目标:带区块的模块
我们准备创建一个简单的模块,这个模块将用block子系统来添加一个新的block,它能简单展示一个包括所有的现在可用的模块的列表。
区块子系统的概念请自行脑补
我们打算分三步走
- 创建新的模块文件夹和模块文件
- 进入区域子系统
- 用drupal自带测试框架写测试代码
按照敏捷开发 我们本应该要先写测试代码,这叫TDD 测试驱动,很流行~
更多关于敏捷 请访问http://agilemanifesto.org/.
但是,在这里我们不是要关注某一种方法论,而是要掌握如何编写模块,因为编写模块非常简单,顺带手学学测试而已,所以我们要先写模块后写测试 原因如下:
- SimpleTest它大概有我们实际模块代码量的两倍。
- 我们在编写测试时 需要先假定API是什么
当然 我们也可以选择常规的TDD开发方式 即:先写测试代码,然后写模块。
so 让我们来编写第一个模块吧
创建新模块
创建一个drupal模块是很简单的。有多简单呢?简单到超过5000个模块已经被开发出来了,而且很多drupal开发者居然都是PHP菜鸟级的!实际上,咱们这一章的代码就能给你展示代码编写有多简单 --- 一个文件夹和两个小文件而已。
模块名
不用说 模块需要一个名字,但是需要注意,一个drupal模块有两个名字:
- 人类可读名:如 Views
- 机读名:drupal内部用
放哪儿?
这里有三个地方有module
你们猜猜应该放在哪儿呢?一般人可能认为会放在/modules里面,错啦~
因为/modules是不允许你放任何东西的,它们都是drupal核心模块,在更新的时候将会被覆写掉
第二个放模块比较明显的位置是/sites/all/modules,这是放所有的第三方插件模块的地方 诸如Drush等 就会下载后存放到这里,咱们的代码放到这里也是可以的 起码更新时不会被覆写掉,但是不推荐,除非你做多站,模块需要被所有站访问。
推荐放到这里/sites/default/modules,缺省状态下你要自己建一个文件夹,这样做的好处不少,一个是我们找自己的代码好找,还有就是加载时顺序可以调整等等。
它的缺点在于制作多站时模块不会被其他站读到。
创建模块目录
我们要在 /sites/default/modules里创建一个新模块,里面包含一个.info文件和一个.module文件
文件夹与文件都应该被命名为机读名称
like this
The purpose of the .infofile is to provide Drupal with information about a
module—information such as the human-readable name, what other modules
this module requires, and what code files this module provides
module—information such as the human-readable name, what other modules
this module requires, and what code files this module provides
编写info文件
.info文件:提供模块的基本信息
代码如下:
;$Id$
name = First
description = A first module.
package = Drupal 7 Development
core = 7.x
files[] = first.module
;dependencies[] = autoload
;php = 5.2
name = First
description = A first module.
package = Drupal 7 Development
core = 7.x
files[] = first.module
;dependencies[] = autoload
;php = 5.2
好 第一行的 是什么?
它是占位符的版本控制系统来存储有关文件的信息的。当文件check in 到drupal的CVS库中时,这一行将自动扩展为:
;$Id: first.info,v 1.1 2009/03/18 20:27:12 mbutcher Exp $
这段信息表明了该文件何时签入,以及谁放入的
现在$Id$已经不用了 这是CSV专用的,而drupal迁入git后 $Id$标签将不再出现
name和description是必须的,每个模块的info都会有 在哪里出现呢?如图:
上面的name和description就是对应的~!
package = Drupal 7 Development
包,如上图 core就是包的名字 所有的模块都放到这个包中 就是放到文件夹中,请注意 这是个人类可读的名字
files[] = first.module
这是声明我们用到的模块是哪个
dependencies[]
这个指令很重要 但是我们这里没有用 这是声明依赖关系的 如果此模块依赖别的模块 那么单独是无法开启这个模块的
这样 我们的first.info创建好了 drupal读取的时候 这个模块将出现在我们的“模块”页面上
创建.module文件
.module是一个包含了主要钩子接口的php文件,我们俩共同学习它
钩子,它是一个实现了类似观察者的设计模式的function,常常被用来为某一特定事件做回调。
当drupal触发了某一事件,而此事件正好是个钩子(这样的事件有数百个),drupal将遍历所有模块找匹配的钩子,然后执行,当所有钩子执行完毕后,drupal才往下处理。
编码开始:我们用到一个钩子 --- 提供help信息的钩子
<?php
// $Id$
/**
* @file
* A module exemplifying Drupal coding practices and APIs.
*
* This module provides a block that lists all of the
* installed modules. It illustrates coding standards,
* practices, and API use for Drupal 7.
*/
/**
* Implements hook_help().
*/
function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
// $Id$
/**
* @file
* A module exemplifying Drupal coding practices and APIs.
*
* This module provides a block that lists all of the
* installed modules. It illustrates coding standards,
* practices, and API use for Drupal 7.
*/
/**
* Implements hook_help().
*/
function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
讲解代码前 我们先说说编码标准问题,这里有几个问题需要注意的:
- 缩进:所有php和js文件都不用tab缩进,全部使用两个空格的方式
- <?php?>指令处理:开始由<?php,但是没有以?>结尾。这样做有很多原因,暂不解释。
- 注释:除了funtions classes interfaces constants files 和 globals ,其他所有注释用//
- 函数:函数应该以小写字母使用下划线命名 分隔单词。稍后我们将看到如何类的方法名称不同 从这个。
- 变量:变量名应该使用下划线在所有小写字母 分隔单词。在对象的成员变量的名称不同
(下面一堆关于注释的讲解 不译了)
下面看我们的第一个function
/**
* Implements hook_help().
*/
function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
* Implements hook_help().
*/
function first_help($path, $arg) {
if ($path == 'admin/help#first') {
return t('A demonstration module.');
}
}
注释里的 接口 hook_help() 这是drupal注释标准里的 当一个function是一个钩子函数时 前面必须注明用的哪一个。第一别人能快速理解,第二一些自动化工具能找到钩子接口
下面讲解用法
hook_help接口当用户在查看帮助文档时被调用,每个模块都有一个help钩子,我们的这个模块通过这个钩子提供了简短的帮助文字
这个function是如何变成一个钩子接口的呢?是由于严格命名,把hook_help变成first_help,就可以了
每一个钩子都拥有自己的参数 具体请查看http://api.drupal.org
一个hook_help钩子有两个参数:
- $path:帮助系统uri路径
- $arg:访问这个路径的权限
在我们这个例子中 我们只需要关心第一个参数就可以了 基本上 帮助系统通过确定uri来显示帮助信息,我们需要声明一下为这个特殊的uri返回什么帮助信息就行了
具体而言 帮助信息的uri就是admin/help#MODULE_NAME,其中MODULE_NAME就是模块的机读名称。
我们的function通过检查$path来开始工作,如果路径是admin/help#first 这是这个模块的缺省uri,这时它将返回一些简单信息。
现在 让我们先把模块激活 如图
激活后 可以看到help出现在OPERATIONS下,点击进去 可以看到
系统运作的关键在于对路径的检查,当$path符合时,模块开始扑捉到动作。
t()函数和翻译
每一个string都会套在t函数中,这是为了以后的国际化
- 如果没有其他语言支持 那么t函数只有一个参数
- 多语言用一个.po文件来支持
- 它并不是简单的赋予一个变量来完成功能的
为什么不用变量来代替呢?因为当自动翻译工具运行代码时 它并不运行,只是读取,并不知道正确的变量到底是什么。
下一步 咱们要开始用block api来生成一个block,里面是所有可用的模块名称列表
运用block API
drupal 6中block只有一个function接口 drupal7中 block有了一系列的function可用
我们有一大堆的block API可以用,提供了从声明一个新区块到改变已存在区块的内容和行为,在我们这个简单的模块中,我们准备用两个不同的钩子
- hook_block_info():告诉drupal关于新区块或我们声明的一系列区块的信息
- hook_block_view(): 告诉drupal区块显示时它将做什么
需要谨记的是---block API与其他的API一样,一个模块的给定钩子的对应接口只能有一个,所以我们只能有一个first_block_info() function。
因为模块能够创建无数个block,这就意味着block API必须能够一个接口管理多个bolck,因此,first_block_info()能声明任意多个block,能返回任意多个block
这里是block官方API地址 里面甚至有个module的例子 http://api.drupal.org/api/drupal/developer--examples--block_example.module/7.
hook_block_info()
所有的function我们都放在first.module中,现在我们开始创建hook_block_info()钩子
创建这个钩子的用意是告诉drupal这个模块提供的所有block的信息,注意,就像所有钩子那样,你只需要提供实现功能的接口就行,换言之,如果没有这个接口,那么drupal就会假定这个module没有相关的block。
/**
* Implements hook_block_info().
*/
function first_block_info() {
$blocks = array();
$blocks['list_modules'] = array(
'info' => t('A listing of all of the enabled modules.'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
* Implements hook_block_info().
*/
function first_block_info() {
$blocks = array();
$blocks['list_modules'] = array(
'info' => t('A listing of all of the enabled modules.'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
可以看出 hook_block_info()的实现不需要参数 会返回一个关联数组
这个返回的数组应该是一个包含了所有该模块声明了的block的对象,这个对象应该类似这样:$name => array($property => $value)
以上代码定义了一个名为list_modules的区块,它有两个属性:
- info:显示在区块管理界面的一句话简介
- cache:告诉drupal如何缓存此区块。我们选择的是放弃缓存
还有很多其他属性 请看http://api.drupal.org/api/function/hook_block_info/7
下一步 我们要把这个块儿显示出来
The block view hook
需要用到的是hook_block_view(),它需要一个参数---用于检索的块名称,返回一个给定名字的数组集合。
我们知道我们的块名称为list_modules,开始编写代码
/**
* Implements hook_block_view().
*/
function first_block_view($block_name = '') {
if ($block_name == 'list_modules') {
$list = module_list();
$theme_args = array('items' => $list, 'type' => 'ol');
$content = theme('item_list', $theme_args);
$block = array(
'subject' => t('Enabled Modules'),
'content' => $content,);
return $block;
}
}
* Implements hook_block_view().
*/
function first_block_view($block_name = '') {
if ($block_name == 'list_modules') {
$list = module_list();
$theme_args = array('items' => $list, 'type' => 'ol');
$content = theme('item_list', $theme_args);
$block = array(
'subject' => t('Enabled Modules'),
'content' => $content,);
return $block;
}
}
这个function拿到了区块名称后,它将把名称作为唯一标识符来对待
如果$block_name为list_modules,那么我们将返回内容。
我们来看第一行 我们调用了drupal函数 module_list()。这个function简单的返回了一个模块名称的数组。
现在我们有了一份儿元数组数据,下一步我们要格式化它好让其显示,drupal总是通过主题层来干这个事儿的 所以我们交给主题层 期望它们显示成为一个有序列表
drupal7 中 theme()需要一到两个参数
- 主题操作的名称
- 传到主题层的变量数组
以前的版本中 theme可以传入任意多的参数 取决于主题层的要求,现在不会这么做了。
为了把string数组转变成html list,我们采用item_list主题,我们带入了两个参数
- 我们希望列出的条目
- 我们希望看到的列表的类型
就这样 我们用theme()得到了一个html
现在我们所要做的就是组装我们的区块view层必须返回的data了,hook_block_view()接口需要返回一个带有两个参数的数组:
- subject: 块名称或块题目
- content: 带有格式的block的内容
第一个参数我们硬编码写死了 第二个参数我们把theme()代入给content
我们需要关注的是$block代码的写法
$block = array(
'subject' => t('Enabled Modules'),
'content' => $content,
);
'subject' => t('Enabled Modules'),
'content' => $content,
);
最后是有逗号的 这是符合php语法的
第一个模块的动作
可以开始了 激活模块 然后打开block管理界面,把这个block放到一个region中 看看效果吧
测试代码略