[2009/04/16]发布《Drupal项目实战-公司订餐系统(四)》本博客内容均为原创(Original),如有雷同,纯属巧合。转载请注明出处。同时欢迎学术探讨与批评。

2009年4月16日星期四

不使用Locale模块本地化Drupal

众所周知,在Drupal中要本地化,需要首先开启Locale模块,然后添加中文语言支持,再将中文包导入。其实还有一个很隐秘的地方可以放置翻译字符串,它在settings.php中。看下面的代码:
/**
* String overrides:
*
* To override specific strings on your site with or without enabling locale
* module, add an entry to this list. This functionality allows you to change
* a small number of your site's default English language interface strings.
*
* Remove the leading hash signs to enable.
*/
# $conf['locale_custom_strings_en'] = array(
# 'forum' => 'Discussion board',
# '@count min' => '@count minutes',
# );
如果你只需要翻译Drupal网站中的少部分字符串,那么就不需要开启Locale模块,再导入翻译包那样费劲了。在settings.php中,可以添加一些翻译字符串,Drupal在显示字符串时,会首先用这里面的字符串去进行替换。这样会省去很大一部分的资源消耗。比如:
$conf['locale_custom_strings_zh-hans'] = array(
'forum' => '讨论组',
'@count min' => '@count 分钟',
);
有两点需要注意:
  1. 要翻译中文,需要使用locale_custom_strings_zh-hans
  2. 要将settings.php存为utf-8格式
Enjoy!

Drupal项目实战-公司订餐系统(四)

在本系列上一个文章中,我画了一些页面草图,用于展示网站原型。本文中将讲解如何用Drpual来实现。

在上面的文章中,我们已经创建了一个Food节点类型,用于存储各个菜单,供员工选择。也就是说,基本上实现了菜单的管理功能。那么接下来要实现在线订餐的功能了。

为了简化功能,我们先不实现购物车的功能,只需要实现用户在设置订餐数量后,即可提交一个订单。午餐管理员就可以查看用户订餐列表了。

可以看出,现在需要下列“东西”:

  • 一个表单:订餐表单,显示一个文本框,用于让用户输出订餐数量,以及一个提交按钮。

  • 一个数据库:用于存放用户的订餐数据。

  • 一个页面,午餐管理员可以查看员工的订餐情况。

除此之外,还需要显示一些提示信息等。

在平时做项目时,我喜欢迭代式的开发,简单的讲就是先开发主要功能,再逐步完善。一方面这样可以使客户能够清楚的看到进展,另一方面也能确保系统更为“扎实”。除此之外,还可以使程序员自己有一定的满足感,避免整天忙碌于开发却看不到效果,产和厌倦感。

下面按照上面的列的“东西”,一个一个的找解决方案。

订餐表单

这个表单上需要下列元素:

  • 一个选择框,列表值为:1, 2, 3, 4, 5。我们假定一个员工最多只能订5份套餐。

  • 一个提交按钮

这应该是最简单的表单了,下面看看怎么实现。

毫无疑问,需要使用传说中的Form APIForm API简称FAPIDrupal中一个非常强大的表单生成函数。它包括表单生成、表单处理及结果显示等各个阶段的功能。其中最重要的几个函数为:

drupal_get_form()
有一个钩子函数和form有关,不过大家要弄清它的用途:hook_form。这个函数是用于通过模块自定义content type时使用的,可以捕获create/edit节点时提交的表单。但这里要用的是drupal_get_form(), 用于生成一个表单。

首先创建一个生成表单元素的函数function buy_food_form(),然后使用drupal_get_form('buy_food_form')就可以输出表单了。除了生成表单外,还要对用户提交的表单进行处理。Drupal中,表单处理分为两步:

  1. 验证提交的表单数据

  2. 对数据进行处理

这两个步骤都转变为两个钩子函数:hook_form_validatehook_form_submit。我们只需要将hook换成生成表单函数的前缀即可,如food_order_form_validatefood_order_form_submit

由于需要自己写函数了,因此不可避免的需要自定义模块了。我们将订餐模块命名为Food Order Module。需要创建下面两个文件:

  • food_order.info

  • food_order.module

下面分析一些表单提示函数和处理函数。

