《Drupal7权威指南》

英文名:《The Definitive Guide to Drupal 7

《Drupal7权威指南》中文版。(因考虑到版权问题,译文不再发布,敬请谅解

内容概览

目录

前言

作者简介

关于技术审阅

致谢

序:什么是Drupal

Drupal 7 的新特性

如何使用本书

Drupal的工作原理

第一部分:入门

  • 第1章:建立一个Drupal 7网站
  • 第2章:必备工具:Drush和Git

第二部分:网站建设基础

  • 第3章:使用views 创建动态页面
  • 第4章:有这样的一个模块
  • 第5章:使用Organic Groups模块构建一个社区网站
  • 第6章:Drupal的安全性
  • 第7章:更新Drupal
  • 第8章:扩展你的网站

第三部分:让你的生活更轻松

  • 第9章:Drupal社区:获得帮助和参与(已完成)
  • 第10章:规划和管理Drupal项目(24% - 9月15日)
  • 第11章:最终用户和开发小组的文档
  • 第12章:开发环境
  • 第13章:把一个网站上线并部署新的功能
  • 第14章:从人类思维方式进行开发

第四部分:前端开发

  • 第15章:主题化
  • 第16章:主题化进阶
  • 第17章:jQuery

第五部分:后端开发

  • 第18章:模块开发介绍
  • 第19章:在模块中使用Drupal API
  • 第20章:优化你的模块
  • 第21章:将模块移植到Drupal 7
  • 第22章:编写项目特定的代码
  • 第23章:介绍使用Simpletest模块进行简单的功能测试
  • 第24章:编写一个主模块

第六部分:网站建设进阶篇

  • 第25章:Drupal 商务
  • 第26章:Drush
  • 第27章:优化Drupal
  • 第28章:结合语义化润色你的内容
  • 第29章:菜单系统与Drupal路径入口
  • 第30章:Hood控制:在Drupal中何时显示一个页面
  • 第31章:搜索和Apache Solr的整合
  • 第32章:用户体验
  • 第33章:完成一个网站:剩下的90%
  • 第34章:Drupal套件和安装Profiles

第七部分:Drupal社区

  • 第35章:Drupal的故事:一些突发事件链
  • 第36章:现在你身临其境:开始Drupal的生活
  • 第37章:维护一个项目
  • 第38章:回馈社区

第八部分:附录

  • 附录A:升级Drupal站点由6到7
  • 附录B:分析Drupal和性能优化
  • 附录C:页面渲染和改变
  • 附录D:Drupal的视觉设计
  • 附录E:可行性
  • 附录F:Windows开发环境
  • 附录G:在Ubuntu上安装Drupal
  • 附录H:Mac OSX上安装
  • 附录I:使用Acquia开发桌面设置Drupal环境

索引

第一部分:入门

第一部分:入门

入门

第1章 从规划开始,到给予人们发表页面和其他内容的特权,带领你全程建立一个Drupal站点,其中包括有很多关键的Drupal概念,一路下来也给出了很多技巧。在第8章和第33章中会继续创建这个网站。

第2章 介绍两个任何Drupal人员【Drupalista】生活中的基本工具:Drush—在Drupal中能更快更容易完成许多任务的Drupal框架;和Git—一个分布式版本控制系统,使你能自由地体验你的代码--并与世界各地的人们合作。

第一章

第1章
■■■
建立一个Drupal7站点

本杰明 梅兰肯 丹 哈基姆扎德 达妮 诺丁著

“好吧,我们能用艰难的方法完成这事,或者我们可以用Drupal的方法完成这事。”

                                                                                                                                     弗莱斯特 玛斯(红茶菌)

本书通过介绍使用Drupal 7创建网站的所有各个方面的内容,将会使你在Drupal的学习中少走弯路:它将会涉及结构和配置,模块开发,前台开发,持续运行项目,以及贡献Drupal的代码,文档,还有社区等等。
在第一章中还有什么比创建一个完整的站点更好的方法开始呢?在27页中,你要每小时从零点走到60英里(或每小时100公里,视情况而定)。在后面的章节中,你要使用Views【视图】的动态页面添加涡轮增压器,主题化赛车的条纹;以及jQuery奖杯的获得者;你还要执行一些商业方面花哨的练习等等。

在整本书中,我们将设法引导你以Drupal的方法做事。从来就没有只有一种方法能达到目地,但是有些方法忽视乃至反对Drupal的产品。相比之下,Drupal的方式在Drupal的优点基础上是任何方式无法相比的。(第8章讲述了其中的优点之--一个活跃的在课程中能够持续给你提供帮助的社区。)

在本章中你要创建的这个站点会允许用户轻松地创建和分类内容。方案不是假设的。本书需要一个网站,而你正在创建它!你将要:

•使用基本方法规划一个站点。
•安装Drupal 7。
•配置Drupal核心,提供一个面向协作的站点,接受作者和观众的内容和评论。
•把该站点和它的首页静态(半永久性)内容和最近更新进行组合。
•给作者和观众不同的添加和编辑内容的访问级别。

这仅仅是第一章,所以就到这儿吧!

规划:设置参数和了解你所在的位置

在开始做任何项目之前,你应该对它需要什么有些了解,如果只设置一些你要输入的参数。适当提交的关键是设置预期。(关于使用灵活的方法规划和管理方面更多的内容参见第九章)。

发现:为什么要建这个站点?
开始一个项目时,要弄清楚的第一件事情不是如何去做这件事情,而是为什么要做这件事情。发现这个目标的过程是一个项目的发现阶段,它精确地解释了本书中的介绍和第9章中进一步的论述。

■提示  虽然很明显,但是极为重要的发现阶段有时很少受到关注。即使是只为你自己建立的一个网站,也应该以确定你的目标开始。忽略这个步骤可能意味着当了解的需求发生变化和在这个过程的后期发现了新的需求时会重复进行所有其他的阶段。

 询问站点发起人(作者)有关他们对该站点透露的目标,他们希望人们获得更多有关Drupal7权威指南的知识,并且他们希望该站点可以帮助交流,并且在多个作者、读者,和感兴趣的Drupal人士【Drupalistas】之间合作。

总的来说,DefinitiveDrupal.org网站(以下简称为DGD7站点)将会补充本书的目标,其中包括以下:
 

•给人们提供各种各样大量使用Drupal的技巧背景环境。
•帮助人们学会如何更加了解他们自己。
•鼓励那些对Drupal软件感兴趣,参加社区,可能会做出软件的人们。

本书要达到的目标,要是人们购买本书它可以提供帮助,因为所有网站访问者一定能够看到有关本书的基本信息,挑选和奖励内容,以及图书购买信息。作者必须能够添加,编辑,并安排这些信息。人们必须能够对本书中包括的概念或将来的版本提出意见。以后,本书的读者必须能够发表评论或对特别章节询问问题。(这些交互的结构表单会比联系表单或站点范围论坛的作者更加合理利用。)站点必须能够扩展新的功能以及新的内容,当添加了有关Drupal重要的新信息时,游客必须能够登入收到更新。 

第二部分:网站建设基础

译者:郑黉宾

 

第三章带您透彻了解到最重要的贡献项目。

Drupal有:视图。您所建立的大部分网站将依赖该浏览模块,提供强大的内容排列,过滤和分类方式。

第四章介绍了许多其他由Drupal公司提供的模块(功能捆绑),您可以从中选择您想要的,更重要的是,如何发现和评估模块,以满足您的网站建设需要。

第五章带您了解有机组织的模块套件,人们可以在您的网站上使内容和他们自己有机化。本章包括一个面板的扩展客串,另一个显示内容特别是与浏览对应的发电站模块。

第六章是关于安全操作,从配置到评估甚至是到编写代码,提供多种方式保持网站的安全。

继安全章节后的第七章提供几种方法保持Drupal代码和贡献模块的实时更新。

继第一章之后,第八章继续网站建设,配置字段,浏览,选择贡献模块展示作者,呈现内容,连接作者与资源,并让浏览者参与网站建设。让您体验到您在Drupal能走多远,而不需要编写任何代码。

第三章带您透彻了解到最重要的贡献项目。

Drupal有:视图。您所建立的大部分网站将依赖该浏览模块,提供强大的内容排列,过滤和分类方式。

第四章介绍了许多其他由Drupal公司提供的模块(功能捆绑),您可以从中选择您想要的,更重要的是,如何发现和评估模块,以满足您的网站建设需要。

第五章带您了解有机组织的模块套件,人们可以在您的网站上使内容和他们自己有机化。本章包括一个面板的扩展客串,另一个显示内容特别是与浏览对应的发电站模块。

第六章是关于安全操作,从配置到评估甚至是到编写代码,提供多种方式保持网站的安全。

继安全章节后的第七章提供几种方法保持Drupal代码和贡献模块的实时更新。

继第一章之后,第八章继续网站建设,配置字段,浏览,选择贡献模块展示作者,呈现内容,连接作者与资源,并让浏览者参与网站建设。让您体验到您在Drupal能走多远,而不需要编写任何代码。

 

第3章 使用views创建动态页面

作者:Michelle Lauer 和 Greg Stout

译者:郑黉宾

 

视图改变了我的生活。如果你已经建立了一段时间的动态网页,有两个任务要重复执行。建立内容,并存在一个数据库里,然后将内容调出来建立网页。后面的请求常常需要复杂的公式,稍有差错就将返回错误的项目,或者更有可能,什么都没有。

视图模块让你更轻松地为显示内容子集,甚至是合并多种内容类型,制定标准。你还能得知显示数据的格式.新的内容添加到网站,由此产生的视图则是动态更新的,以反映新的内容.它可以帮助你做到这一切,而不需要编写任何代码。视图改变了我的生活,谢谢!现在是时候改变你的了!

什么是Views?

该名称来自数据库术语。数据库视图是一个复杂的存储查询,使用起来就像是数据库中的表格。当您在数据库视图里查询项目的时候,您用您需要的方式得到您需要的东西。

Drupal视图的工作方式是类似的,但是他们让您用的是一个图形用户界面去创建数据库查询。如果您建立了Drupal视图,该模块会为您编写查询,所以您根本不必知道任何相关的数据库管理。

视图模块是由迈尔斯设想,创建并维持的(drupal.org上的merlinofchaos)。所有可下载的版本、文件和列队均可在drupal.org/project/views的工程网页上找到。

该工具本质上是一种智能查询生成器,给予了足够的配置,可以建立正确的查询,执行它,就能显示结果。

撇开别的不说,视图可以用来生成报表,创建摘要,显示图像和其他内容的集合。

 

--摘自drupal.org/project/views

就像Drupal本身,视图模块提供了强大的功能。只需要点击几下,您就可以放一个板块到您的主页上,罗列网站的新内容。再点几下,您就可以将之变成一个选项卡式菜单,第一个标签显示您网页上最流行的内容,第二个便签显示最新评论,第三个则罗列最新成员。

该视图模块在动态网站上提供了一个发电机。它使您的工作(建设网站,特别是维护网站)更轻松更强大。可以很轻松地写一本关于视图的书,并不觉得无聊。

出于这些原因,本章该学到的本质不是在视图的帮助下你能做什么,或者是如果去做,而是该如何用简便的方式去做,使您维护网站起来更便利,并将责任传递给下一个人。换句话说,我希望您真正学到的是过程,标签,面熟命名约定。一旦学到这些,您将能够想象并用视图建立几乎所有的东西。

运用Views的例子

以下只是视图的普通运用:

  • 最新5篇新闻稿
  • 活动预告
  • 某人写的所有帖子(类似博客)
  • 每月存档的内容
  • 管理用的内容列表(见图3-1)

图3–1. 管理用的内容列表示例

您真的可以展示任何类型的内容,也可以引进相关的内容。如果该内容在数据库中,您可以使用试图模块来展示。

视图展示的最普遍的类型是网页和板块。网页的话,您可以指定输出到它自己的URL。板块的话,您可以将输出放在网站的任何页面的任何区域。

视图模块的下载,启用和配置权限

为了使用视图开始开发,你需要以下标准程序来下载并启用模块。

下载

访问drupal。org/project/views。往下滚动到下载部分,您可以看到名为“推荐版本”的一个绿色表题。选择与您已安装的Drupal版本(比如7。x-3。x)相匹配的格式(tar。gz或者zip),点击下载链接。

解压文件,并把它们放到贡献模块的目录里。对大部分开发者来说,这个在sites/all/modules/contrib。,或者在sites/all/modules。于是您可以在sites/all/modules/contrib/views或者sites/all/modules/views找到所有的试图文件。(Drush,第2长涉及到,可以为您下载放置文件。)

启用

在您的网站上,确保您是用有权限的账号登陆到管理员模块,或者具有管理员角色的账号(或用户/1)。使用顶部的管理菜单并点击“模块(管理/模块)”。

下拉滚动条到视图字段设置。您将看到三个模块:视图,视图输出和视图用户界面。

在视图模块定义下面,您可以看到CTools是一个视图工作所需的模块。如果您已经下载了CTools模块并在网站上启用,你可以看到“已启用”的文本。如果您已经下载了CTools,但尚未启用,文本则显示“缺失”。如果您的网站文件中不具备所有的信赖,Drupal将不允许您启用模块。

如果您还没有这么做,请从工程网页drupal。org/project/ctools下载CTools模块。解压文件并将ctools文件夹放在贡献模块目录里。对大部分开发者来说,这个在/sites/all/modules,所以您可以在sites/all/modules/ctools里找到所有CTools文件。

请注意,CTools是一个为其他模块提供辅助代码的模块。

回到模块页面(管理/模块),点击刷新。下拉滚动条到视图字段设置。CTool信任文本应该为“已禁用”。有了所有需要的文件,您现在可以启用视图。点击视图和视图用户界面的复选框,然后保存配置。

我们后面将在本章讨论视图输出。

图3-2。模块列表管理页面。所需模块已下载,但尚未启用。

Drupal知道该视图模块需要启用另一个模块并通知您。

您必须启用CTools模块来安装视图用户界面。

您希望继续吗?

请“继续”。

配置权限

Drupal所提供的功能之一是能够将权限授予不同的角色,在第1和第8张涉及到。大多数模块都与权限相关。您的网站用户不是匿名用户,就是验证用户,当然也有可能还有其他其他授予的角色。

提示启用任何一个模块后,最好马上配置权限。如果等到开发结束后才配置,会导致超量的权限审核。

在顶部的管理菜单,点击“人们”。在该页面上,点击权限标签。下拉滚动条至底部,找到视图部分。视图模块有两个权限“管理员视图”和“所有视图”。

请注意:您也可以在模块管理页面使用视图权限链接。您将直接进入权限页面的视图部分。

“管理视图”将访问视图管理页面,用户将可以创建,编辑或删除视图。请将该权限授予合适并能正确使用的用户。大部分“管理”权限只给管理员角色。

“跳过视图访问控制”是另一个必须谨慎使用的权限。对于某个特定的视图,你可以指定某些角色可以看到结果。为某一角色选择“访问所有视图”权限则可以忽视该设置。我们建议只将该权限授予合适并能正确使用的用户,比如您的网站管理员。

确认没有为验证用户和匿名用户角色选择这两种权限。

确认为管理员角色选择这两种权限。如果做了任何修改,请点击保存权限。

在开发过程中,请务必用不同的用户查看网页,确保各角色按照权限设置拥有正确的用户体验。用三种不同的浏览器打开,分别展示不同的角色,比如再火狐是管理员,Chrome是验证用户,在IE是匿名用户。您需要用不同的浏览器登录每种角色,因为浏览器在打开窗口/标签共享您已经登录的账号。

恭喜!您已经成功下载并为视图模块配置权限。您现在可以管理视图了

第三部分:让你的生活更轻松

第三部分:让你的生活更轻松

第四部分:前端开发

第15章和第16章将带你踏上Drupal主题系统的探索之旅,讲述如何打造出强大且易于维护的网站。

第17章将介绍前端开发中的另一个关键部分,利用JavaScript构建强大且易用的功能来提升用户体验,更有趣的是jQuery库。

另见附录D,其中包括为Drupal设计。

第15章:主题化

Jacine Luisi

 

Drupal的主题层,以及主题使用到的,是负责外观或者一个Drupal站点给人的感觉。你会发现一些有名的网站上优秀的主题都包含一些相同的元素,具有符合标准的XHTML标记,CSS和JavaScript。这些是如何组合在一起的?是什么如此特别,是什么让Drupal的主题如此灵活和强大呢?

Drupal主题可以依据你的需要,既可以很简单也可以很复杂。主题具有对每个页面几乎所有部分的最终的决定权和控制权。和Drupal本身一样,主题也很灵活、很强大。无可否认,充分利用Drupal的主题层意味着要克服一个相当陡峭的学习曲线,如果对引擎的内部机制不够理解的话,很容易在开始时犯一些错误。

在本章,你将学习一些关于Drupal主题层的基础知识。你将学习如何去定制和合理修改以及常见任务的最佳做法。您将用自己的方式在任何时间创建灵活和可持续定制的Drupal主题!下一章将在这个基础上创建更加深入复杂的Drupal主题。

本章和下一章的一些例子你可以在DGD7主题里找到。如果你想跟进的话,可以到这里https://github.com/jacine/dgd7去下载。

核心主题目录

开始时,人们通常做的第一件事是到核心的“/themes”目录下,浏览主题中的文件以了解它的总体结构和目录。不幸的是,很多人开始时会错误地去直接定制核心的主题。不要犯这个错误!他们通常此后不久,就会碰到障碍和挫折。Drupal有一个庞大和多样化的用户群,而Drupal核心主题的主要目标是满足大众。

除了美学,核心主题要满足很多需求和不同的使用情况。有些主题支持Color模块,以使其方便让网站管理员改变用户界面的配色方案。这不是一件坏事,但是,它会在试图定制配色(colorized)主题时很容易造成混乱和令人沮丧,因为CSS是自动存储在主题目录外面的。核心主题,还必须有这样的功能,如果作为一个管理主题的话,他们必须支持双向文本;在一般情况下,它们不能偏离Drupal的默认区域和设置。

满足所有人并不简单,而Drupal核心主题恰恰肩负着这个重任。因此核心主题不能达到它本可以具有的灵活和尖端。大多数时候,你创建定制主题的目标和方法会有很大的不同。你将能够把重点放在编写你自己的前端或后端集中设计,定制标记,决定使用哪些CSS文件(如果有的话),和其他令人兴奋的决定。

核心主题

Drupal核心包含4个主题。它们将会在接下来的小节中介绍。

Bartik

Bartik是Drupal7的一位受欢迎的新成员,Drupal安装后将Bartik设为默认的面向用户的主题。这是一个干净、简单的主题,支持Color模块,较好地使用区域(见图15-1)。除了Drupal默认推荐的区域,Bartik还增加了7个区域,用于放置页脚和页脚分块的区块。

图15-1. Bartik是一个干净、简单的主题

Garland

Garland最初是在Drupal5中作为核心主题首次亮相。它是一个更复杂的主题,具有良好的color模块的支持(见图15-2)。它包含15个配色方案,并提供了一个选项,以固定或流动的布局之间切换。

图15-2. Garland是一个更复杂的主题,具有良好的color模块的支持

Seven

Seven也是Drupal7的新成员,同时也是Drupal管理界面的主题。Seven来自Drupal7用户体验项目(http://d7ux.org),它承载着许多用户体验上的改进。它包含了少量的区域,而把重点放在执行管理任务上。

图15-3. Seven是Drupal默认管理界面的主题。

Stark

Stark是一个独特的,轻量级的Drupal主题(见图15-4)。其主要目的是展示出Drupal的默认HTML标记和CSS。它不提供任何模板文件,除了默认边栏区域的基本布局样式,几乎没有提供任何CSS。别让它的简单欺骗你,它实际上是非常有用的。对开发人员来说,在为他们的模块编写CSS代码时,Stark是一个完美的主题。它也能够帮助开发者解决不确定问题是否出在主题上还是另一个模块上。

图15-4. Stark是一个独特的,的确很微小的Drupal主题。

主题引擎

Drupal的主题目录中也包含一个“engines”目录,其中包含一个叫做PHPTemplate的主题引擎。主题引擎提供了一种方式用来分离主题化输出模板文件如同原始的PHP一样简单。使用PHPTemplate引擎的主要好处是,简化了从表现中分离逻辑。这对那些不熟悉PHP语言的人将大有作为,因为他们都能够在主要包含标记和打印变量的模板文件下工作。

虽然其他主题引擎如Smarty、XTemplate和PHPTal可能被使用,PHPTemplate是Drupal的默认主题引擎而且也是迄今为止Drupal主题用过的最流行的主题引擎(许多流行的贡献模块也在使用),所以在本章中我们将会包含该内容。当然,也可以做纯PHP的Drupal主题。例如,变色龙(Chameleon)主题就是一个纯PHP的主题,http://drupal.org/project/chameleon。想获知更多可用的主题引擎完整列表,请访问http://drupal.org/project/theme+engines。

主题管理

主题设置任务位于Drupal管理中的“外观”(Appearance)一栏。这里就是你可以控制一些事情,如哪些主题打算开启还是禁用,你想申请哪些设置,你想选择哪种配色方案(如果你的主题支持color模块的话),等等。

开启和设置一个默认主题

在一个全新安装的Drupal7中,默认的主题(Bartik)出现在“外观”页的顶部,其次是启用和禁用的其他主题(见图15-5)。什么是默认主题?对Drupal来说,仅仅开启一个主题是不够的。设置一个主题作为默认主题就是让它成为前台主题(也就是你的站点访问者可以看到的主题)。

图15-5. 默认安装下外观页面展示开启的主题。

当你想一次同时利用多个主题时,开启一个主题而不设为默认主题是很有用的。当此设置与贡献模块一起使用时是更有益的。这样的例子如SwitchTheme模块(http://drupal.org/project/switchtheme),它允许用户从所有启用的主题名称列表中切换主题。

管理主题

在Drupal7中,Seven主题是默认的管理主题。管理主题通常用在执行管理任务时,其中大部分是发生在“/admin”路径下。你也可以选择允许在编辑站点内容时用管理主题。虽然有些主题比其他的对Drupal管理界面支持的更好,但是如果需要的话所有的主题都可以用来作为管理的主题。

管理主题的配置位于“admin/appearance”页面主题列表的下面。如果想同时在前后台都用一个主题,只要将默认的主题设置为管理主题即可。

全局主题设置

Drupal自带的一些主题设置,可以在管理界面配置。这是大部分网站定义个性化的地方,以及一些其他设置。全局设置页位于“admin/appearance/settings”路径下包含这些设置。当个别主题的设置页面上应用主题设置时,它们将覆盖全局设置。以下章节将详细介绍这些,以及在你的主题什么地方会碰到。

其中一些设置确定是否将位于模板文件中的变量因此输出出来。在图15-6中描绘的设置代表Drupal提供的默认值。这些都可以通过定义主题中的info文件里定义features来覆写,这将在“定义主题元数据”一节中进一步讨论。当在info指定一些功能时,你需要确保包含了所有你将要支持的功能,如只是一个的话,将会覆盖所有Drupal提供的默认值。以下是他们会在info文件中一些设置的快速参考:

features[] = logo
features[] = name
features[] = slogan
features[] = favicon
features[] = main_menu
features[] = secondary_menu
features[] = node_user_picture
features[] = comment_user_picture
features[] = comment_user_verification

图15-6. 全局设置页面

Logo

默认情况下,Drupal会在主题的根目录下寻找一个名为logo.png的文件。另外也有一个选项可以指定一个不同文件的路径作为logo,也可以上传一个logo。当Logo复选框选中时,一个叫做$logo的变量,它的路径将会输出在page.tpl.php里,若没有选中,logo将不会输出。

名字和口号

网站的名字是在安装过程中定义的。站点名字和口号都可以在“admin/config/system/site-information”页面进行修改。在主题设置页,你可以切换他们是否可见。两者都可用在page.tpl.php中,分别为$site_name和$site_slogan。

快捷图标

快捷图标,也称为favicon,是一个Drupal的小图标,出现在地址栏、书签和大多数浏览器的标签。和标志一样,快捷图标可以切换是否可见,也可用自定义的文件。默认的文件是“misc/ favicon.ico”。

在发布和评论中的用户图片

这些设置控制变量$user_picture和变量$picture是否分别在node.tpl.php和comment.tpl.php里输出,从而决定当查看节点和评论时图片是否显示。

评论中的用户验证状态

当用户没有一个验证的账号时,此项将显示在用户名字的旁边“(未验证)”。该文本是在template_preprocess_username()函数中定义,作为变量$variables[‘extra’]打印在theme_username()函数中。见“预处理和处理函数”和“主题函数”部分,了解如何改变它。

主菜单和次级菜单

当主菜单和次级菜单的复选框被选中时,变量$main_menu 和$secondary_menu就会以数组形式包含每个菜单的菜单链接在page.tpl.php中输出。在位于“admin/structure/menu/settings”菜单设置页,你可以选择每个用于哪个菜单。默认情况下,主菜单是用作填充$main_menu源的,可以通过在“admin/structure/menu/manage/main-menu”页来管理。默认的次级菜单源用作用户菜单,可以在“admin/structure/menu/manage/user-menu”页来管理。

这些都是在page.tpl.php中使用theme_links()函数(将在本章稍后介绍)简单的单级菜单输出。

这使得他们很难样式复杂的导航设计时使用。因为往往需要复杂的导航的,很多主题开发人员为导航创建区域和使用的区块来输出菜单,而不是使用这些菜单。我们强烈推荐Menu Block模块(http://drupal.org/project/menu_block),它允许你很容易地去使用菜单做几乎任何东西。

自定义主题设置

自定义主题设置与全局主题设置很相似,它可以由主题或者模块来提供。一个自定义主题设置的例子可以在Garland主题中的garland.info文件里看到。它创建了一个叫做“garland_width”的可以用来设置固定或者流体的设置。快捷方式模块还提供了设置在Seven主题里的遮罩层内的标题旁边提供小图标以显示“添加或删除快捷方式链接”。要学习如何为您的主题创建自定义主题设置,请访问http://drupal.org/node/177868。

安装一个新主题

Drupal会到它的主题目录去扫描可用的主题,因此将你的主题放在正确的目录下,这样Drupal才能识别它。你也有可能将新增的主题放置到Drupal的“/themes”目录下,但从技术上来讲,这被认为是“破坏核心”,并且应当避免。在下载和解压你的主题之后,选择以下哪个目录来放置主题。使用其中任何一个目录都可以帮你确保Drupal自身的任何升级不会覆盖你的主题。

  • sites/all/themes:当你想为你的Drupal中的所有站点安装使用这个目录的主题时。
  • sites/sitename/themes:提供给你的Drupal多站点安装在特定的网站上时。

您也可以使用主题安装程序,点击“外观”页的顶部安装新的主题链接,下载并安装来自贡献的主题。这将使你在这里您可以进入链接到项目下载的压缩包的位置,并单击“安装”。主题安装程序会自动下载你的主题,并放置在“sites/all/themes”目录。一旦完成,您可以像往常一样在“admin/admin/appearance”页上来启用主题。

与区域一起工作

在Drupal页面中所见到的大多数内容是输出在一个区域里的。典型的区域包括header、footer、 sidebar以及content(见图15-8);这些区域往往在定制高级的HTML标记结构时经常用到。在“admin/structure /block”页,区块中有一个选项可以设置显示到每个区域,允许网站的管理员控制和放置区块显示在里面。

图15-8. Bartik主题的区域以及在区块管理页面中的区块放置选项。

在区域的自定义和处理输出和美化上,主题具有充分的控制权。比如,在图15-9就给出了在Bartik主题里关于这方面的一个例子。

图15-9 Bartik区域里填充着定义的区块。

另外,还有一个不太显著的用途,主题有可能也会利用区域与JavaScript或者jQuery配合。常见的用例,包括包含模式或者隐藏某些内容来提升用户体验,或者将区块嵌入到节点内容里。

默认区域

默认情况下,Drupal核心通过程序为主题定义了9个区域。在清单15-2中的代码复制出了以“.info”文件格式默认区域。和大多数的主体层实现原理一样,主题定义区域的原因是因为他们想要修改或者添加到默认值。除非某个主题定义了它自己的区域,否则Drupal将使用默认的区域。这意味着,假如默认的区域足以满足你的设计,那么你将不需要在你的“.info”文件里定义区域。

清单15-2 Drupal的9个按时间顺序排列的预定义的主题区域

regions[page_top] = Page Top
regions[header] = Header
regions[highlighted] = Highlighted
regions[help] = Help
regions[content] = Content
regions[sidebar_first] = Sidebar First
regions[sidebar_second] = Sidebar Second
regions[footer] = Footer
regions[page_bottom] = Page Bottom

然而,在主题的“.info”文件开头包含这段代码是个不错的做法。倘若你仅定义一个区域的话,它将会覆盖核心默认的,因此保持默认区域的完整列表,注释掉已禁用的区域(而不是完全删除或者忽略)是一个很好的方法,可以跟进将用它们来做什么。其中的一些区域可能会需要,比如:page_top,content,page_bottom这些区域。对于正常运作的网站,这些是必需的且必须在每一个Drupal主题输出的。一个如何组织“.info”文件中的区域的例子,且考虑到默认设置,如清单15-3 所示。

清单15-3. 一个在“.info”文件中实现区域的例子

; 核心区域 – 禁用的
;regions[highlighted] = Highlighted
;regions[help] = Help
;regions[header] = Header
;regions[footer] = Footer
; 核心区域 – 需要的
regions[page_top] = Page Top
regions[content] = Content
regions[page_bottom] = Page Bottom
; CORE REGIONS
regions[sidebar_first] = Sidebar First
regions[sidebar_second] = Sidebar Second
; CUSTOM REGIONS
regions[my_custom_region] = My Custom Region

提示:如果对Drupal在哪里定义默认区域感兴趣,不妨看看“_system_rebuild_theme_data()”函数,地址在http://api.drupal.org/_system_rebuild_theme_data

如图15-10所示,展示的Drupal默认区域是一个标准的三栏布局。灰色的区域是必须的,而其余的是可选的。Header、sidebar_first、sidebar_second、footer是布局方面的区域。page_top和page_bottom是特殊区域;它们将在本章的“隐藏区域”部分讨论。

图15-10. Drupal的默认布局区域

Highlighted(高亮)区域取代了过去的站点任务(Mission),它过去是一个包含站点任务状态的静态变量,或者是一个在page.tpl.php中手动输出简短的摘要文本。过去实施不够理想有多方面原因,但是最主要是因为仅显示在首页。所以说,使用自定义的区块来显示此信息是一个更好的选择,因此创建了highlighted区域。

Help(帮助)区域曾经也是page.tpl.php中的变量,用于输出错误和状态信息。现在状态信息放在一个称为“系统帮助”的区块中,同时Help区域用于放置它。但是“系统帮助”区块可以很容易地放在内容区域中,权重设置高于内容区块也能达到同样的效果。

内容区域是Drupal7新增的。据介绍,包含页面主要内容的区块,有一点比较特殊,就是它可以在区域之间切换,但是不能被禁用。

定义主题元数据(.info文件)

.info文件(读作“点info文件”)包含关于你的主题的元数据,例如,主题的名字,Drupal支持的哪个版本,以及一些如样式表和主题将包括的区域等。书写info文件通常是是创建一个主题的第一步。

文件的第一部分名字通常是机器识别的主题的名称,它是Drupal用来存储关于你的主题到数据库的信息。破折号和其它特殊字符是不允许的。虽然下划线是被允许的,但是通常认为最佳的做法是避免使用它们命名你的info文件。使用themename.info而不是theme_name.info。当进行主题函数覆写时,这个名字也用于函数名字的前缀。当覆写theme_menu_link()函数时,比如,当试图确定覆写正在被执行时,函数名themename_menu_link()通常比theme_name_menu_link()更容易阅读。

注意:你的主题(机器)名字必须唯一的。不要让你的名字与已有的模块重名,因为这样有可能会导致命名空间问题和增加追踪下载PHP错误的难度。

每个主题依赖主题的info文件中的一些基本属性。名字、核心和引擎属性都是Drupal主题最起码的要求。下面章节将包含每个可选属性的概述,并且带有语法说明。

提示:为了给info文件添加注释,在每一行的开头添加一个分号,像这样:

;这是一条注释。注释非常好。经常去使用它。

必要属性

Core:Drupal将仅当你的核心设置为支持当前的Drupal主版本时,才允许你的主题可以被开启。主版本仅仅是6.x,7.x或者8.x等。

core = 7.x

Name:人类识别的主题名字。它不需要与机器识别的名字一致或者相似,这里可以自由创建。

附加属性

Base theme(基主题):Drupal允许主题间设立一点关系。创建一个子主题,它可以继承基主题(详见下一章)的功能和优点。创建子主题时,需要指定基主题。在这里用的基主题的机器名很重要。

base theme = themename(主题名称)

Description(描述):是指主题的基本功能或用途需要在此说明。描述将被显示在“admin/appearance”页面,描述也可以包含HTML。

description = The description of my theme(我的主题描述)

Engnine(引擎):指定主题引擎。PHPTemplate是默认和最常用的,所以除非你想改变它,没有必要去手动设置。其他选项包括一个纯PHP主题的醒目和主题(比如,到http://drupal.org/project/chameleon看一下Chameleon主题)。

engine = phptemplate

Features(功能):设置功能是覆写Drupal全局主题设置的一种方法。下面是Drupal默认主题设置的一个列表。这些设置可以在每个主题设置页面的主题界面上进行开关的切换。甚至指定禁用Drupal默认的而使用你的。

features[] = logo
features[] = favicon
features[] = name
features[] = slogan
features[] = node_user_picture
features[] = comment_user_picture
features[] = comment_user_verification
features[] = main_menu
features[] = secondary_menu

PHP:Drupal 7支持PHP 5.2.5版,默认情况下你的主题也是如此。这一点,你可能永远不会需要,但万一你的主题包含只有一定PHP版本的代码,你可以在这里指定。

php = 5.3

Regions(区域):区域是页面用来放置内容(区块)的地方。每一条都是以“regions”为前缀,括号中为区域的系统名称,最后以人类可读得名称作为值。例如:regions[system_name] = Human readable name(人类可读的名字)。默认的区域如下:

regions[page_top] = Page Top
regions[header] = Header
regions[highlighted] = Highlighted
regions[help] = Help
regions[content] = Content
regions[sidebar_first] = Sidebar First
regions[sidebar_second] = Sidebar Second
regions[footer] = Footer
regions[page_bottom] = Page Bottom

Settings(设置):设置属性是用来储存主题中的自定义实现的。Garland主题提供了设置站点管理员可以选择布局的类型(固定或者流式)。虽然我们不会包含自定义主题的设置,但是我们强烈建议查看Omega(http://drupal.org/project/omega)和Fusion(http://drupal.org/project/fusion)主题,来了解主题设置如何使用的。更多信息,请查看http://drupal.org/node/177868。

settings[garland_width] = fluid

Screenshot(截图):Drupal会自动在主题根目录下查找叫做“screenshot.png”的文件,所以此行只有在你需要使用一个主题截图替代路径或者文件名字时才是必须的。截图的推荐推荐尺寸是294 x 219像素。

sceenshot = screenshot.png

Stylesheets:Drupal 7 中有相当多的方式来加入CSS文件。如果你想在每个页面都加载CSS文件的话,你可以在主题的info文件中添加css。在下一章中的“管理CSS文件”部分将会详细介绍。

stylesheets[screen][] = path/to/screen-stylesheet.css
stylesheets[print][] = path/to/print-stylesheet.css

Scripts:JavaScript文件可以在info文件中使用scripts属性来定义。和样式表相似,当你需要在每个页面都加载某些JavaScript文件时。

scripts[] = path/to/script.js

version:贡献的主题和模块都是不提倡定义版本的。这是因为drupal.org有一个包装脚本,在发行新版本时,用来详细控制添加版本。当然,如果需要的话,你可以在自定义的主题中使用它。

version = 7.x-1.1

现在我们就通过实例看一下你的DGD7主题下初步的样子,如列表15-1中所示。

列表15-1。DGD7主题info文件的顶端部分

name = DGD7 Theme

description = A theme written for The Definitive Guide to Drupal 7 book website.

core = 7.x

核心的属性除外,所有上述的都可以在“admin/appearance”页面的用户界面中看到,如图15-7所示。这是你展开主题所需要的。

图15-7. DGD7主题显示在主题列表页admin/appearance上。

创建你的第一个主题!

根据到目前为止所学到的,创建一个自定义的主题:

1. 通过在“sites/all/themes”目录下创建一个名为dgd7的文件夹开始。

2. 在dgd7文件夹下,创建一个名为dgd7.info的文件,同时将加入以下代码:

name = DGD7 Theme
description = A theme written for The Definitive Guide to Drupal 7 book website.
core = 7.x

3. 从本章的源代码文件中取得screenshot.png并复制到dgd7文件夹下。这一步是可选的。如果没有定义截图,将会以“没有截图”来代替。

4. 现在访问并重新加载admin/appearance页面,你将看到该主题位于“禁用的主题”之列。点击“启用并设为默认”链接开始在你的站点上使用该主题。

提示:你需要清除你的网站的缓存来让info文件中的改动生效!若想清除网站的缓存,请查看位于“admin/config/development/performance”的性能页面。

第五部分:后端开发

第18,19和20章针对同一个问题,可以将它们看作是一个大的章节,其中涵盖了所有在你开始写自己的模块时应该了解的内容。

第21章介绍如何将Drupal6的模块升级到Drupal7,这也是学习模块开发的一种非常好的方法。

第22章提供了编写模块的另一种好方法—“粘贴代码”,或者为站点定制的模块,实现你无法通过配置来实现的功能。你可以不用先阅读之前的章节而直接阅读本章。

第23章介绍如何为你的模块编写测试代码,这是编写可靠且易扩展代码的重要一环。

第24章介绍了API模块的概念,并深入介绍了编写功能代码时所应用到的一些Drupal开发机制。

第18章 模块开发介绍

作者:Benjamin Melançon

到现在为止,我们知道Drupal是一个功能强大的模块化系统。事实上,Drupal的强大正是源于它的模块,基于Drupal与模块之间的交互来实现一些很棒的事情。

那如何向你原有的功能中也注入这股强大的动力呢?你可以编写一个模块。你需要做的就是创建2个文件。一个文件向Drupal提供模块信息;它不是代码文件。第二个文件最少可以只包含3行代码。在本章的第一节,你将会创建这2个文件,从而创建一个可用的模块。制作模块是任何人都可以做的事情。在开发中需要遵守很多规则(大多很简单),使用很多工具,以及进行很多探索。对每个人来说,开发一个Drupal模块的过程,同时也是学习的过程。

本章是模块开发的概述,第19章和第20章将着手进行开发。这章包含以下内容:

  • 介绍模块的基础知识以及如何通过使用钩子系统来完成几乎所有的工作,包括扩展模块甚至是更改Drupal。
  • 大致介绍开发一个模块所用到的技术,包括基本的PHP以及Drupal的编码规范。

一个很简单的模块

在本节中,你将先快速浏览一个小模块,然后我们将再回来详细分析。在第19章的最后,我们将完成这个模块的功能,这个模块将帮助站点的架设者和模块的开发人员侦察站点。这个模块的用意,是让他们看到站点的骨架,所以这个模块被命名为X-ray(X光透视模块)。这个模块将取得站点中表单的ID,并将ID显示在表单上方。

模块文件夹下的两个文件

模块文件夹下的两个文件

最简单的模块可以仅由放于同一目录的两个文件组成:一个用于提供模块信息,另一个包含代码(该模块可以做什么的指令)。模块信息文件的文件名由模块名加上“.info”后缀构成,代码文件的文件名则是以模块名加上“.module”后缀。可以给模块起任何易读的名字,但一开始需要确定的是模块的机器名:机器名只能使用小写的字母和数字,不能有空格和特殊字符。你可以将这个名称字用于模块文件夹、模块文件名以及代码中的函数名。所以就这个例子而言,X-ray模块的机器名是“xray”,模块文件夹的名称为“xray”,其中的两个文件分别为xray.info和xray.module。这两个文件分别定义于清单18-1和清单18-2,稍后我们将详细介绍这些内容。

清单 18-1 xray.info文件

name = X-ray
description = Shows internal structures and connections of the web site.
core = 7.x

清单18–2. xray.module文件, 包含注释内容(/**”和“*/”之间的文本)

<?php
/**
* @file
* Helps site builders and module developers investigate a site.
*/
/**
* Implements hook_form_alter() to show each form's identifier.
*/
function xray_form_alter(&$form, &$form_state, $form_id) {
  $form['xray_display_form_id'] = array(
    '#type' => 'item',
    '#title' => t('Form ID'),
    '#markup' => $form_id,
    '#weight' => -100,
  );
}

现在你知道你能制作一个模块了。本节结束时,你就会知道这半张纸的代码的涵义了。该模块的使用方法,与使用其它人编写的模块的方法一样:将模块文件放到Drupal能够查找到的目录下,然后启用这个模块。在你的开发站点中,将xray文件夹放入存放模块的文件夹中,例如sites/all/modules/custom(可根据需要自行创建custom文件夹)。然后通过浏览器访问你的开发站点,访问“管理”> “模块”(admin/modules)页并开启X-ray模块。(当然你也可以通过Drush来启用它,但是第一次启用一个自己编写的模块,能在模块管理页面看到并手动启用它,感觉会很不错的)。 启用X-ray模块后,它就立即生效了。你可以马上看到模块管理页面的变化:X-ray模块修改网站中的表单,让它们能够显示自己的表单ID;现在你知道,模块管理页面是由一个很大的表单组成,它其实是由system_modules()函数提供的(如图18-1所示)。

图18-1 在模块管理页面上方显示出表单ID“system_modules”(同时也是生成这个表单函数的名称)

这还不是一个最令人兴奋的模块,尽管它只用了几行代码来使网站产生变化,但它同样是一个完整的模块。像专属领域中的Drupal忍者那样进行模块开发的神话已经被打破了。你可以带着信心前进,通过自己开发模块,逐步丰富构建你的Drupal网站所需要的知识。

注意:不要陷入在试图用你的第一个模块去完成一些独特的、令人敬畏的或者特别有用的功能。创建一个模块,是为了学习和锻炼,而不是必须去实现一些之前没有做过的事情。所有的一切对你来说都是新的。第20章将会教你制作一些既简单又实用的模块。

当然,这还是有点令人兴奋的。只需少量的代码,就可以给网站中的每个表单都添加一些信息!这是怎么做到的?通过详细的分析以及结合大量的背景知识,让我们慢慢回顾一下。

何处放置自定义模块

你的模块包含在自身的文件夹中,可以放在Drupal会查找到模块的任何位置,就像你从drupal.org上获取的模块一样。但是,你应该把它放在哪里呢?

你知道你想将它放在sites文件夹中的某个位置,因为你对Drupal发行包所做的每一项自定义的修改都发生在sites目录下。本章遵循将自定义的模块放在“sites/all/modules/custom”里的做法,在第一次放置模块之前,你需要创建这个目录。

注意:当你像第34章中介绍的那样,开始制作自己的发行包时,你可以将模块放置于安装配置文件的目录下。安装配置文件“example_profile”所用到的模块,会被放置在“profiles/example_profile/modules”目录下。

从drupal.org下载的每个模块都可以放到“sites/all/modules/contrib”目录下(正如第4章中关于手动放置模块和通过drush命令下载模块的介绍,一旦你创建了“sites/all/modules/contrib”目录,Drush会自动将从drupal.org下载的模块放置进去)。

另一方面,自定义模块可以放在“sites/all/modules/custom”目录,许多开发人员习惯将自定义模块放在“sites/default/modules/custom”目录,而将第三方模块放在“sites/all/modules”目录。如果不采用Drupal 的多站点机制,这个放置模块倒也没什么不好。(多站点是指通过在“sites”目录下放置另外的文件夹,仅安装一个Drupal来构建多个网站。通常情况下,安装多个Drupal也能够用得很好,并且与使用Drupal的多站点机制相比,前者会更好。然而,面对部署许多Drupal站点的情况,如Aegir(aegirproject.org),安装多个Drupal站点将是份非常繁重且艰辛的工作。有关建立Drupal多站点的说明,请查看任意Drupal安装包根目录下的INSTALL.TXT文件。)

表18-1列出了推荐放置自定义模块的目录。

注意:Drupal在目录中查找模块时,它将非常认真仔细:它将会挖掘目录下的所有子目录,并找出所有可用的模块。因此,如果你将John Albin Wilkin 的Bad Judgment 模块放在“sites/all/modules/contrib/experiment/set_a/johnalbin/amusements/bad_judgment”目录下,当Drupal在“sites/all/modules”目录下查找模块时,它也能找到Bad Judgment模块。但是这并不是说你应该把它或任意其他的模块放在那里。(包含多个模块的模块项目必须使用此功能,以便Drupal能在它们的项目文件夹里找到附带的模块,有的模块会将子模块放在子目录或两级子目录中,如是核心的Field模块就使用这种形式组织子模块。)

18-1. 自定义模块的推荐位置

目录

用途

sites/all/modules/custom/

适用于单站点或应用了多站点机制下的所有站点。

sites/example.com/modules/custom/

适用于多站点机制下的特定的一个站点,如example.com。

sites/default/modules/

适用于单站点放置自定义模块。

 

你将在本地开发站点上开发X-ray模块,(你这样一个的本地站点也称为沙盒站点,它只用于开发测试,而不会发布上线。)

提示:要开发你的模块,你的计算机上应该有一个运行的Drupal站点。如果你还没有,请参阅第12章设置你的开发环境,参阅附录F(Windows),G(Linux)的H(苹果机),或I(跨平台Drupal的Acquia栈安装包)使本地可以运行网页。另外,一些开发者通过SSH或FTP在远程开发服务器上进行工作。

使用命令行                              

你可以使用计算机的图形用户界面(GUI),也可以使用命令行。首先是创建一个“modules”的文件夹,然后里面创建“xray”文件夹,接着打开文本编辑器创建xray.info文件,接着就可以使用命令写入整个文件了。(使用“mkdir”命令创建目录和使用“cd”命令用于切换目录的用法后面将会提到,关于vi 编辑器的命令介绍请查看在线附录:dgd7.org/vi。)

使用命令行是你学习Web开发时可以掌握的特别实用的技能,因为它可以让你查看和编辑服务器(通常是Linux和没有图像用户界面的系统)上的文件。(即便可以,也不要直接编辑在线站点上的文件和代码!)

我想说是:“不要害怕命令行”,但正因为命令行的操作不能被撤消,所以从某种程度上来讲它会让人害怕——因此,这也就是我们为什么会使用版本控制的原因了。就最低的期望来讲:命令行进行Web开发时为你提供了大量实用的、强大的、方便的工具。从Linux的终端,到Mac OS X 中的Termial.app 应用,这些地方都能够使用命令行。

尽管你可以使用带图形界面的文件管理工具(如Mac OS X的Finder或微软Windows资源管理器)来创建文件夹,但是本章将介绍如何使用命令行来做这个事情(见清单18-3)。这将有助于使您更好、更快地开发,而且也可以培养良好的习惯。

清单18-3 通过以下命令来创建“xray”目录和它的父级目录“modules”,然后切换到xray目录下

mkdir -p sites/default/modules/xray

cd sites/default/modules/xray

.INFO文件

该文件只向Drupal提供你的模块信息,但其中仍然有很多东西值得一看。一个“.info”文件就像对Drupal说“嘿,这有个可以玩的东西。”除非模块被启用,不然Drupal只会读取“.info”文件而忽略模块中的其它东西,直到模块启用为止。对于未启用模块,Drupal模块管理页面(admin/modules)显示的信息,也都是来自“.info”文件。(一旦某个指定的模块被启用,由此模块的代码所定义的帮助、权限、以及配置链接,将会在适当的页面显示出来)。

基本的“.info”指令

.info文件的内容是比较简单和公式化的。我将在接下来的几页讨论许多常用的指令,同时,所有的这些指令都能够在drupal.org/node/542202上找到。以下是一个内容最简单的“.info”文件示例并在其中进行了自我描述假设可以在machine_name.info中发现:

name = Human-readable name of our module
description = Describes what our module does in a sentence or two.
core = 7.x

.info 文件还可以有其它的值,但必需的值只有以上三个。.info 文件内容的语法十分简单:标签=值。它总是由标签(或名称)和值组成,中间用空格隔开,一个等号,再上一个空格。例如,最后的那个指令(或属性),“core”是标签,而“7.x”是它的值。

注意:Drupal 7中,不需要写入“$ID$”。drupal.org之前使用旧的版本控制系统——CVS——需要每一个放置于cvs.drupal.org文件的顶部都包含一个$ID$的注释,当有人再次提交时则会将它替换成提交时间和提交者的名字。现在使用的Git和git.drupal.org,并不需要这个标签,并且Git仍然知道谁在什么时候提交了什么内容。

要使模块在“模块管理”上可用,模块的人类可读名称是必须选中的(然后才能启用)。没有指令用来设置模块的机器名,因为它会使用“.info”文件的文件名,所以从技术上来讲机器名并不是必须的,不过为模块提供description(描述)是模块开发者最基本的礼貌。”core”(内核版本)指令是必须被设置为“7.x”,否则Drupal 7会拒绝使用这个模块。(目前,Drupal不允许模块指定使用特定的小版本,如7.8,但你也可以这样做,不过是通过声明你的模块依赖于某个特定版本的核心模块。马上就将介绍依赖性指令。)

dependencies[]

.info 文件中最常见的一个可选指令就是dependencies[](依赖性)指令,其中列出了你的模块所依赖的所有模块的系统名称。如果你想让前面的例子依赖于Views模块,你可以在“.info”文件里添加这样一行:dependencies[] = views。

你应该只列出直接的依赖关系。例如,Views依赖于CTools,但如果你的模块直接使用CTools,你应该只在你的模块中列出CTools。这可以帮助你避免列出无效(过时)的依赖性。同样的道理,如果你改变了你的模块,使它不再依赖于其它模块,请删除依赖性指令,这样网站建设者就不必再强制安装额外的模块了。

方括号是干什么的?当一个指令可以有多个值时,名称采用数组表示法,在指令后加上[],使它在必要时可以重复使用多次。因此,当有一个既依赖核心的Help(帮助)模块又依赖于贡献的Views模块,它将要重复使用两次dependencies[] 指令,如清单18-4所示。

注意:在Drupal 7中,每个依赖性必须单独列为一行,为你的模块的每个依赖性重复一次dependencies[] = system_name

清单18-4。一个依赖于其他两个模块的模块的“.info”文件

; Require the core Help module and the contrib Views module to be enabled.
dependencies[] = help
dependencies[] = views

清单18-4中的第一行是注释。在.info文件中,使用分号(;)开头的一行会被视为注释。所以“.info”文件中任何以分号开头的行,都将被Drupal忽略。模块的“.info”文件中的注释并不是必须的,因为“.info”文件是如此简单和不言自明。清单18-4接下来的两行是两个依赖关系,使用了Help模块和Views模块的机器名。(请记住,机器名可以与人类可读的名称有很大不同。例如,Views Bulk Operations模块的机器名是vbo。)

创建你的模块版本库

让你的模块可以使用,以及在创建模块的所有流程都与这一步无关。第2章介绍了使用Git进行版本控制以及第14章阐述了它在实现对处于流动状态的开发人员的好处:你可以随意尝试任何操作,而且总是能知道你可以恢复到某个工作状态。在开发模块时使用源代码版本控制技术,就可以时刻提交你的修改,而不怕改错什么。

从你正在开发的模块的根目录(在此案例中,即xray目录;在我的电脑上,该目录位于~/code/dgd7/web/sites/all/modules/custom/xray),初始化一个Git仓库。当你创建了自己的第一个文件后,就可立即进行第一次提交,像这样:

it init

注意:即使你的模块已经在某个进行着版本控制的网站项目之中 ,你依然可以在你的模块目录下创建一个仓库,。这将让你的模块从你的网站上分离并与全世界分享您的模块。

初始化仓库后,添加和提交你在模块目录中所做的更改。你将不断地重复下面的步骤,每当你中途需要暂停或休息的时候,你将不断重复以下的工作,这样才能确保你可以回到这个模块开发旅程中的任何一步。

git add .
git commit -m "Basic xray.info and .module files."

在第14章,最高产的Drupal核心贡献者之一的Károly Négyesi说:“不用担心提交信息说了什么。最重要的是高效自如地去记录你的所有更改。(我经常提交,不过我并不遵循这种做法。xray开发过程中的每一次提交信息都写得非常清楚,这些你都可以在drupal.org/node/953650/commits上看到。)

版本控制的另一个好处是,你现在可以轻松地与全世界分享你的工作。第37章将介绍将你的模块链接成drupal.org的沙盒项目,这样任何人都可以尝试你的杰作。第37章中也有很多很多关于使用Git来跟踪修改,共享代码,以及与其他开发者协同工作的内容。

第19章在模块里使用Drupal API

 

19在模块里使用Drupal API

by Benjamin Melancon

 

制作模块的游戏规则是使用Drupal提供的工具来开发。API代表应用程序编程接口,是一种有趣的方式,它使用已经清楚地定义代码跟用户代码进行交互。本章着重讲解APIs,即Drupal提供给你的钩子(hooks)和函数(function)来构建18章中介绍的X-ray模块。由于模块不同功能特性的需要,有时我们需要使用其他扩展工具(API工具箱以外的),我将会介绍给大家并使用在项目中。

在写本章内容时,Drupal核心提供了251个钩子(hooks)。本章介绍了一些最常用的。钩子(hooks)虽然星光闪耀,但必须作为合奏队的一部分出现在舞台中。你有神奇优秀的实用功能函数的支持。这些功能也是Drupal  API的一部分。

本章所做的模块是粗略基于佐伊.奥尼尔圣.克莱尔在贡献模块想法小组(groups.drupal.org/contributed-module-ideas)的建议。她提出了一个模块,实现一Drupal站点的技术摘要,展现内容类型和解释在Drupal中产生的每个页面之间的关系。你还不知道如何做到这一点,但你知道这是可以做到的,其他细节我们马上一并填充。

在本章中,你将会看到使用Drupal提供的钩子和函数功能的示例和说明。这些都包含在建立一个完整的模块的过程中,包括以下内容:

*     表单的修改。

*     本地化(提供一个可翻译的用户界面)。

*     模块主题化和样式化你的模块。

*     使用hook_menu()建立页面。

*     使用和定义权限

*     使用的数据库抽象层检索和存储数据。

第六部分:网站建设进阶篇

第六部分:网站建设进阶篇

第25章 Drupal Commerce

本章作者 Ryan Szrama

随着drupal7的commerce模块的推出,drupal的电子商务功能变得更加强大了。Drupal commerce项目是由一组commerce核心模块组成的,并且利用了很多drupal7的新的改进和特性。这章将从对drupal commerce的一个广阔概述开始,在强调其关键的特性之后,才能继续进行详细的考察核心系统,它们的实现,它们如何一起使用。它还包括了针对建站者和开发者的一些建议,使他们能够在自己的网站上更好的实现drupal commerce的功能。这一章将以一些话题讨论结束,其中包括项目的发展历史、设计理念以及如何利用好drupal7的新特性。

Drupal Commerce概述

很多方面,drupal是一个理想的电子商务开发平台。其核心模块和系统为深度整合第三方模块和服务定义了丰富的API。它包含大量功能,用于内容管理、社区建设,使您可以围绕着产品去创建一个社区和服务,或者通过客户已有的社会网络关系来提升自己的品牌。

早在drupal4.5版的时候,所有主要的电子商务模块都是drupal的基础模块,而不是第三方模块,drupal commerce模块也不例外。随着drupal的发展,这些核心特性和主要的第三方模块比如views也越来越成熟了,使得基于它们去建设模块也越来越灵活和强大了。因此,drupal commerce头开始,围绕着drupal7的新特性和一些强大的模块比如viewsrules,设计了一个全新的体系结构。

无论你的业务需求大小如何,它都能提供一套解决方案让你从头来构建自己的电子商务系统。drupal 商务站点受益于drupal的安全性、易扩展性、众多第三方模块的选择。因为内容管理和社交商务工具融入了核心本身,所以对于今天的在线业务,Drupal是一个非常强大的平台,它不需要集成外部的电子商务应用程序。

主要功能

第七部分:Drupal社区

第七部分:Drupal社区

第27章 Drupal规模化

本章作者 Károly Négyesi

要定义“规模化”这个术语,让我们以一个小咖啡馆为例。当它刚开业的时候,因为小,店主料理一切事务:她负责点单、准备饮料、一手端上咖啡一手收钱。一段时间过后,咖啡馆生意变得好起来,店主因此雇了一个咖啡师和一个服务员。现在服务员负责点单和收钱,咖啡师拿到点单的纸条、准备饮料并端给顾客。在这里你应该注意到的是,当一个人来做每件事的时候,钱和饮料的交易是同时发生的。但是现在顾客是先付钱,最后才拿到饮料。在拿到饮料之前交钱,这其中是否包含某种风险呢?如果这时突然失火,顾客交的钱可能换不回来任何东西。然而,没有人真的会为这种可能性而担忧;为了更快得到咖啡,他们会愿意冒这么极小的一点风险。通过把“收钱”和“上咖啡”这两个动作分离开来,小店可以更快地服务更多的顾客。它可以雇佣任意人数的咖啡师和收银员以更好地满足客流。这就是规模化:用这样一种方式来容纳客流以致顾客较多的时候也不会减慢流程。

但是要注意的是,增加咖啡师并不会让付钱和拿到咖啡之间的时间有任何缩短。店主会确保始终有一位空闲的咖啡师,但你还是要等待他为你准备饮料。然而,如果店里雇佣狡猾的红色机器人来更快地制作咖啡,将有助于缩短这个等待时间。这就是性能:Web应用程序在接收一个请求到完成它之间的时间。

性能是重要的,因为人们容易放弃太慢的网站。规模化使得网站能应付大流量的情况,但规模化本身并不会让网站变快,让网站变快的是性能。然而,如果你有足够多的访客,即使是最快的网站,也最终不得不让某些访客等待,然后才能开始服务他们的请求。

现在我已经定义了规模化和性能,我将在本章的后续部分告诉你,为什么你应该关心他们。然后,我将讨论Drupal 7中的一些规模化可选方案。该讨论将主要集中在数据库这块,因为数据库对于Drupal规模化是整体上的。Drupal不总是可以随心所欲地规模化,但是我将要讨论到的更改能让Drupal的规模化更加高效。

你需要关心规模化吗?

当你刚开始着手构建一个网站时,处理大流量通常不是你的重心所在。大部分人所关心的是先引来流量。毕竟只有拥有大型成功网站的人才需要担心流量问题。没有哪个网站一开始就有数百万用户。然而,你可能恰恰在一段时间内就可以获得那么多甚至更多。如果你在花大气力做网站营销,那么就有理由为用户增长而谋划了。即使你只是在整个互联网上有那么一个固定百分比的流量,你也可能会迎来惊喜;新的设备,甚至新类型的设备,让越来越多的人花越来越多的时间去浏览网站。当成功就在撞击大门时,你会做什么?你的网站是否能适应呢?

如果你的网站不能适应访客的增长,你将会处于痛苦不堪的境地:本应到来的成功,将会被抓狂的用户所取代。等待页面加载,有时还可能根本就接收不到网站的响应,不管是新进用户还是长期粉丝,无不如此。雪上加霜的是,通常网站的编码方式造成增加额外的硬件也帮不上太多忙;在这种情况下,网站就必须重构了,通常还是从头再来。当然,这种情况不是一夜之间发生的,而且通常发生这种情况时,公司正忙于其他事情:增长。简而言之,更多的流量几乎意味着更多的潜在业务,所以与此同时,公司要尽力应付新需求所带来的必要增长,而这时网站方面又突然需要重大重构。当这种事情发生时,网站可能每天都会崩溃。公司或组织眼看着成功在望,却要艰难地维持网站的运转,这是我们不愿意看到的一幕。

但还有另外一个陷阱:在网站生命周期的开端就沉迷于规模化,以致为它耗去功能开发方面的宝贵资源。Drupal的模块化机制就一直提供针对这些问题的解决方案,Drupal 7更是在这方面上升到了一个新的高度。

缓存

缓存是指临时存储一些处理过的数据。它可以是结构化数据或是包含HTML格式化文本的字符串。用缓存数据来提供服务,比从多个数据表中读取和处理数据更快,但另一方面,缓存数据是不可编辑的,所以不可能把它处理成另外一种格式。因此,原始数据必须保留在数据库中。现在你有了多个数据副本,原始数据和缓存之间就有可能会失去同步。在这种情况下,缓存数据是“陈旧的”。有时这样没问题;如果你一天出产几篇文章,更新的内容在原本发布日期后的几分钟内不被匿名用户看到可能并没有多大关系。这是规模化方面的另外重要一课:理论服从实践。规模化永远是在寻求折衷方案,对于一个给定的网站,这只是哪种折衷更能接受的问题而已。

提示:注意用语“对于一个给定的网站”。没有放之四海而皆准的良方妙药,规模化永远都是因网站而异的,虽然有些实践经验适用于很多类似的网站。

Drupal可以利用缓存来存储整个页面,即页面的HTML,以供匿名用户访问。到admin/config/development/performance页面启用这个功能很简单,而且在最简单的共享主机上也能工作。注意该功能仅对匿名用户起作用,但常见的是,网站的大部分流量都来自匿名用户。默认情况下,提交新的内容会删除这部分缓存,但是可以设置一个最小缓存生命周期。还是那句话,因站而异。

提示:在共享主机上还有一个快于内建页面缓存的可选方案,Boost模块(drupal.org/project/boost)。该模块可以为你的匿名访客提供页面服务,完全绕过PHP。

让我们来看看开发者可以如何利用缓存来存储一个慢查询。假设你有一个非常慢的函数叫作very_slow_find(),下面就是如何利用缓存:

<?php
$cache
= cache_get('very_slow');
if (
$cache) {
   
$very_slow_result = $cache->data;
}
else {
   
// Run the very slow query.
   
$very_slow_result = very_slow_find();
   
cache_set('very_slow', $very_slow_result);
}
?>

首先,你试图读取缓存。如果成功,就使用缓存中的数据;如果缓存不存在,你就运行你的查询并把结果存储在缓存中。采用这种方法,那个极慢的查询很少有必要发生,不过需要注意前面提到的陈旧缓存。在该例中,very_slow是缓存ID(cid)或者叫缓存键,$very_slow_result是你所存储的数据。

缓存被广为使用,并且被存储于各种不同的箱子中。把所有数据都存储在一起固然可行,但是把它们分开存储在一种Drupal称作“箱子”的东西中有两个好处:一个箱子中的内容可以与其他箱子中的内容分开来,在必要时废弃以避免陈旧数据;并且,每个箱子可以设置不同的机制来存储数据。(我将在本章稍后讲解一些可用的存储机制示例)在使用Drupal的缓存API提供的cache_getcache_set以及其他函数的时候,你不用担心哪个存储机制可用。这就是为何可插拔的子系统如此重要:你可以选择不同的存储机制而无需修改任何代码。

Drupal默认使用数据库来存储缓存数据——不是因为这是最佳方案,而是因为Drupal知道它们在那里。所幸还有替代方案。下面首先你将看到一个更多关于辅助开发的例子,它还展示了如何配置可插拔子系统;然后你将看到一些性能和规模化的解决方案。

在开发过程中禁用缓存

对于开发目的而言,我建议使用最简单的缓存实现:无。有一种缓存实现等同于黑洞:缓存写入和清除程序不做任何事情,读取则永远是失败的。Drupal在安装过程中就使用这种假缓存,因为还没有任何关于何处缓存存储的信息可用。这种假缓存在开发过程中也非常有用。需要提醒的是,多步骤(因此还有AJAX)表单,需要一个工作缓存,当所有缓存都用黑洞方式处理时,它们将无法工作。要用Drupal自身的假缓存来让缓存短路,在settings.php中加入下面三行,settings.php文件在你站点的/sites文件夹下,通常它会是(相对于你的Drupal安装根目录下的)/sites/default/settings.php

    <?php
    $conf
['cache_backends'][] = 'includes/cache-install.inc';
   
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
   
$conf['cache_default_class'] = 'DrupalFakeCache';
   
?>

   

这么做了之后,复杂的数据机构,如主题注册表,将会在每个页面加载时重建(加入类和菜单项仍然需要到admin/config/development/performance页面执行明确缓存清空)。这让站点明显变慢,但却简化了开发者的生活,因为对于代码所做的修改会直接在Drupal的行为上反映出来。

settings.php中的$conf数组是指定Drupal配置信息的总中央。虽然有些配置选项有UI界面并把状态存储在数据库中,但有太多选项以致于不能为它们一一提供UI界面,并且大多数不便提供UI的选项,没有必要提供给一般用户。使用$conf而不使用UI界面的另一个理由是,有些选项在数据库可用之前就是必须的。缓存是两点兼而有之:它不是用户需要从UI界面设置的东西——大部分人永远没有这个必要——并且它可能在数据库加载之前就被使用。大部分人与默认的缓存实现相安无事,而缓存又可能在数据可用之前被使用。然而,因为数据库还不可用,缓存设置比平时要更复杂一些。你需要指定文件(在$conf['cache_backends']中),而不仅仅是所用的类(如$conf['cache_default_class']中)。对于其它设置,大多数时候指定类就够了,因为Drupal会从数据库中读取到包含该类的文件所在的位置。

memcached

接下来的解决方案在共享主机上将无法工作,你需要控制你的主机环境以达到优秀的性能和规模化。

memcached是将缓存存储在内存中并允许通过网络访问的一个独立程序。做为一个独立程序没什么特别,Drupal使用的数据库(如MySQL)也是这样的应用程序。memcached的与众不同之处在于,它的数据仅存储在内存中,这使得它非常非常快。使用该程序来取代数据库缓存对于Drupal的性能大有裨益。并且不仅仅是Drupal——这是一个非常成熟的解决方案,在每个大型网站中都有实际应用。

注意memcached不仅仅是一个性能解决方案,同时也是非常易于规模化的:你只需根据需要,在任意多个服务器上,启动任意多个它的实例,并配置Drupal来使用它们。memcached无需任何设置,因为每个独立的memcached实例不需要彼此了解。与它们每个进行对话的是Drupal。这和MySQL非常不同,它的主从服务器模式(master-slave)需要作明确的配置。

memcached由三部分组合而成:应用程序本身、一个PHP扩展和一个Drupal模块。memcached可从memcached.org获得,网站上有安装和配置的详细介绍。如前所述,你需要添加一个PHP扩展以让PHP和memcached进行通信。有两个名字易混淆的PHP扩展:“memcache”和“memcached”,即使专家对于它们谁更好也是各持己见。我谨慎推荐更新一些的“memcached”,它的PHP扩展可以通过以下命令安装:

pecl install memcached

你也可以按照操作系统特定的方式来安装,比如在Debian或Ubuntu上可以如下:

apt-get install php5-memcached

第三点也是最后一点,你通过安装Memcache模块(drupal.org/project/memcache)来让Drupal使用memcached。项目页面有大量文档,在此不再赘述。

Varnish

规模化工具集的另一个重要部分是Varnish。Varnish是一个存储页面和提供页面服务的外部程序。普通页面缓存需要一个到达web服务器的请求,顺序是:引导Drupal、加载页面、然后由Drupal发送请求。Boost模块提供一个更快的解决方案,因为现在请求只需到达web服务器,而Drupal并未启动。Varnish则更快,因为它自己处理请求。在匿名页面服务方面,它确实提供了非常快并且大范围的规模化解决方案。它以“Varnish让网站飞起来”为座右铭,并一直为此而努力。Varnish应用程序地址在www.varnish-cache.org/,与Drupal的整合方案在drupal.org/project/varnish

数据库方面

至此,你已经看到了一些可插拔子系统是如何配置的,你还简要地回顾了各种不同的提供快速匿名页面服务的解决方案。仅通过采用上述解决方案,你就可以使你的站点在性能方面(感谢memcached)和对匿名访客规模化方面(感谢Varnish)有良好的表现。然而,如今是社会化网站的天下,它们需要的是服务已登录用户。问题变得棘手起来。虽然memcached确实为你赢得一些性能,还是有很多问题需要克服。你需要退回一步,去了解网站是如何操作、存储和读取数据,所遭遇的问题,以及解决这些互相之间不太相关问题的全新解决方案。

从更高的层面看,多数网站都执行着相同的动作:收集数据(要么是用户或管理员通过浏览器表单输入的,要么是从另一个网站聚合来的),存储数据到数据库,然后再把数据显示给用户。显示和更改数据的操作通常称为创建(Create)、读取(Read)、更新(Update)和删除(Delete)四大操作,简称CRUD。一个典型的网站会使用某种SQL数据库,并在其上执行这些操作。

正如你马上将要看到的那样,对于一个网站所面临的诸多问题,SQL,特别是目前流行的各种SQL实现(包括MySQL/MariaDB/Drizzle、PostgreSQL、Oracle和微软的SQL Server),并不一定是最适用的。可是既然SQL不是最优的,为什么它却如此流行呢?同样,既然SQL如此流行和管用,另辟蹊径真的是个好主意吗?简而言之,大家一直在用这些SQL数据库,何必大惊小怪?

问得好。我们就来看看所谓的“一直”到底有多久。今天所用的大部分数据库都起源于上世纪七十年代末。确实,像MySQL或PostgreSQL这样的数据库或许并没有多少UNIREG(MySQL的祖先)、Postgres甚至INGRES(PostgreSQL的祖先)的遗留代码。就像亚伯拉罕·林肯用的斧子的传说,光荣地承载着一段家族通过使用而赋予的历史。几个世纪过去,斧柄被换了五次,斧头被换了三次。SQL数据库的设计初衷及其导致的局限性却不像更改代码一样那么容易。亚伯拉罕·林肯的斧子仍然是一把斧子。(此段中的比喻源自蒂姆·波顿电影《吸血鬼猎人林肯》——译者注。)

从这些数据库被构造的年代至今,像CUP速度、可用内存及可用磁盘空间这类因素已经增长了百万倍,磁盘速度却并未紧跟上来。并且,尽管任务也同时在增长,却没有几个增长了百万倍。最后,操作系统也变得更加复杂起来。

所有这些都意味着一些新的、以前不可想象的设计方案是必不可少的,并且具有重大意义。因为现在磁盘空间丰富而磁盘速度是制约因素,你可以把注意力集中在让数据库变得更快而不是更小。当有如此之多的廉价存储可用时,牺牲磁盘空间来获取性能看上去是个绝好的买卖。你可以假定内存能满足整个数据库,如果不能,问题由操作系统处理。这种设计方案影响的不仅仅是数据库:比方说,Varnish就引入了这些先进的编程范式,这也是它如此快的原因之一。

内存记忆

第一块个人电脑硬盘是希捷科技公司于1980年生产的ST-506(希捷科技公司,原文为“Shugart Technologies”,此处为原文错误。希捷科技的创始人虽然姓Shugart,但公司名称一直都是“Seagate Technology”,并未使用过“Shugart Technologies”这个名字。——译者注),它有5MB空间,和今天的DVD驱动器一样大,价值1500美元。所以,按1980年的美元市值计,1GB价值30万美元;按今天的美元市值计,已将近一百万。今天最快的硬盘可以存储300GB,价值不到300美元(这样1GB不到1美元),并且就像典型的笔记本电脑硬盘一样小。慢些的硬盘可大到3000GB(3TB),按照有些2TB的硬盘售价为80美元来算,当前硬盘每GB的最低价大约为0.04美分。

然而,不是每个方面的改变都如此之大:ST-506的寻道时间为170毫秒,1981年生产的ST-412的寻道时间为85毫秒,而今天一般硬盘的寻道时间为8到10毫秒,绝对最快的硬盘的寻道时间为3毫秒——连百倍的增长都不到。(寻道时间是指驱动器到达数据存储位置所耗费的时间。)一旦有的话,ST-506每秒读取半兆字节,而今天的典型硬盘则可能每秒读取一百多兆字节——速度仅提高了区区200倍。

快速闪存设备通过消除寻道时间,并让纯读取速度增加大约两倍,使得这一现状得以改善。对这些新设备来说,前面的路还很长:它们的可靠性还不能与硬盘相匹敌,而它们仍然比主要的内存要慢大约一百倍。

在1980年,64K内存对个人电脑来说是最大的,且价值400美元,这使得每兆字节价值6200美元(以1980年美元市值计)。今天,64G(100万倍)的服务器内存价值不到2000美元,这样每兆字节价值大约3美分,便宜了大约100百倍。

1980年最快的微处理器每秒运行一到两百万条指令(英特尔iAPX423,摩托罗拉68000)。今天,速度纪录高于万亿次浮点运算(IBM POWER7)——提高了一百万倍。商品化的处理器刚出现时价值200到350美元,如今仍在这个价格范围。

除了在磁盘空间和内存变得廉价充足之前所做的设计方案之外,SQL的基础知识也同样举足轻重。SQL基于数据表,数据表有列。比方说,你可能有一个用户资料的数据表,包含一个名字的列和一个姓氏的列,这意味着每一个用户资料将拥有一个名字和一个姓氏——每列都是精确的一个。即使只是在西方文化中,这种严格结构的不足之处也不难发现。例如,有个名叫Hillary Diane Rodham Clinton的人想要注册该怎么办呢?或是某人用他的网名作为合法名字,因此只有名字而没有姓呢?更好的存储方式是,你可能还需要一个用户名字的数据表,它有一个用户资料ID的列和一个名字的列,从而让存储任意多个你想要的名字成为可能。你马上将会看到这种方法的弊端。在Drupal 7中,你可以轻松地让一个“名字”字段变成多值文本域(multiple-value text field),不过这种作法并不漂亮,尽管Drupal把丑陋都藏起来了。

名字有上述问题大家都知道了,而要找出几乎每种数据的问题也并不难。例如,假设你在描述汽车,那么你有一个叫作汽车的数据表,并且有一个叫作马力的列,存储数字类型,一个叫作变速箱级数的列,也存储数字,一个叫作颜色的列,存储字符串。总之,汽车有一个固定马力的引擎,对吗?(哦,不一定,有的汽车既有汽油发动机又有电力发动机,它们当然有不同的马力级别,可记住,你只能输入一个数字。)对于变速箱级数,无级变速箱就不太好用数字来存储了。而对于颜色,列表是无穷尽的:两种颜色的小汽车普遍存在,还有遇热变色的喷漆可以让你的汽车变成一个变色龙。这个问题可以通过存储复杂的数据结构而非单个字符串或数字来解决。

所有这些都表明,你不能把所有数据都用SQL存储在一个单独的数据表中。可这又有什么大不了的呢?要回答这个问题,让我们来看看数据库是如何找到数据的。

索引

索引

你怎样在一本烹饪书中找到一个菜谱?当然是查找目录。按字母排序的菜谱固然便于查找,可是你会去记那个用西班牙香肠和牛油果做的美味沙拉的名字吗?即便你会,烹饪书也一般都是按照某些其他的主题,如季节或场合来排序的,所以就算是记住菜谱的确切名字,也不会有多大帮助。因此,让我们来建立一个基于原料的索引,它包含原料所出现位置的页码,如下:

    牛油果
      40, 60, 233
    西班牙香肠
      50, 60, 155

这样有点帮助了,但是要找到任何东西你都需要把每个列出的菜谱过一遍,因为没有任何信息告诉你哪些页面上有些什么。让我们来创建一个先按原料再按名字的字母顺序索引,如下:

    牛油果
      牛油果酱
        40
      玉米饼汤
        233
      暖风西班牙香肠凯撒色拉
        60
    西班牙香肠
      黑豆西班牙香肠卷
        50
      墨西哥风味炒鸡蛋
        155
      暖风西班牙香肠凯撒色拉
        60

这个索引虽然有用,但它只适用于当你在查找原料或原料和名字的情况。当你要按菜谱名字列出包含某种原料的菜谱时,它也有用。然而,如果你要查找一个名字,它就变得毫无用处了。比方说,如果你要找到墨西哥风味炒鸡蛋,你需要先浏览到牛油果,再是培根菜谱,再到西班牙香肠——比把整个书翻一遍好不了多少。

记住,你一开始是浏览你的烹饪书来查找一道既包含牛油果和又包含西班牙香肠的菜谱的,你要怎样建立一个让这种查找简单而快速的索引呢?如果你以菜谱名字开始索引,那么你需要浏览每个单独的菜谱才能找到它们,那不是一个好主意。如果你像你的第一个索引那样做,你可以轻松找到所有包含牛油果和所有包含西班牙香肠的菜谱。假设你有1000道牛油果菜谱和1000道西班牙香肠菜谱,但只有一道菜是两者均有的,为了找到单独的一个你需要比较2000个数字。要让这种操作变快,你其实需要一个类似于下面的索引:

    牛油果
      培根
        30, 37, 48
      西班牙香肠
        60
    培根
      牛油果
        30, 37, 48
      西班牙香肠
        70

那简直太可怕了。它不仅仅是存储需求会非常高,更重要的是,在现实生活中,维护类似这样的数据很快就会变得不切实际。就算一道菜谱平均不过8种原料,你能创建8*7=56个数据对以致于每次更改都要更新56个索引条目吗?!(即使你不同时存储“牛油果-培根”和“培根-牛油果”,仅仅将它减半并不解决任何问题,实际上在查询过程中还会产生一些问题,因为“原料-名字”索引不能反过来用。)这种方式恰恰和SQL存储和索引数据的方式相同,而这也恰恰是SQL规模化中的问题:你不能在大部分跨多表查询中使用索引——而由于SQL的严格你又要被迫使用多个表。让我们来看一个Drupal例子!

在Drupal中,节点有与之相关的评论。节点存储在一个数据表(Node)中,而因为一个节点可以有多个相关评论,评论存储在一个单独的表(Comment)中。如果你要显示最近十个被评论过的“页面”节点,你将面临类似的问题:如果你有一个基于创建日期的索引,那么你可以有一个按创建日期排序的评论列表以及它们所属的节点,而如果你有一个基于节点类型的索引,你可以有一个“页面”节点的列表,但是,假如最后二十万条评论全部都是针对“新闻”节点的,并且你有五万个页面,那么,你要么先将二十万条评论过一遍,以发现它们中没有一个是“页面”,要么你需要将五万个页面过一遍以找到每个的最新评论。那显然不够快。

通过把节点类型存储在评论表中,这个例子可以轻松地得到解决,因为节点类型不会改变。但是,如果你要把节点的更改信息和评论一起处理,那么你将被迫把节点的更改信息存储在评论表中,一次节点编辑可能触发评论表中数百行的更新。这种作法叫作“去规范化(Denormalization)”,且被广泛使用。然而,它应该亮起红旗:你在一个什么样的系统上工作啊?难道它非得抛开最基本的理论(规范化)才能表现良好?对于这个问题,我稍后将展示一个极好的解决方案,但是现在还是让我们继续来列举SQL的问题吧。

SQL中的NULL值

SQL中的NULL值

NULL是一个极妙的颠覆常规逻辑的结构;它的使用充满矛盾,它甚至在数据库中都不是始终如一的。NULL用来发出这样一种信号:某些数据丢失或无效,并且它永远都不等于、大于或小于任何东西。不管你对它做什么,操作的结果都是NULL。你需要一个特殊的IS NULL操作符来判断NULL值,如下:

mysql> SELECT 0 > NULL, 0 = NULL, 0 < NULL, 0 IS NULL, NULL IS NULL;
+----------+----------+----------+-----------+--------------+
| 0 > NULL | 0 = NULL | 0 < NULL | 0 IS NULL | NULL IS NULL |
+----------+----------+----------+-----------+--------------+
|     NULL |     NULL |     NULL |         0 |            1 |
+----------+----------+----------+-----------+--------------+

这就是所谓的三值逻辑。语句既不为真也不为假,而是NULL。NULL列导致很多奇怪的问题。Drupal(提醒你,并非出于有意的决定)没有太多的NULL列,从而侥幸避免了这种疯狂。唉,这不是一贯的,有时你还是被迫掉进NULL的坑里。

NULL比只是三值逻辑甚至更加颠覆常识,比如:

CREATE TABLE test1 (a int, b int);
CREATE TABLE test2 (a int, b int);
INSERT INTO test1 (a, b) VALUES (1, 0);
INSERT INTO test2 (a, b) VALUES (NULL, 1);
SELECT test1.a test1_a, test1.b test1_b, test2.a test2_a, test2.b test2_b
FROM test1
LEFT JOIN test2 ON test1.a=test2.a
WHERE test2.a IS NULL;

+---------+---------+---------+---------+
| test1_a | test1_b | test2_a | test2_b |
+---------+---------+---------+---------+
|       1 |       0 |    NULL |    NULL |
+---------+---------+---------+---------+

test2表中显然没有(NULL,NULL)这么一行。但是,左联接使用它来显示所谓的反联接(anti-joins)的结果。它们用来从第一个表中选择那些第二个表中没有与之匹配的行。

现在,如果1 = NULL为真,它应该找到了你插入到test2中的那单独一行,它没有那么做。记住,1 = NULL,既不是真也不是假,它就是NULL。但是仅从查询结果来看,这绝对不明显;总之,结果包含一个为NULL的test2_a,却没有执行规定的test1.a=test2.a联接不是吗?不,它确实执行了联接,但是在这个特例中,联接的部分并不需要为真。查询结果中有用NULL标记出来的丢失数据,这和联接无关。如果你发现这使人迷惑,它就是如此。你不仅需要与三值逻辑共存,还要对左联接给予豁免。

在我展示这些问题的答案之前,还有一个数据库架构细节需要涵盖。目前的SQL实现侧重于“事务(Transactions)”(该术语来源于财务交易,但它可以指数据库所执行的任何单位的工作。)你将马上会看到,这种侧重的效果与网站用户的期望是背道而驰的——因为网站用户的期望与银行用户的期望是不相同的。

ACID和BASE之间的一致性、可用性和分区宽容度(CAP)

事务(Transaction)的典型例子是汇款给别人。这是一个极为复杂的过程,但是在当天结束的时候,你期望你的帐户余额减少了你汇出的金额,而收款人的余额增加了该金额(减去汇费)。你同样期望,一旦银行说“你汇出了钞票”,它就确实汇出了,而不管实际的汇款需要用时多久(有个叫作SWIFT的汇款耗时简直是长得太荒谬了)。还有一个期望是,如果钱从你的帐户中消失了,它就会出现另一方的帐户中,而不管银行的计算机系统出了什么状况。你当然不想如果某台电脑崩溃,你的钱就消失得无影无踪了。基于这些期望,数据库的一组属性集合足以使事务正常工作——它们一般缩写为ACID:

  • 原子性(Atomic:):要么整个事务成功,要么整个不成功。
  • 一致性(Consistency):数据库在事务之间处于一个一致的状态中。比方说,如果一条记录指向另一条记录,而到事务结束时这个指向是无效的,那么整个事务就必须回滚。
  • 隔离性(Isolation):在其他事务结束之前,事务看不到被它们更改的数据。
  • 持久性(Durability):一旦数据库系统通知用户事务成功,数据就永不丢失。

另一方面,大部分的Web应用程序需要的是完全不同的一组属性,它们被巧妙地命名为BASE:(BASE更常见的解释是:Basically Available, Soft-state, Eventual consistency,和本文此处所述略有出入。——译者注。)

  • 基本可用(Basically Available):用户都有一种愚蠢的期望,就是当他们把浏览器指向一个网页时,就会有某些信息出现。这是你期望发生的,即便系统的某些部分宕机了也一样。这听上去微不足道,事实却并非如此;有很多系统只要一台服务器宕了,整个系统就宕了。
  • 可伸缩(Scalable):添加更多的服务器使得服务更多的客户成为可能。不是创建一个巨大的怪物服务器,而是添加更多的服务器,这更具有前瞻性,而且也通常更便宜。重申一下,这是用户的期望之一:信息不仅仅是出现,而且还要快速地出现。(注意这项用户期望不仅要求伸缩性,还同时要求性能。)
  • 最终一致(Eventually Consistent):如果数据最终在所有副本出现的地方变为可用,这就足够了(如上一点所述,你可以有很多副本)。这里的期望是,信息要快速出现才会显得及时。 当你在Craigslist上发布一个广告,它不会马上出现在列表和搜索结果中,这不是太大的问题;但如果一个广告需要几天才出现,就没有人会去用那个网站了。

关于这里的最后一点,如果你有一个像ACID属性列表那样的一致性系统,它也是“最终一致的”,因为它遵循一个更强的要求:数据立即对所有副本可用。(这一点我会稍后回来详细讲解。)

如果有个数据库系统能够既是ACID又是BASE,那就太棒了,可是,唉,那其实是不可能的。记住,BASE一般与由大量服务器组成的系统有关。如果你要求所有的写入都非常一致,那么每次写入都要经过每台服务器,而你则必须等到所有的服务器都完成写入并且回应这个事实。现在,如果你有分布在全球各个数据中心的服务器,那么网络问题是意料之中的。让我们假设有一个横跨大西洋的连接发生故障——这叫作网络分区。如果你需要一致性,那么系统就必须和横跨大西洋的连接一起发生故障——即使其它所有的服务器都是工作的——因为在欧洲的写入无法到达美国,反之亦然,所以那些部分将产生不一致数据。你需要放弃一些东西:要么放弃BASE中的可用性(A),系统宕机以获取一致性;要么放弃ACID中的一致性(C),你屹立不倒而系统不一致。换句话说,在一致性、可用性和分区宽容度这三者中,你只能选择两者而不能三者兼而有之。至此,我只是为数据库定义过一致性这个术语,所以是时候来给它一个一般性的定义了:在任何时间点,不管哪台服务器应答了一个请求,所有服务器都会给出同样的答案。可用性,如上所述,意思是即使某些服务器宕机了,整个系统仍能正常工作。最后,分区宽容度是指两台服务器之间的通信可以丢失,而系统仍能正常工作。你看后面两点,尽管多么“软性”,但当你需要它们和一致性并存时,它们又摆出一副很难搞定的样子了。

我已经展示了CAP的三个部分不能同时工作,而我也可以展示它们中的任何两个都可以轻松共存。如果你确保网络的各部分永不失效,那么提供一致性和可用性就不是问题。尽管听上去不大可能,Google的BigTable实际上正是一个这样的系统,有超过60个Google项目在使用它。如果你抛开可用性,那么另外两个需求也能轻松满足:只要你不打开宕掉的服务器,即使部分网络停止工作了,数据还是会保持一致。最后,如果你不关心一致性,你大可不必从一台服务器往另一台服务器复制数据——这种系统自然不必关心网络分区(因为它根本就没有用到网络),并且只要你到达一个工作的服务器,它总能应答一些东西。虽然后面两个例子有点极端,不能用来描述一个有用的系统,但是这里要重申的是,尽管一致性、可用性和分区宽容度三者不能共存,但它们中的任何两者都可以。

让我们重新回忆一下咖啡店的故事:为了能够雇佣更多的咖啡师(规模化),你需要接受顾客在短时间内付了钱而没有咖啡这个事实(最终一致而不是马上一致)。而且人们甚至在对待Web应用程序时也接受这点。如果你的评论需要过一会儿(即使是几分钟)才能显示给全世界每个人,并无伤大雅。当然,你期望它能马上显示给评论的发布者,或者至少在数据一旦发往服务器并且回复已经到达的时候。但是对于其他人,从互联网上加载网页总是要耗费一些时间的,所以,对于那些刚开始加载页面的人们来说,服务器是在“发送”按钮按下前还是按下后显示那个新的评论,几乎无关紧要。更重要的是网页总能响应(可用的),而不管有多少人在同时浏览(伸缩性)。

所以,虽然SQL数据库大多是兼容ACID的,但注重BASE的数据库更适合Web应用程序。不仅如此,下面的这些数据库还把翻天覆地的硬件变化和操作系统的可能性考虑其中,以形成更加适应这些目的的数据库版本。依据这些理论而构建的数据库先驱之一是CouchDB,发布于2005年,Cassandra于2008年向公众发布,而MongoDB首次发布于2009年。

同样是在2009年,这些个新的非关系数据库系统们有了一个琅琅上口的别号——“NoSQL”。当然这只是个流行词,因为用SQL来驱动一个不兼容ACID的数据库也是可能的(实际上MySQL就在不兼容ACID的情况下使用SQL有十多年了),但是这些数据库的特性集和SQL所要求的是如此不同,因而没有意义去尝试。比方说,这些数据库中没有一个提供数据表联接(JOIN)功能。

由于MongoDB是很多Drupal任务的最佳选择,我现在就来对它进行详细讨论。

MongoDB

MongoDB的入门超级简单:在浏览器中打开try.mongodb.org即可。它提供一个教程,让你用该数据库来玩,而无需下载任何东西。如果你想在自己的电脑上使用,你可以从mongodb.org/downloads下载,并且可随时运行;没有复杂的配置文件要写。Drupal的整合项目在drupal.org/project/mongodb

MongoDB惊人地适合于Drupal 7,尽管很多Drupal 7的开发是先于MongoDB的。它是一个为Web而设计的数据库,所以它和全球最好的用来制作网站的软件Drupal匹配得如此之好也就不足为奇了。

MongoDB存储的基本上是JSON编码的文档(实际差别很小)。一个文档粗略等同于一条SQL记录。任意多个文档组成一个集合,粗略等同于一个SQL数据表。最后,数据库包含集合,和MySQL数据库包含数据表非常类似。

一个MySQL数据表只能有固定的记录,而一个MongoDB集合可以存储任何类型的文档,比如这样:

{ title: 'first document', length: 255 },
{ name: 'John Doe', weight: 20 }

如此等等,任何东西都可以。没有CREATE TABLE命令,因为一个文档和另一个文档可以大不相同。

还记得在SQL中存储名字的问题吗?在这里不是问题了!

db.people.insert({ name: ['Juan', 'Carlos', 'Alfonso', 'Víctor', 'María', 'de', 'Borbón', 'y', 'Borbón-Dos', 'Sicilias'], title: 'King of Spain'})

如果你想要得到名叫Carlos的人的一个文档列表,你可以运行db.people.find({name: 'Carlos'})。它会找到西班牙国王,不管Carlos在名字列表的何处。

还有,属性可以比仅仅数字和字符串要多得多。

db.test.insert({
  'title': 'This is an example',
  'body': 'This can be a very long string. The whole document is limited to a number of megabytes.',
  'votes': 56,.
  'options':
    {
      'sticky': true,
      'promoted': false,
    },
  'comments': [
    {
      'title': 'first comment',
      'author': 'joe',
      'published': true,
    },
    {
      'title': 'first comment',
      'author': 'harry',
      'homepage': 'http://example.com',
      'published': false,
    }
  ]
});

所以,文档有属性(上面例子中的title, body等等),并且属性可以有任何类型的值,如字符串(title)、数字(votes)、布尔值(options,sticky)、数组(comments)和更多对象(也叫作子文档,options就是一个例子)。文档能有多复杂是没有限制的。不过,文档有尺寸上的限制(曾经很长一段时间里是4MB;目前是16MB,但计划会增加到32MB);对于大部分网页来说,这个限制不是问题。仍就这个例子来说,一篇文章能有多少评论?几千条的空间是有的。如果不够,实际的正文(它从来不会被查询)可以单独存储。

因此同SQL相比,主要的优点是,无需事先指定数据表结构,其实没有固定的结构,值也可以是复杂的结构体。

针对上面的文档,还有一些更有趣的查找命令:

db.test.find({title: /^This/});
db.test.find({'options.sticky': true});
db.test.find({comments.author': 'joe'});

正如你所见,find命令使用的是同一个插入的JSON文档。第一个find命令使用正则表达式来查找标题以“This”打头的文章,第二个显示了在对象内部查找有多容易,第三个显示了对象数组也同样容易。对所有这三个查询进行索引也是可能的。

如果你看一看Drupal 7的带有字段的实体(Entity),它实际上是包含数组的对象。你可以把它们整个存储和索引到MongoDB里面。这正是最至关重要的:在SQL中,尽管你可以把相同结构的实体(比如相同类型的节点)存储在一个单独的数据表中,但如果你需要跨越这些类型进行查询(比如,显示一个用户收藏的所有近期内容,不管它们是文章还是照片),那么你就不能索引这个查询。记住,要快速执行一个这样的查询,你需要去规范化(denormalize);换句话说,在一张单独的表中保存一份你需要一起查询的数据的副本。这难于维护并且更新缓慢,因为每条数据都有很多副本。在MongoDB中,你可以轻松做到这个跨内容类型的查询,因为所有节点都可以待在一个单独的表中,你只要在‘favorite’和‘created’上建立一个索引就万事大吉了。

总而言之,SQL是不能存储复杂的结构体的。尽管MongoDB不是针对该问题的唯一解决方案,但相比SQL使用多表和去规范化的方法,它实现和维护起来都要简单得多。这个问题在Drupal 6中使用CCK时就存在,而随着Drupal 7中实体和字段API的引入,它已经变成了一个重要的问题。

要使用MongoDB作为默认的字段存储引擎,把以下代码:

$conf['field_storage_default'] = 'mongodb_field_storage';

加到(sites/default或相应的sites目录下的)settings.php文件中。如果你是从代码创建字段,设置字段数组的‘storage’键同样可行。用户界面上不提供该选择,所以如果你完全使用用户界面,就不能按字段来选择存储方式。

尽管MongoDB最好的方面可能是作为一个极佳的存储引擎而存在,可它甚至还不止于此。它还能解决其它问题。再次重申,它并非唯一的解决方案,但确是非常优秀的一个。

首先,让我们来看一些与写入有关的更多特性。比如像这样来增加我们的测试文档中的投票数:

o = db.test.findOne({nid: 12345678});
o.votes++;
db.test.save(o);

这个代码尽管工作,却不怎么漂亮,而且容易导致争用情况(race condition)。争用情况是开发者的灾星,因为它极难复制却会产生神秘的错误。让我们来看一个例子。下面是当两个用户试图投票时可能会依次发生的事:

  • 用户A运行o = db.test.findOne({nid: 12345678});-- 票数为56。
  • 用户A运行o.votes++
  • 用户B运行o = db.test.findOne({nid: 12345678});-- 票数为56。
  • 用户A运行db.test.save(o);把票数设置为57。
  • 用户B运行o.votes++
  • 用户B运行db.test.save(o);把票数设置为57。

在MongoDB中,你可以替换成:

db.test.update({nid: 12345678}, {$inc : { votes : 1 }});

  • 用户A运行该命令,票数加1,为57
  • 用户B运行该命令,票数加1,为58

这是一个原子操作,不可能发生由于争用情况而产生的错误了。注意更新操作同样是JSON文档的形式;有特殊的更新操作符,如上面所示的$inc,但语法还是一样的。

你也可以指定一个多文档更新

db.test.update({nid: {'$in': [123, 456]}, {$inc : { votes : 1 }}, {multiple:1});

尽管单个文档会原子地递增(无争用情况),多文档更新却不是原子的。我在前面列出事务属性的时候提到过,在事务的情况中,原子性意味着,要么所有文档更新,要么没有文档更新。在这就不是这种情况了,因为MongoDB中没有事务:有可能一个文档更新失败了,却对其它成功更新的文档没有任何影响。MongoDB还缺少事务所需的另一项性能,叫作隔离性:当一个文档已经对每个客户更新了,其它文档可能还拥有它们的旧数据。

按文档原子递增使得MongoDB成为存储各种统计数据的理想选择。例如,你可能需要存储并显示一个节点被查看的次数。首先,给节点添加一个数字字段,叫作‘views’,接下来,你会看到一小段代码,接收节点ID作为参数,并把views字段的值增加1。注意,为了阻止浏览器或代理缓存它,这段代码发送一个和Drupal一样的头部,最后显示一个1x1的透明GIF。

<?php
if (!empty($_GET['nid']) && $_GET['nid'] == (int) $_GET['nid']) {
 
define('DRUPAL_ROOT', getcwd());
  require_once
DRUPAL_ROOT . '/includes/bootstrap.inc';
 
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
 
  require_once
DRUPAL_ROOT . '/sites/all/modules/mongodb/mongodb.module';
 
$find = array('_id' => (int) $_GET['nid']);
 
$update = array('$inc' => array('views.value' => 1));
 
mongodb_collection('fields_current', 'node')->update($find, $update);
}

header('Content-type: image/gif');
header('Content-length: 43');
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
header("Cache-Control: must-revalidate");
printf('%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c ', 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 33, 249, 4, 1, 0, 0, 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59);
?>

现在你要做的就是把它保存为Drupal根目录下的stats.php,并在node.tpl.php中加入一个<img src="<?php print base_path() . 'stats.php?nid=' . $node->nid; ?>"/>。通过这种方式来统计节点的查看次数,即使页面是缓存或者Varnish提供的,查看也会被统计。

这会扼杀站点的性能吗?要视情况而定。它确实造成每次有人加载页面都动用到PHP,这可能是可接受的,也可能是不可接受的。对于大多数站点来说,没问题。如果站点用它来工作,MongoDB不会产生问题。鉴于MongoDB是在内存中操作数据库,偶尔写回磁盘,因为写入仅仅发生在内存中,所以要快很多。同时,它尽力在原地更新数据,所以索引无需做没必要的更新。

2011年的新特性是即使使用单个服务器时也不丢失写入的功能。对MongoDB最大的抱怨之一是,因为它只是偶尔才写回数据,所以如果服务器崩溃,数据可能会丢失。以前,通过使应用程序等待写入被复制到另一台服务器,寄望于两台服务器不会同时崩溃,这种情况得以缓解。但是现在,如果mongod是以-dur选项启动,则写入不丢失。

日志、会话和队列

Drupal还有其它有着大量写入操作的部分:日志(Watchdog,亦见直译为“看门狗”,本文使用“日志”这个译法。——译者注)和会话。会话子系统维持用户的登录状态;所以它需要在每个页面加载时执行写入。MongoDB的快速写入使得这不是问题。一旦你运行了mongod,并且安装了Drupal模块,只要加入:

$conf['session_inc'] = DRUPAL_ROOT
. '/sites/all/modules/mongodb/mongodb_session/mongodb_session.inc';

到(sites/default或相应的sites目录下的)settings.php中,MongoDB就会接管会话,再次让站点加速。

日志(Watchdog)有点棘手,因为如果某个原因导致有大量的错误消息(比如大规模蠕虫爆发试图获取不存在的URL),SQL数据表可能会增长到一个尺寸,以致唯一可行的操作是完全丢弃(TRUNCATE)它。删除旧行可能都无法招架写入的洪流,而且写入还有点慢。传统作法是使用系统日志(syslog),就是一个文本文件,所以查询起来不是最简单的。使用MongoDB,你可以指定一个集合只应保留最后的N个文档,然后自动重来,覆盖掉旧的消息,所以永远不会有多于指定数目的消息,让查询轻松而方便。同时,Drupal的日志实现把不同的消息放到不同的集合里,所以对于每个消息,都不会有多于指定数目的消息被记录。例如,“评论被创建”的消息不会被php错误消息挤掉。要使用这个功能,启用mongodb_watchdog,并禁用dblog模块。

最后,Drupal 7有一个消息队列,它也可以用MongoDB来实现。有很多队列,但是如果你已经为字段存储、日志和会话部署了MongoDB,那么使用MongoDB的快速写入来取代SQL是够便利的了。只要添加:

$conf['queue_default_class'] = 'MongoDBQueue';

settings.php中你就搞定了。你已经看到如何利用MongoDB作为字段存储引擎、存储会话数据、记录日志消息以及作为队列机制。把它用作缓存也是可能的,但memcache用于那个目的更好(因为它在伸缩性方面做得更加细致)。

MongoDB中的Null值

至此你已经看到MongoDB是如何解决SQL问题的。让我们来看看它能否改善前面所讨论的SQL的NULL带来的怪异现象(你在做这个的同时会学习到更多的MongoDB知识,并看到更多的MongoDB查询方面的例子)。

MongoDB处理NULL的方式比SQL稍许理智一些,但它一定还是有它自己的怪处。下面的查找是完全合法的,它同样显示NULL值需要特殊的操作符:

db.test.find({something:null});

这将会找到something有NULL值的文档。非常简单。NULL是自成一派的,与任何其它类型比较的结果都是false,因为MongoDB是严格类型的,不会为你作类型转换。所以,把一个数字同NULL作比较将永远是false,但把同样的数字和字符串相比较是true。这其实不是一个问题——只是一个你需要留意的地方。马上会有例子显示。至少MongoDB没有引入三值逻辑;比较的结果只能是true或false,不会是NULL。

不过,对于NULL有一个警告:不存在的值是当作NULL来处理的。

> db.test.drop()
> db.test.insert({a:1});
> db.test.insert({something:null});
> db.test.insert({something:1});
> db.test.find({something:null});
{ "a" : 1 }
{ "something" : null }#

要找到你真正想找的,这样做:

> db.test.find({something:{$in: [null], $exists:true}});
{ "something" : null }

新的操作符是$in和$exists。下面是将NULL和1作比较的例子:

> db.test.find({something: {$gte: null }});
{ "a" : 1 }
{ "something" : null }

我们再次看到,第一个文档不包含something属性,所以当作比较时,它是匹配的。第二个文档包含一个something: NULL的组合,并且操作符是“大于等于”($gte),所以它也匹配了。你有一个something为1的文档,但它不匹配——因为NULL不是零。

小结

如你所见,关于规模化的思考在建站早期不一定是最优先考虑的。但是,在你真正开始头痛之前就早做打算总是值得的。本章介绍了你为什么应该尽早关注规模化,以及Drupal 7中有哪些可用的技术来应对规模化。主要关注焦点集中在数据库方面,因为它们对于Drupal中的规模化是绝对整体上的。缓存之类的技术当然也是有用的,所以你也对它们进行了一番审视。总而言之,本章中所介绍的更改将带你的Drupal网站朝着高效规模化的道路迈进。

提示:可到dgd7.org/scale上面更快获取不定期更新、讨论及资源。

第八部分:附录

第八部分:附录

附录E:易用性

作者:Mike Gifford

Drupal 7 在整体的易用性上有个大的飞跃,这得益于过去三年来400多人的共同努力。Drupal 7是现今最为好用的内容管理系统之一。无论是公共页面还是管理页面都针对易用性问题做了检验,很多阻碍用户参与的障碍被清除了。Drupal的模块化结构 通过借助API和钩子,使内核函数也能为主题和模块所使用,这就意味着当开发者定制自己的网站时也能继承内核中的很多处改进。当然,这不表示只要用它开发 的网站就是好用的。

最新改进

API表单实现了多项改进以保证站点的互动元素尽可能的易用,包括:

  • 跳过导航被引入到所有的内核主题中了,另外对于如何处理焦点元素的隐藏、不可见和可见显示有了默认的方法。
  • 针对弱视用户提供了更为充分的颜色对比和亮度,同时图片被添加到系统信息中去了,用来为所有用户提供额外的可视化提示。
  • 密码管理系统也增强了,鼓励每个用户都有个好密码,这些改进包括一个使用WAI-ARIA(无障碍富互联网应用程序)来提示屏幕阅读器当前安全级别的进度条(关于WAI-ARIA,请看本附录中的“引入WAI-ARIA”部分)。
  • 在大多数情况下,拖放式界面给盲人用户制造了障碍。为解决这个问题,现在用户可以根据需要关闭拖放排序功能。
  • 由jQuery添加的一些互动元素被增加了提醒。现在屏幕阅读器用户会看到自动完成的短名单提示。
  • 现在Drupal内核可由盲人或仅依靠键盘操作的人来安装和管理了。网站的内容也能为更多的残障人士进行有效浏览。

遵循哪个标准?

万维网联盟(W3C)的无障碍网页倡议(WAI)一直引领着易用性技术国际标准的开发。在美国,国家标准在508节;但是,它正在修订很有可能要考虑Web内容无障碍指南(WCAG)2.0标准。

Drupal是个全球社区,因此我们选择WCAG 2.0来测试,我们同时也部分的使用无障碍富互联网应用程序(ARIA)草案中的一些条款,这些条款允许屏幕阅读器在内容动态变化时获得提醒。

自2008年底发布以来,WCAG 2.0已经不局限于网络而是朝着技术中立的方向前进。不集中于关注HTML之后,这些指南将为网站在可感知、可操作、可理解和强健方面(简称为POUR准则)提供更具通用意义的需求。它们定义了大量的技巧以确保网站不给用户制造障碍。

当我们开始希望有更加动态、更多互动的网络行为发生在更加广泛的设备上时,有一个灵活的通用的指南变的越来越重要了。这就是WCAG 2.0为什么要围绕着POUR准则来制定的原因,它必问到你几个问题:

  • 所有用户都能感知到你的内容吗?做一些简单的事情,如测试颜色对比、为图片和其他非文本内容提供替换性文本和说明就能帮到这一点。
  • 每个人都能操作你的网站吗?现在人们与网站互动的工具有鼠标、键盘、屏幕阅读器和专门软件的头针等等。
  • 你的网站好理解吗?用户界面是否按照可预见方式运转?有没有途径让用户快速的识别他们的错误并纠正它们?
  • 你的网站强壮吗?向后兼容吗,是否建立在开放标准之上?你支持并使用哪种现有技术来测试?

谁是受益者?

是的,如果易用性最佳做法得到遵守,盲人是肯定受益的,这个你已经知道。他们代表极小一部分人,他们仅是要考虑的残疾人群之一。2005年在美国进行的一项收入和计划参与调查行动(SIPP)结果估计有5440人有残疾。

便利性的常见符号是轮椅,但是在使用互联网时,很多使用轮椅的人并不处于劣势。上面调查估计的残疾人包括运动机能损伤者但可能不包括色盲和难语症患 者。我们仅能对后两种病患者的数量有个粗略的估计,但是它占人口中相当大的一部分——而且一个容易忽视的问题是我们不大可能一眼就判断出谁是难语症者。

Drupal 7 中的改进部分不仅仅是要让那些我们已经认识到的残疾人群受益。大多数国家都在面临着一个迅速老龄化问题,这就意味着在视力、听力和精细动作控制方面存在问题的用户要增多。针对易用性上更多的需求进行开发,我们就能最终找到提升每个用户体验的办法。

易用性问题将影响到每个人,尽管仅是短暂的。随着我们慢慢变老,我们的身体和意识变得越来越不容易使用各种技术,因此易用性设计也就更为重要了。而且随着平均年龄的增长,受益于易用性改善的人数也在增长。易用性问题归根到底是个关乎我们人自身的问题。

法律规定

很可能你所在的国家签署过联合国残疾人权利公约。这个公约承诺全体人员平等的参与社会。而且,很多国家已经或者正在为易用性立法。在美国,美国人借 助残疾人法案要求领导人承认残疾人独立生活和完全参与生活各个方面的权利。澳大利亚、比利时、加拿大、丹麦、芬兰、法国、德国、香港、印度、爱尔兰、以色 列、意大利、日本、韩国、荷兰、新西兰、葡萄牙、西班牙、瑞士和英国也都制定了保证易用性和信息通信技术的法律。

易用性正在成为一项法律需求,各种组织都要意识到这一点。事实上,有政府机构和营利性企业就因为它们的网站给残疾人设置了障碍而受到控诉。这不是经常发生,但是鼎鼎有名的Target公司和国际奥林匹克运动委员会就因为没有提供方便易用的网站而败诉。

九大方法让你的网站方便易用

Drupal 7 为你提供了一个坚固的平台让你搭建非常好用的网站,但是这个不足以保证你的网站就是易用的。使用贡献主题和模块很容易给你的用户设置障碍。用户贡献内容的架构和图片字段的使用导致了更多的障碍。

关于这方面要说的东西很多,但我们只限于介绍选择好的模块和主题方面的基本原则。我们将了解一下颜色和对比,因为这是易用性设计上经常被忽略的一个 方面。然后我们将了解一下如何进行自动测试和自我模拟。最后,我们将看看如何让你的网站时刻遵循新的标准以确保对每个人都保持易用。

易用模块

Drupal包含着复杂程度和可靠性不一的各式模块。Drupal内核的钩子能被模块和主题调用,这样就能对Drupal内核的很多功能进行修改。 这意味着一个开发时没把易用性放在心上的模块可能会导致你站点的易用性降低。你可以在模块的问题队列里搜索一下该模块的易用性问题来看看你想安装的模块是 否存在已被发现的问题(看http://drupal.org/project/issues/search /?issue_tags=accessibility)。

如果你碰到一个易用性问题但是还没有列入到问题队列里来,请往问题队列里添加一个错误报告以便它能得到解决。只有这样,我们才能改进Drupal数以万计的模块和主题,并实现易用性方面的最优做法。

Accessible Helper模块 (http://drupal.org/project/accessible)的目标就是帮助站点更加轻松的取得易用性。还有一些子模块可以帮助你轻松改善站点主题、模块和内容方面的易用性。

主题化站点注意事项

有一些贡献出来的很好的基础主题像 AdaptiveTheme、Genesis和Zen等与Drupal 7 工作的很好,而且都对易用性做过测试。将工作建立在一个坚固的基础框架上将助你确保你的主题易用。

当你开发主题时,别忘了给你所有图片都添加上alt标签。意义通常都是通过可视化图片传递给用户,但是要想想如何将这些意思表达给看不见它们的人。使用自动测试工具(后面有介绍)是发现图片缺失alt标签的好方法。

主题化出易用网站的Drupal指南(http://drupal.org/node/506866)很好,这里还有很多的方法可以让生成一个易用 网站变的更加简单。利用CSS 3,你能实现此前需要用图片才能实现的一些效果,如圆角和3D。CSS border-radius属性和专属于Webkit、Mozilla的一些扩展能让你更进一步。去除图片让网站页面更易用、快速,维护也更简单。

当心对CSS属性display:none的使用,因为屏幕阅读器只按字面理解。当一个东西用display:none样式处理的时候,屏幕阅读器 还有屏幕显示设备是看不到的。请确保每个链接链表(如一个菜单)都有一个页首(通常是一个H2)。如果需要对视力正常者隐藏,请用新的Drupal CSS类属性.element-invisible。

从项目全过程来说,根据标准开发能够节省时间和金钱。保证你的主题符合W3C指南将保证你的站点向后兼容、同时也肯定会在更多的浏览器里展示良好。 借助一些免费工具如W3C的(validator.w3.org)或Validator.nu(validator.nu)能够帮助发现你CSS和 HTML里面的问题。

对比度和颜色

理解人看世界有多种不同方式将提高你为他们设计的能力。Drupal文档中有一些很好的材料就是关于颜色的使用和正确匹配颜色对比度的,见 http://drupal.org/node/464500

很多站点没有为内容提供足够的文字对比度以让所有用户都能轻松阅读。这里有些简单的工具能保证你的站点为弱视用户提供足够的对比度,如 http://webaim.org/resources/contrastchecker

颜色经常被用来传递信息,但是不是每个人都能区分全部的颜色。大概有8%的成年男性辨色有问题。如果你想传递重要信息给你的用户,请在使用颜色的同时配上方位和距离,以及图片类东西如图标。这个特别提示也能帮助那些有学习和认知残疾的人辨认和访问你的网站。

自动程序测试

完成这些之后,下一步就是要利用自动测试工具来评测一下看你还遗漏什么没有。WebAim’s WAVE (http://wave.webaim.org) 是测试已知障碍非常好的工具。它们提供了一个Firefox工具栏,这个工具栏对判定对那些需要用户登录进来的互动页面的影响非常方便。 Functional Accessibility Evaluator (fae.cita.uiuc.edu) 对单个页面进行评估,你注册后会提供另外的功能给你。还有些工具如Mozilla Firebug Ainspector ( code.google.com/p/ainspector/) 能扩展这个被广泛使用的Firebug工具为你提供易用性方面的报告。

还有些工具能帮你更好的认识到别人是怎样感知你的网站的。Google的搜索机器人仍然是最大的、最为丰富的盲人网络用户,所以你给你的内容提供的语义信息越多,你的SEO可能就越好——而且你的易用性也越好。

尽管屏幕阅读器不这样工作,但是也值得到Lynx去检测一下一个站点在纯文本状态下是如何显示的,请看en.wikipedia.org/wiki/Lynx_(web_browser)。

模拟测试

把你的鼠标拔了,然后试着去浏览你的网站,这也是个很好的办法。内核主题和前面我们推荐的那些主题都有一个选项可以跳过或直接来到站点的导航链接。 这让只用键盘的用户访问你的网站时更加容易。了解更多信息,请看AIM网站(webaim.org/techniques/keyboard/)。

最为强大,无疑也是最为流行的屏幕阅读器是JAWS;但是,其他免费的屏幕阅读器也迅速的抢占了一些市场。正在使用现代苹果电脑、iPad、或iPhone的每一个设备都内置有VoiceOver。Windows用户可以去下载NVDA(www.nvda-project.org)这个免费程序,Linux用户可以用ORCA (live.gnome.org/Orca)。

视力正常的用户可以学着使用屏幕阅读器去浏览网站,但是盲人用户仅有近似的感受。即使是新近视力损失的人与天生的盲人在浏览网站时感觉也是不同的。 网络开发者通过使用屏幕阅读器来浏览自己的网站可以了解很多东西,但是他们这时是带着关于网站信息结构的视觉理解的。要真正理解如何才能最好的帮助盲人 (或其他残疾用户)浏览你的网站,最好是鼓励那些残疾人自己来反馈信息。

引入WAI-ARIA

WAI-ARIA标准里的一些元素已经引入到了Drupal 7 内核。WAI-ARIA还只是个草案,所以用的还很有限。在没有别的方法将一条重要信息传给屏幕阅读器的时候就用上了它。WAI-ARIA能提供更多工具来为你的网站添加语义信息。

WAI-ARIA的界标角色为HTML的特定区块定义关键词从而传递更多意义给屏幕阅读器。界标允许网络开发者将一个页面分割成几个部分让内容更容 易浏览。Juice Studio Firefox插件能为识别你网站中的界标提供支持(juicystudio.com/article/examining-wai-aria- document-andmark-roles.php)。

Drupal 7和jQuery提供很多互动元素。互动元素越多,添加对ARIA live(实时)区域属性的支持就越为重要,因为这样它们的信息能及时的传回给屏幕阅读器。通过能给动态元素添加诸如 “polite”、"assertive"、"rude"等重要性定义,能指引屏幕阅读器或中断当前正在阅读的文字或等待它完成。

维护是关键

人们常常拿易用性指南当做另外一回事,仅仅用来做个核查然后就完了。从根本是讲,这不是个很有用的做法。真正需要的是一个实践准则而不是一个复选框列表。

WCAG 2.0提供了一套重要的易用性成功标准,但这只是孤立的看来才如此有用。拥有一个极为易用网站(WCAG 2.0 AAA)绝不仅仅是建一个简单的网站。站点应尽量的符合更多成功标准,但是定期的经常性的做持续改进检查以及确保站点持续创建易用内容也是甚为重要。

Drupal是个强大的框架,任何用户创建的内容都可能产生易用性问题。有些模块如HTML Purifier (drupal.org/project/htmlpurifier)能帮助确保所有xHTML是有效的。其他一些模块如Accessible Content module(drupal.org/node/394252)能对Drupal内核提供专门的易用性改进。

定期规律性的检查新旧页面

经常访问的页面应该经常性的用自动化测试工具进行检查,系统安排一些定期的随机测试也能帮助网站尽可能的保持易用。最好是,对于大型网站应定期召集一些受众团体来提供反馈。持续不断的评估检测是网站不断升级以响应不断变化的技术和用户行为的唯一途径。

在测试过程中,讲点策略。在Drupal 7,清除一些未知事物能减少你的测试工作量。完事之后,挑选几个合适的页面也测试一下代表性功能。

征询专家意见

考虑一下引入易用性方面的专业人士来帮助你提升你的网站。发布易用内容的最好方法总是在变化,就如那些残疾朋友用来访问你网站的软硬件经常变化一样。当新标准被开发并采用之后,需要重新考虑我们的最优做法来确保内容能有效的显示。

引入一个外部人员或团队来核查你的网站并查找增强点是划得来的。他们知道怎么找到并清除一些自动化测试工具检测不到的一般障碍。另外,考虑请个残疾人来做这个核查。我们都学习用多个不同方法使用技术,残疾人的体验却是不能完全模拟的。

如果你有任何疑问,你可以提交到易用性群组里面来(groups.drupal.org/accessibility) ,你可以阅读到Drupal易用性倡议文档,地址是:drupal.org/about/accessibility 。

       ■ 提示  所有的链接和资源,还有我们发现的更多东西,请访问 dgd7.org/access。