OK,表单准备好了,该研究一下将表单放哪里了。由设计图看出,我们需要将表单放置在每个菜单页面上,这样用户在浏览到感兴趣的菜单时,就可以直接订餐了。那么如何将表单放置在菜单页面中呢?有两种方法:

  • 修改node-food.tpl.php

  • 生成Block,放置在node/*页面中

我更倾向于第二种方法,虽然第一种方法更为直接。本例中我们还是使用第二种方法,因为这样更遵循`DrupalMVC模式,而且自定义模块的好处是可以根据需要开关模块。

数据表

我们需要使用数据库来存储哪些同事订了什么餐。首先要做的工作是创建一个数据表。等等,由于我们使用的是强大的Drupal,是不是不需要创建数据表也可以实现这样的功能呢。答案是:YES,但是,本例为了让读者练习使用自定义的数据表,因此不采用Drupal模块的解决方案。其实在Drupal中有个很有名的电子商务类模块——Ubercart。其实订餐这个业务和买商品本质是一样的,因此Ubercart也可以实现这样的功能,只不过有点重量级了。感兴趣的朋友们可以试试Ubercart是否可以完成同样的需求。

Drupal之所以说它是CMF(Content Management Framework),是因为一方面,它具有很好的层次结构,比如“数据库抽像层”,同时提供了很多方便的API——一系列的数据库操作函数,用起来还是相当方便的。使用DrupalDAL,可以使我们不去关心数据库服务器配置及连接等细节,而且由于Drupal除了mysql外还支持pgsql,因此我们的程序还可以移植到pgsql上而无须更改数据库操作代码。而且,有望在不远的将来,Drupal可以使用MSSQLOracle等大型数据库。数据库抽像层的好处也就更为明显了。

数据表中主要字段有:

  • 菜单号(nid)

  • 用户ID(uid)

  • 订餐数量(qty)

  • 应付金额(money

  • 订餐时间(created)

订餐查看页面

最后,需要一个页面,让管理员可以查看当天有哪些人订餐了。当然也可以查看历史订餐信息,不过这个没有什么太大意义。因此在这个页面上只查看当天的订餐信息就可以了。

我们通过一个表格显示所有订餐的信息。DrupalAPI中有一个theme_table的函数,可以生成表格,而且还可以自动生成排序字段,不过使用起来稍微有些复杂。但还是推荐大家使用。

由于我们是自己创建的订餐表,因此需要用到db_query

$sql = “SELECT * FROM {food_order}”;

$result = db_query($sql);

while ($row = db_fetch_array($result)) { }

另外,还可以使用pager_query轻松实现分页。

下面总结一下,本文给出了实现“在线订餐”的基本解决方案,主要是自己创建数据表,并使用FAPI生成表单,用db_query等插入数据及显示数据。下一小节将给出源代码,供大家参考。

2009年3月2日星期一

使用sites文件夹灵活管理多站点配置

Drupal目录下的sites文件夹是用于存放所有开发者对Drupal自定义的模块脚本以及主题文件的地方。同时,还可以用于存放不同的站点配置文件。在sites目录下,默认有两个子文件夹:all和default。其中,default文件夹用于存放settings.php,即网站配置文件,里面主要包括数据库连接信息。all目录用于存放第三方或自定义模块。

Drupal支持多站点共用一个Drupal程序,意味着我们可以通过在sites目录下创建以网站域名为名称的子文件夹。Drupal就会根据网站域名读取对应的配置。

举一个经常碰到的应用场景:
在发布了网站后,需要维护本地开发用的程序和网站发布程序之间的同步。一般情况下,生产服务器的数据库配置等信息与本地开发环境是不同的。如果只有一个配置文件的话,那么在同步网站程序和本地程序时,就需要手工过滤或更改配置文件。
这时就可以利用Drupal支持多站点配置文件的特性,在其sites目录下创建一个localhost子目录,以及www.example.com的子目录。然后分别将settings.php放置于这两个文件夹,并更改为相应的配置。那么当对本地程序和服务器程序进行同步时,就不需要更改配置文件了。

2009年2月26日星期四

Disable user language模块介绍

今天给大家介绍一个新模块:DUL ( Disable user language : 关闭用户语言选择)。

在开启了Local模块后,Drupal即可以支持多语言界面了。同时,也使用户可以在Account中修改他所需要的语言界面——Language Settings。不过,在很多情况下,比如做国内的网站,并不需要用户选择“英文”界面,因为有很多情况下,界面中的文字是直接用中文写的,如果用户切换到英文界面,就会很“ugly”。当然,我们还是提倡大家尽量用t()写更“国际化”的界面。

这时,我们就需要DUL模块了。开启它后,它就将user/edit中的Language Settings部分去掉了,用户只能使用网站的默认语言。

DUL模块的下载地址为:http://drupalmodules.com/module/disable-user-language

2009年2月21日星期六

使用Drupal 6 Form API上传文件

在Drupal 6中,上传文件的函数与Drupal 5中的File Interface是有不同的。在Drupal 5中,上传文件使用的是file_check_upload函数;而在Drupal 6中,这个函数被取消了,换成了file_save_upload函数。
file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME)下面对每个参数作简要说明:

$source:自定义表单中,上传组件的名称。我在开发时,在这个参数上花费了不少时间,下面是手册中的解释:
$source A string specifying the name of the upload field to save.
我开始一直把upload field看成了upload file,以为这个函数传递的是文件名,因此始终不能上传成功。最后去include/file.inc的源文件中读源码,才发现了这个问题。因此提醒大家,不要误以为传递的应是文件名。

$validators: 用于检查上传文件的回调函数数组,元素就是函数名。这样使用:array('validate_upload' => $arg),其中$arg是你要传递给自定义检查函数的参数。
$dest: 上传文件的最终存放地址。Drupal有一个file_directory_path函数,用于返回当前Drupal的文件目录,如sites/default/files。
$replace: 指明如果上传的文件与现有文件重名,是否进行覆盖。如果不覆盖,则由Drupal自动重命名为filename_0, filename_1这样的文件。

下面给一个具体的示例,这是在drupal.org 找到的一些代码,然后我自己测试通过了。在使用之前,需确保files目录可写。
function my_form()
{
$form['upload'] = array(
'#type' => 'file',
'#title' => t('Upload your file:'),
'#size' => 40,
);
$form['#attributes'] = array('enctype' => "multipart/form-data");
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}

function my_form_submit($form, &$form_state)
{
$dest = file_directory_path();

if($file = file_save_upload('upload', array(), $dest))
{
$a = t('The attached file was successfully uploaded');
drupal_set_message($a);
}
else
{
drupal_set_message(t('The attched file failed to upload. Please try again'));
}
return;
}

2009年2月20日星期五

使用Firefox的GreaseMoneky插件

今天在写Blog时,发现Blogger的编辑页面中的编辑框实在是太小了。便能过Firebug把其高宽都调了了调,很好用。不过下次再用时,还要用Firebug来调,很麻烦。我记得有一个Firefox的Add-on叫Better Gmail,它是用本地的脚本去定制Gmail的样式,给我了点灵感。我马上就找到了Better Gmail所使用的核心插件——GreaseMonkey。

GreaseMonkey插件主要是可以让浏览器的使用者自定义网站的样式,或执行javaScript脚本。这样,我只需创建一个js脚本(GM是以.user.js为结尾的文件),然后使用GreaseMonkey设置,将进入Blogger的编辑页面时载入,就可以了。我写的脚本如下:

function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}

addGlobalStyle('#richeditorframe { height:500px ! important; } #modebar, #richeditorframe, #richbars, #editarea

{ width: 1000px }');
感兴趣的朋友们可以试一试。

2009年2月5日星期四

Drupal论坛简介

Drupal自带有“论坛”的功能,相信国内的广大Drupal使用者对Drupal自带的这个论坛功能也很感兴趣。但是,质疑声也是此起彼伏。很多开发者认为Drupal的论坛很不适合国人使用,因为其功能较为简单,且操作方式等也不太方便。那么Drupal论坛的特点是什么?究竟在哪种项目中适合使用Drupal的论坛呢?本文将引领大家简要了解Drupal的Froum,分析其适用场景及使用方法,并给出我的看法。

引言

Drupal的Forum模块是核心可选模块,开启Taxnomy和Comment模块后即可以使用。Drupal官方认为这个Forum像phpBB那样的message board。核心功能就是:

  • 用户就某一主题进行讨论
  • 把用户的讨论进行归档收集

这是Drupal论坛的设计宗旨,知道这两点可以便于设计者决策在实际项目中是否采用Drupal论坛。通常的情况下,客户们只说:我要一个论坛。但他究竟要的是什么样的论坛?主要的功能是哪些?这都需要详细的确定才行。比如下面两个客户对论坛的需求是截然不同的: 

客户甲:我需要一个简单的供用户交流的地方即可。论坛可以分为几个部分:产品讨论、使用交流和客户服务。嗯....只需要简单的发帖回帖应该就可以了。
客户乙:我们要打造全宇宙最酷最棒的论坛。论坛功能要炫!论坛版块要又多又全,用户互动性和可玩性要强,什么金币啊、等级啊、荣誉啊,一个都不能少!还可以互相买卖帖子,可以发不到一定等级就不能浏览那种帖子。积分功能要全,根据积分等级还要分什么虾米、大侠等,总之就是:炫!另外,由于用户至少同时在线100万,每个子论坛至少要支持10个以上的版主......

的确,第二个客户的需求有些夸张,存在的可能性也微乎其微。不过从这两个客户的需求上可以看出,虽然他们要求的都是一个“论坛”,但预期还是很不同的。前者需要的是论坛的基本功能,而后者则需要一个非常完整而复杂的论坛”系统“。那么结合Drupal Forum的特点,我认为,Drupal Forum比较适合前一类客户的需求。也就是说,Drupal Forum比较适合作为一个网站的支持社区,而不是作为主体功能。 

当然,我并不是说Drupal的论坛过简,由于Drupal强大的扩展性,我们可以自定义模块来进行论坛功能的增强。不过要在动手之前看看成本是否可以接受,还要考虑性能方面等问题。这也主要通过测试来考量。

Drupal论坛的组织结构

Drupal论坛由Forum和Container组成。前者就是我们熟悉的论坛单元,比如论坛分为“国内讨论”、“国外讨论”等,这些都可以创建为Forum。Forum还可以无限的嵌套,也就是有子论坛。而Container只用于对Forum进行分组和归类,用户不能在Container内发表主题。Container也是可以嵌套的。Drupal以这种方式组织论坛很容易让国人费解,其实只要一种Forum就可以了。 下图为Container和Forum的层次结构图。

我推荐的方式是这样的:最低层的论坛使用Forum,而其上的各种分类都使用Container。这样可以让用户只在最低层的论坛里发主题,就避免了在某个大类的论坛里发主题,好处就是在进行论坛主题归检索和显示时,大大提升检索效率。

为什么使用Drupal论坛

我认为使用Drupal论坛的原因很简单:它可以与其它网站的用户服务紧密的联系在一起,并可以利用Drupal的模块架构来进行功能扩展。Drupal是一个整站的框架,其中可以包含各种内容发布的功能,如博客、相册和论坛。那们,使用Drupal自带的论坛,可以有机的将这些功能整合在一起,给用户提供完整一致的使用体验。另外,对于计算用户贡献(如积分等制度)也是非常方便的。如果单独使用专门的论坛,如phpBB和Discuz等,如果很好的将论坛与Drupal站点相结合,就成为了一个不小的问题。而且由于目前还没有一个统一的标准来实现异构程序之前用户信息的同步,因此,使用Drupal构建的站点要使用论坛的话,最好的方式还是使用Drupal自带的Forum功能。

我曾经尝试过使用Discuz作论坛,来和Drupal集成,不过效果很不好。可能一方面因为我没有掌握Discuz的UCenter,另一方面我觉得UCenter还需要完善其API,使其更好用,而且文档和示例应该更全面一些。

常用的扩展模块

Drupal还是有很多关于Forum的模块的,比如最著名的当属Advanced Forum(简称AF)了。它的主要功能是使Drupal Froum看起来更像Forum,而不是简单的将节点和评论组织起来。比如它会将帖子的样式更改为:左侧是发表者头像和是否在线等信息,右侧为主题帖的内容。不过它的安装稍显复杂,而且目前针对D6还没有稳定的版本。

除此之外,还有一些模块可以起到辅助作用,如User Point模块可以使论坛具有积分系统,User Stat模块可以获取用户的一些注册日期、发帖数量等,用于计算用户等级并定制显示等。

示例

用事实说话,才会使人信服。我看过一些Drupal Forum搭的论坛,感觉不错的确实有几个,比如The Webmaster Forums(http://www.webmaster-forums.net),下面是一些截图,可供参考。
  
总结

简单的说,喜欢欧美风格的朋友们应该比较适合使用Drupal Forum。不过, 好不好用,还要用户说的算。以上只是我的个人观点,欢迎朋友们和我讨论。

2009年1月25日星期日

方医生恭祝大家牛年快乐!

值此新春到来之际,我代表我和我的家人,对在过去的一年来对我给予极大支持的同学们、朋友们,以及广大的Drupal爱好者们致以最诚挚的问候:祝愿大家在新的一年里,万事如意,心想事成!在牛年里,无论是学业、事业还是家庭,都牛气冲天。特别感谢美丽可爱的Ballet网页设计,也祝福Drupal在牛年发展的更好。

2009年1月17日星期六

让Drupal展示绚烂的图表-Open Flash Chart

有朋友问起关于在Drupal中生成图表的功能,并推荐了一个Charts模块。但经我的试用,发现这个模块文档奇缺,无法简单的安装成功。因此找到了另外一个模块:Open Flash Chart API。
Open Flash Chart 是非常有名的一个开源的、免费的Flash图表生成程序,它的网址是:http://teethgrinder.co.uk/open-flash-chart/index.php。通过它可以生成很酷很炫的各种图表。如下图所示。


为了方便大家的使用,我将做一个小视频,欢迎收看,敬请关注。

Drupal项目实战-公司订餐系统(三)

上节回顾

上一节的进展为: 

  • 创建了Food的Content type
  • 使用CCK创建了Food的相关字段
  • 修改了node-food.tpl.php

目前,简单的菜单管理功能模块可以说基本上完成了,除了权限控制部分。我认为权限控制应该在所有系统功能完成后再统一考虑,目前还是先实现功能为主。那么接下来,就可以进入订餐功能模块的开发了。本文我们就开始订餐功能的设计和开发。

订餐功能模块分析

公司员工在浏览了菜单后,可以选择订购此午餐,同时设定购买数量。由于菜单是一个套餐,因此通常一个员工只会选择一种套餐。但是,此处我认为应该在一定程序上考虑系统的可扩展性,也就是说,应该考虑一个用户订两个或多个“套餐”的情况。因为很显然的是,员工小A想替小B和小C订餐的话,那么使用小A的帐户就需要同时订购三种不同的套餐。

其实此处就存在一个实际的业务模型和系统模型间的映射关系。如果只是从“一个员工中午只吃一种套餐”的常识来想,将每个“帐户”设定为“只允许订购一种套餐”的话,做成的系统就明显不适合使用了。这点也提醒做系统设计的朋友们要注意。

由于目前本系统只限于某公司员工内部使用,因此不存在需要填写送餐地址的信息。同时为了方便起见,暂时不考虑网上支付。这样问题就简化了。另外,用户在订餐时,通常会按照个人口味提出一些建议,因此系统还需要提供一个文本框,用于用户对订餐进行一些“补充说明”。

总结一下,订饭的流程如下:

  1. 浏览菜单并选择“订购”某套餐
  2. 设置订购数量
  3. 输入补充信息
  4. 查看购物车并确认生成订单
  5. 查看订单状态(已收到订单、已发货、收货确认)

图!图!图!

我认为任何语言的描述都不如页面草图来的直接。UML和简洁的文字都不是和客户交流的最好方式。看得见的页面图才是最有效的办法。

上面几张图演示了在浏览套餐菜单后,可直接订餐的全部过程。
(未完待续)

如何升级Drupal 6

使用Drupal 6.0后,可以在后台管理的“报告”中收到Drupal最新更新的提示。如下图:
比如,我现在使用的是D6.8,Drupal提示我要升级为D6.9。那么如何升级呢?

首先直接下载D6.9的安装包,其中有一个UPGRADE.txt文件,用Editplus或其它高级点的notepad打开(因为是UNIX换行,所以在Windows的记事本中看是一团文字。),里面便是Drupal的升级建议,其中首要的几条是:

  1. 确定你的系统符合Drupal新版的要求。要求(Requirements)可以在http://drupal.org/requirements中查看。
  2. 备份所有的相关数据
  3. 检查自定义模块和主题的兼容性
  4. 确保你阅读了整个文件内容

第1条没的说,只要是LAMP或能运行PHP和MySQL即可。推荐使用APACHE。
第2条比较重要,我一般是备份一下整个数据库,以及sites/all下面的我自己安装的modules和themes。要不怎么说不要轻易更改Drupal内核文件呢,现在就看出好处来了。如果你开发时偷懒,不使用Drpual API和Hook来开发自定义模块,要升级系统时就很郁闷了,你需要记住你修改了哪些内核文件,而且还要和新版的文件对比再合并,so troubled。另外,建议大家读一下新版有哪些升级,是否对数据库有大的改动。
同时,还有sites/default/下的settings.php配置文件、.htaccess以及robot.txt文件等会被开发者修改的文件,都要留意的备份一下。OK,总结一下,列个表:

  1. 数据库
  2. sites/all下面的所有文件
  3. sites/default/settings.php配置文件。如果是多站点,还需要备份每个站点settings.php文件。如sites/example.com/settings.php。
  4. .htaccess
  5. robots.txt

第3条其实也很简单,去你使用的module和theme的project首页,检查一下是否可以使用在新版本中。一般情况,module或theme都是默认适用于同一版本系列的,比如5.x或6.x,微小的版本升级都可直接使用。不过对于像D5到D6这样的重大升级,还是要先看看是否支持D6,然后去下载新版本。
第4条写给英语好的朋友们,UPGRADE.txt文档里列出了升级的步骤,还是值得参考的。下面是我整理的步骤:

  1. 备份。备份所有能备份的东西。
  2. 获得超级管理员帐户。就是UserID = 1的用户。
  3. 将站点设置为off-line即下线状态,这样可以防止在升级时有用户访问。
  4. 将主题切换至默认主题——Garland。
  5. 关闭所有的第三方模块和自定义模块。
  6. 将Drupal自带的安装目录和程序全删除。这里指drupal根目录中除files、sites外其它的任何目录。
  7. 将新的Drupal程序包解压缩至相同位置。
  8. 设置好配置文件,如settings.php等。并且要确认是正确的。
  9. 关键的步骤:运行站点下的update.php,如http://www.example.com/update.php。如果你没权限运行,找到settings.php文件,将里面的$update_free_access设置为TRUE。然后在安装结束后再设置为FALSE。
  10. 运行update.php成功后,开启自定义和第三方模块modules。
  11. 再次运行update.php。
  12. 成功后,将站点设置为“上线on-line”。
  13. 更新结束。

欢迎朋友们交流升级心得,分享遇到的问题。

2009年1月12日星期一

Drupal Views 2攻略-自定义View模板

上个视频主要讲解了Views 2模块的基本使用技巧。本次视频的主要内容是:如何创建自定义模板文件来定制View样式。敬请收看!

Youku网址为:http://v.youku.com/v_show/id_XNjU2MzcyMjQ=.html



网速快的朋友可去Youtube看,地址为:
PS: Youtube生成高清版本可能需要一段时间。

2009年1月11日星期日

Drupal地心游记:神秘的羊皮纸

夜深人静。我独自一人收拾行囊,将探险所需要的各种工具装在包里。由于长期的奔波与不安定的生活,没有多少钱和家当,而且我估计在探索Drupal地心深处的过程中,钱也派不上什么用场。唯一可用的,就是几本经常翻看的手册,如PHP、HTML、CSS、JavaScript,以及MySQL等,它们陪伴了我很多年。这些手册应该是Drupal地心中最重要的参考依据了,少了它们,有些谜团就不好解答了。

动身之前,有一件非常重要的事,那就是有一样东西必须要弄到——地图。这个地图并不是去Drupal城的地图,而是探索Drupal地心的路线图。前年夏天,我辗转获得了Drupal城缔造者Dries Buyteart写的一本厚厚的书,每页的内容是被称为“程序源代码”的文字。这本书详细记录了Drupal城地下的构造及运转机制,显然是探险的依据,但还有一个重要的问题没有解决:入口的位置以及探索的路线。

我不停的翻看那本厚厚的书,试图从中找到答案。突然,我觉得这本书中有一页与其它纸的质地不同。其它的纸就像我们日常用的普通的纸,而这张纸的手感却不一样,很有韧性,而且上面的字迹比其它纸上的更为清楚且浑厚。原来是一张羊皮纸!



在很多西方的探险小说中,羊皮纸通常是非常重要的道具,因为它的上面会记载着通向神秘之国以及宝藏的路线图。同样,要探索Drupal,也一定会有一个“羊皮纸”,上面应该标注着地图或神秘的密码,这样才使探险者不会迷失方向。我翻看了很多次这本书,才将目光集中在这一页羊皮纸上。在这个页上有一行中的字迹比其它的更厚重——index.php。

经过我仔细的阅读,确定它就是(Drupal根目录下)index.php的源代码。不难发现,Drupal城中的所有建筑以及景观(页面),都是由index.php来生成的。那么index.php的源码中,也一定蕴含着探索Drupal地心的路线图了。

在Drupal中,index.php负责所有URL的解析和所有页面的生成工作。比如要访问增加内容的页面,它的URL是http://localhost/drupal/index.php?q=node/add;如果要访问管理页面,那就是index.php?q=admin。由此可见,index.php是整个Drupal系统的路由(route),用于接收URL请求,并跟据请求,调用不同的函数,最终生成整个页面。

即使我们开启了Clean URL,用简洁的方式(如http://localhost/drupal/admin来访问管理页面)访问页面,其原理只是利用Apache的Rewrite功能,将Clean URL转义为index.php?q={},调用index.php来生成页面。

据此分析,Index.php就是“Drpual城地心”的入口,沿着它的指引的道路(脚本的执行顺序),应该就可以走完整个探险之旅,最后平安回到入口。否则就有可能迷失于Drupal地心深处,等待我的只有未知的生物和无尽的黑暗了......






2009年1月6日星期二

Drupal 缓存基础

关键字:DRUPAL,缓存,API

方医生注

本文介绍了Drupal的缓存机制,主要是自定义模块开发时,如何使用缓存的问题。英文原文地址为:http://www.lullabot.com/articles/a_beginners_guide_to_caching_data。不过我在翻译时,并没有100%按照原文,进行了一些改进,使其读其来更像中文文章。如果大家觉得有不明白的地方,欢迎指出。

正文开始

在Drupal中创建复杂的、动态的内容是很easy的,但需要做出一定的牺牲的。现在很多Web 2.0的特性的加入,使得网站在变得很cool的同时,也坠入了“性能恶梦(performance nightmare)”的深渊中。每一个节点的加载或一个页面的加载,都会引起高负载、大量的数据库访问、复杂的计算和大量的客户端脚本的执行。

一个解决方案是在Drupal的管理界面开启页面缓存。这个可以使匿名用户(即不登录的情况下)访问速度增加,因为Drupal会把每一个要输出的页面预先缓存(方医生注:请注意!不是缓存成HTML页面,而是把整个页面的HTML内容序列化为字符串,然后存入数据库),这样可以大大的减少数据库的查询次数。但是,这个对于登录的用户是没有任何效果的:因为页面级的缓存是一种“要么全是要么就一点都没有(all-or-nothing)”的缓存方式,它只有在很标准的情况下才有效,并且所有用户看到的视图都是一样的。

不过,早晚有一天你需要挖掘你的代码,找出数据库的查询访问热点(hot spots),然后自己对程序进行缓存。幸运的是(我最喜欢看到Fortunately这个单词),Drupal有一些内建的关于缓存的API和一些指导性的文档,使得我们可以很easy的自定义Drupal程序的缓存。

基础

做优化和缓存的第一条(The first rule)就是:永远不要使消耗时间的操作执行两次!要充分利用第一次执行的结果,并重用它们(Never do something time consuming twice if you can hold onto the results and re-use them.)下面我们看一个简单的示例:

<?php

function my_module_function($reset = FALSE) {
  static $my_data;
  if (!isset($my_data) || $reset) {
    // 在些进行复杂的计算,并使用正确的内容生成$my_data变量
  }
  return $my_data;
}

?>

这段代码中最重要的部分,就是这个函数创建了一个静态变量——$my_data.静态变量的好处在于,当其第一次被赋值后,就可以被持续的使用,即使这个函数被重新调用。也就是说,我们先检查这个变量是否被赋值,如果已经有值了,那就不需要重新计算,只需要直接返回其值即可。

这种模式经常在Drupal中使用——其中包括很多关键的函数,如node_load。这个函数在被第一次调用时,根据传入的节点ID,查询数据库得到节点对像,并赋值于一个静态变量;然后如果这个节点再在一个Block中被调用,或被列表中调用,就不会再次查询数据库了。

另一个重要的特性是使用$reset参数。缓存确实很好,但偶尔你也需要确保用户访问的页面所得到的数据是绝对新鲜的(absolute freshest)。这时$reset变量就派上用场了。我们可以在需要更新缓存的时候,将$reset设置为TRUE。

Drpual的缓存函数

你可能注意到,静态变量只能在一个单独的页面生存周期内有效。如果需要更好的性能,我们可以使缓存的数据更持久……

<?php

function my_module_function($reset = FALSE) {
  static $my_data;
  if (!isset($my_data) || $reset) {
    if (!$reset && ($cache = cache_get('my_module_data')) && !empty($cache->data)) {
      $my_data = unserialize($cache->data);
    }
    else {
      // 处理复杂计算,生成$my_data变量
      cache_set('my_module_data', 'cache', serialize($my_data));
    }
  }
  return $my_data;
}
?>
这个版本的函数仍然使用静态变量,但是它增加了新的一个层(layer):数据缓存层。Drupal的API提供三个关键的函数你需要了解:cache_get(), cache_set()和cache_clear_all()。接下来让我们看看如何使用它们。

回到上面的函数,在对静态变量做初始检查后,这个函数就会通过一个键来查找Drupal缓存(数据库中的缓存)。如果能够查到,并且$cache->data元素是非空的,函数公反序列化缓存数据,并将其存入$my_data变量中。

如果没有缓存被找到(或如果$reset == TRUE),函数就会实际生成数据。然后它会将其序列化再存入数据库,这样以后就可以在将来被使用了。缓存的键值(key)的命名最好以你自定义模块的名称为前缀,这样便于标识。

最终的结果是什么?一个很聪明很轻便的函数可能节省很多的时间——首先检查内存中是否已有变量存在(静态变量),然后再检查数据库缓存,如果这些都没有那就重新生成。如果你查看很多针对数据库查询的模块的话,会发现很多模块都应用了这种模式。

保持最新

发生了什么呢?如果你缓存的数据过期了,并且需要重新计算呢?默认的情况下,缓存的信息将会一直存在直到某模块执行了cache_clear_all()函数,清空所有的数据。如果你的数据偶尔的被更新,那么当每次你需要做一些更改时你或许可以考虑简单的调用cache_clear_all(‘my_module_data’, ‘cache’)。如果你缓存了很多数据(比如你为一个模块存储了n个缓存,或为每个角色都做了个缓存),那么可以传入第三个参数给cache_clear_all函数,以使其全部清空:

<?php

cache_clear_all('my_module', 'cache', TRUE);

?>

上面的函数将清空所有以“my_module”为前缀的键值的缓存。

如果你不需要使你的缓存“时刻保持最新“,但又需要使其“适当的”新鲜(reasonably fresh),你可以给cache_set函数传入一个过期时间。比如:

<?php
cache_set('my_module_data', 'cache', serialize($my_data), time() + 360);
?>

最后一个参数是一个UNIX时间戳,用来表示过期时间。最简单的方法是使用time()函数,再加上这个缓存的生存时间(以秒为单位)。过期的缓存会被自动的清除(应该是通过cron函数来实现)。

高级缓存

你可能注意到了cache_set()函数的第二个参数是“cache”——这是Drupal用于存放缓存的默认的数据表的名称。如果你要存储大量的数据在缓存中,你可以设置一个自定义的单独的缓存表,并通过这个参数传递给Drupal。这样可以使缓存更快一些,因为可以与其它的模块的缓存表分开。我们熟悉的Views模块就是使用的这种模式(单独创建缓存数据表),尤其当需要清除所有缓存时,单独的缓存表可以避免删除其它模块的缓存数据。

如果你真的希望充分利用服务器的每一个资源,Drupal还提供了可以使用其它缓存系统的机制。只需要改settings.php的一行代码,你就可以使三个标准函数cache_set(), cache_get(),和cache_clear_all()函数有不同的实现。基于文件的缓存(File-based caching),与memcached集成,或使用其它的缓存系统都成为可能。而且,只要你使用了Drupal的标准的缓存函数(就是上面提到的三个),那么你不需要在你的模块中更改任何代码,就可以实现缓存系统的移植。

一些忠告

过犹不及(Like all good things, it’s possible to overdo it)。有时用缓存是不需要的——如果你只是查找一条数据的话,把它存入缓存数据表就是很愚蠢的行为了。可以使用Devel模块来定位哪些查询降低了加载的性能。它可以高亮一些查询时间较长的查询,以及那些被反复执行的查询。

在其它时间,你使用的数据可能会不适合标准的缓存系统。如果你需要在SQL查询中连接缓存数据,比如,cache_set()将会把数据字符串序列化,有时就会造成麻烦(因为不能直接使用)。在这种情形下,你需要找一个针对你的模块的缓存解决方案。VotingAPI模块维护了一个数据表用于存放每个用户的投票结果,另一个数据表用于存储计算结果(如平均值、总和等),这样可以在SQL查询时,快速的进行连接排序和过滤节点。

最后,一定要记住:缓存不是一个长久的存储方式!因为如果其它的模块调用了cache_clear_all()函数的话,那么所有的缓存数据就都会消失。因此如果你要存放某些只能计算一次的数据的话,还是找个安全点的地方放比较好。

去西部吧,年青的Drupaler!

祝贺你:你现在拥有一套加速你的代码的强有力的工具了!前进,优化你的代码吧。

后记

本文的几个示例函数比较有参考价值,大家在写自定义模块时可以参考一下。缓存对网站的性能影响是相当的大,一定要在适当的条件下使用适当的缓存机制。

2009年1月3日星期六

Drupal视频讲座第一辑—Views 2攻略

Views 2 是Drupal 6最重要的模块,它可以生成Drupal站点内容的各种列表,而且不需要写一行代码。同时,它的作者又荣获2008年度开源CMS最有价值专家。由此可见Views 2对于Drupal的重要性。我也制作了一系列关于Views 2的视频讲座,旨在与大家分享Views 2的使用经验。欢迎大家收看并与我交流。相关视频还会陆续发布,敬请关注。

Views 2:基础
本节视频主要包含了Views 2的相对于Views 1的改进,一些基本概念和配置,并创建了一个实例区块来展示区块列表。
如果你的浏览器无法看到视频,可直接访问:http://v.youku.com/v_show/id_XNjM4NjE3NzI=.html。


可能视频中的截图不是很清楚,下面是两个主要的页面,大家可以参考着看(点击看大图):



Views 2相关视频还会陆续推出....Coming Soon....