Drupal8-开发初学者指南-全-
Drupal8 开发初学者指南(全)
原文:
zh.annas-archive.org/md5/45e069f6b4aea3b6f64a530bdcc457b3译者:飞龙
前言
传统的网络,即人们通过桌面电脑查看网站的时代已经过时。现在,用户通过手机、平板电脑甚至手表以无数种方式查看网络内容。互联网上的内容可能根本不是由人类直接消费的——网络服务现在为我们的手机和几乎所有作为新兴和备受赞誉的物联网一部分的设备提供动力。
Drupal 正在演变,以反映整个互联网的变化,Drupal 8 代表了开源内容管理系统的一个重大技术飞跃。其核心焦点正在从构建网站(桌面用户优先)转移到支持现在互联网上内容消费的多种方式。
Drupal 8 默认包含以移动优先、响应式主题。HTML5 内置其中,使得任何支持网络浏览器的设备用户都可以查看 Drupal 内容。RESTful 网络服务包含在 Drupal 8 核心中,这意味着 Drupal 内容可以通过应用程序或其他机器,以及传统的网络浏览器进行消费。
毫不奇怪,Drupal 使用方式的主要变化伴随着作为开发者你将与之合作方式的一些重大变化。本书将引导你了解 Drupal 8 带来的激动人心和深远的变化。你将学习如何在无需编写任何代码的情况下配置 Drupal 来构建复杂、强大的网络应用程序。你还将学习如何创建响应式、移动优先的主题,编写自定义模块,并使用现代增量开发技术管理你的 Drupal 项目。
如果你已经是 PHP 开发者,Drupal 8 将会是一次令人兴奋的旅程。现代面向对象的编程技术,使用了许多 Symfony PHP 框架的元素,将帮助你编写更灵活、健壮和可重用的代码。随着 Drupal 社区越来越优雅和前沿,未来几年它将吸引越来越多的非 Drupal PHP 程序员。无论你是否是 Drupal 社区的一员,现在都是一个参与度更高、更激动人心的时刻。
本书涵盖的内容
第一章,设置 Drupal 开发环境,以专业的方式引导你设置 Drupal 开发环境。你将接触到一系列强大的工具,这些工具将使 Drupal 开发更加高效和有趣,同时也有助于你大幅提高生产力。
第二章,自定义模块开发,深入到代码编写。你将编写面向对象的代码,使你的模块更加灵活和可扩展。本章还将涵盖测试驱动开发,这是部署始终如一执行其功能的 Drupal 网站所需的一切基础。
第三章, Drupal 视图和配置管理,展示了 Drupal 8 的新配置管理系统,它允许你正确地将网站配置与网站内容分离。你将看到内容类型和字段配置现在是如何使用一种名为 YAML 的标记语言来完成的。
第四章, 字段类型 API 简介和自定义字段模块的开发,探讨了 HTML5 支持是如何集成到 Drupal 8 中,以及它提供了哪些新功能。你将构建一个新的自定义字段来展示这些功能。
第五章, Drupal 8 中的主题化,介绍了 Twig 模板引擎,并指导你构建一个完全响应式的主题。本章还将涵盖允许你进一步修改 Drupal 8 默认外观和感觉的主题钩子。
第六章, 提升内容作者的用户体验,展示了 Drupal 8 如何极大地改进了管理界面,在之前的 Drupal 版本中,用户常常被遗忘。Drupal 8 不仅面向最终用户,也面向编辑、版主和网站管理员。
第七章, 将媒体添加到我们的网站,通过向你的网站引入一系列媒体元素,帮助你启动多媒体活动。你将学习如何让你的网站更具吸引力,不仅用文字,还用其他类型的媒体传达所需的信息。
第八章, 它的味道如何? – 获取反馈,通过 Drupal 8 内置的功能引导你从用户那里获取反馈。本章还将涵盖通过构建一个表单来帮助用户与你沟通并提交他们的观点或请求,从而开始双向对话。
第九章, 高级视图开发,深入探讨了视图模块,你将在许多 Drupal 项目中使用它。我们将探讨如何创建一个使用 jQuery 的自定义视图插件。
第十章, Drupal 项目管理与合作,介绍了一个你可以结识一些好朋友的地方——Drupal 社区。我们将探讨协作工具,如何更多地参与 Drupal 社区,以及如何作为团队一起工作,创造一些惊人的成果。
第十一章, 使用搜索 API 模块搜索您的网站,介绍了由搜索 API 提供的强大搜索功能,这是一个扩展 Drupal 标准搜索的框架。我们将看看它单独能实现什么,然后看看 Apache Solr,这是一个第三方搜索引擎,可以使您的搜索功能变得非常快。
第十二章, Drupal 中的 RESTful Web 服务,打破了您 Drupal 开发的界限。您将使用 REST 创建一个自定义 API,并使用 AngularJS 应用程序来消费通过 API 从 Drupal 获取的内容。现在 Drupal 不仅需要为网站提供动力——它几乎可以为任何东西提供动力。
附录, 快速测验答案,涵盖了书中快速测验部分的所有答案。
您需要这本书什么
为了跟随本书中的示例,您需要一个可以设置自己的开发环境并配备一些有用工具的计算机。所有这些工具都是开源的或者有免费的替代品。在第一章, 设置 Drupal 开发环境中提到的 PhpStorm IDE 是一个专有软件,而且相当昂贵,但您也可以使用 NetBeans 或 Eclipse 作为替代的 IDE,因为它们具有类似的功能。
您还需要一个互联网连接。本书中的许多示例依赖于外部服务,如 GitHub 或 PuPHPet,如果您从头开始安装一些推荐的软件包,您将不得不进行一些相当大的下载。
本书面向的人
本书旨在帮助那些想要开始配置、开发或使用 Drupal 8 进行主题设计的人。尽管不需要有之前版本 Drupal 的经验,但如果您已经使用过 Drupal 7,您会发现许多熟悉的 Drupal 概念。
对于以开发为重点的章节,假设您有一些 PHP 的先验知识,并且了解现代 PHP 开发实践。主题章节将假设您熟悉 HTML 和 CSS。一些使用命令行的经验会有所帮助,但不是必需的。
首先,一颗好奇心,愿意犯错的态度,以及投身于伟大的 Drupal 冒险的热情是最重要的。
部分
在这本书中,你会发现几个经常出现的标题(行动时间、刚才发生了什么?、快速测验和英雄试炼)。
为了清楚地说明如何完成一个程序或任务,我们使用以下这些部分:
行动时间 – 标题
-
行动 1
-
行动 2
-
行动 3
指令通常需要一些额外的解释以确保它们有意义,因此它们后面跟着这些部分:
刚才发生了什么?
本节解释您刚刚完成的任务或指令的工作原理。
您还会在书中找到一些其他的学习辅助工具,例如:
快速测验 – 标题
这些是简短的多选题,旨在帮助您测试自己的理解。
尝试一下英雄 – 标题
这些是实际挑战,可以给您提供实验您所学内容的想法。
规范
您还会找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“导航到 Drupal 的模块页面以获取最新打包的tar.gz文件的链接。”
代码块设置如下:
<div>
<ul>
<li>content 1</li>
<li>content 2</li>
</ul>
</div>
任何命令行输入或输出都应如下编写:
git checkout [filename]
git reset --hard
新术语和重要词汇以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,在文本中显示如下:“点击保存然后重新索引您的内容。”
注意
警告或重要注意事项以如下框中的方式显示。
小贴士
技巧和窍门如下所示。
读者反馈
我们欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发您真正能从中获得最大利益的标题。
要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书籍的标题。
如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您充分利用您的购买。
下载示例代码
您可以从www.packtpub.com的账户下载此书的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持选项卡上。
-
点击代码下载与勘误。
-
在搜索框中输入书籍的名称。
-
选择您想要下载代码文件的书籍。
-
从下拉菜单中选择您购买此书的来源。
-
点击代码下载。
您还可以通过点击 Packt Publishing 网站上的书籍网页上的代码文件按钮来下载代码文件。您可以通过在搜索框中输入书籍名称来访问此页面。请注意,您需要登录到您的 Packt 账户。
下载文件后,请确保使用最新版本的以下软件解压缩或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
7-Zip / PeaZip for Linux
本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Drupal-8-Development-Beginners-Guide-Second-Edition。我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
勘误
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过<copyright@packtpub.com>与我们联系,并提供疑似盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过<questions@packtpub.com>联系我们,我们将尽力解决问题。
第一章. 设置 Drupal 开发环境
在本章中,我们将设置 Drupal 框架的开发环境。我们还将从开发者的角度安装和探索 Drupal 8,并了解虚拟开发环境。
在本章中,我们将介绍:
-
为本地开发安装 Drupal
-
为 Drupal 开发设置 PHPStorm IDE
-
在 Drupal 8 中使用 Drush 7 进行安装、配置和使用
-
安装 Drupal 8
-
GitHub 用于 Drupal 开发
-
使用 PuPHPet 安装 Vagrant
为本地开发安装 Drupal
您可以使用不同的 Web 服务器和数据库来安装 Drupal。最常用的组合是 Apache、MySQL 和 PHP,通常被称为AMP 栈。
针对 Drupal,你可以通过两种主要方式来完成这项工作:
-
Acquia Dev Desktop(仅适用于 Mac 和 Windows)
-
AMP:Apache MySQL PHP 的手动安装
这两种方法有以下最低系统要求:
-
磁盘空间: 最小安装需要 15 兆字节。安装包含许多贡献模块和主题的网站需要 60 MB。
-
Web 服务器: Apache、Nginx、Microsoft IIS 或任何具有适当 PHP 支持的其它 Web 服务器。
-
数据库:
-
MySQL 5.5.3/MariaDB 5.5.20/Percona Server 5.5.8 或更高版本,需支持 PDO 和 InnoDB 兼容的主存储引擎
-
PostgreSQL 9.1.2 或更高版本,需支持 PDO
-
SQLite 3.6.8 或更高版本
-
使用 Acquia Dev Desktop 安装 Drupal 的行动时间
如果你使用 Mac 或 Windows,Acquia Dev Desktop 是开发 Drupal 最简单的方法:
-
访问
www.acquia.com/downloads并下载所需的安装程序。![使用 Acquia Dev Desktop 安装 Drupal 的行动时间]()
-
打开
Acquia Dev Desktop文件并开始安装过程。如果你是 Windows 用户,找到你保存 Acquia Dev Desktop 下载的文件夹,双击.exe安装文件。 -
如果你使用 Mac,找到你保存 Acquia Dev Desktop 下载的文件夹。将文件移动到
Application文件夹,双击.dmg安装文件。会出现一个新文件夹,上面有一个写着Acquia Dev Desktop Stack Installer的图标。点击它。允许安装程序在您的 Mac 或 PC 上运行。![使用 Acquia Dev Desktop 安装 Drupal 的行动时间]()
-
读取并同意条款和条件后,让安装程序运行并点击每个步骤的下一步。接下来,你可以选择安装应用程序堆栈(Apache、MySQL 和 PHP)的位置以及安装你的第一个 Drupal 网站的位置。
![使用 Acquia Dev Desktop 安装 Drupal 的行动时间]()
-
在此安装过程中,我们不会直接使用我们的 Drupal 站点,但在其他章节中,如果你决定使用 Dev Desktop 进行开发工作,你也可以从硬盘上的其他位置导入站点。保持默认设置并点击下一步。在下一页,你必须选择 Apache 和 MySQL 的端口号。在大多数情况下,它将无需任何冲突即可工作,但如果你正在使用多个 AMP 堆栈,你可以继续并做出更改。
![操作时间 – 使用 Acquia Dev Desktop 安装 Drupal]()
-
在下一屏,我们设置我们第一个 Drupal 站点的默认值。填写完毕后,点击下一步。确保你记下用户名和密码。你需要这些信息来登录你的 Drupal 站点。
![操作时间 – 使用 Acquia Dev Desktop 安装 Drupal]()
为第一个 Drupal 站点创建凭证
-
点击下一步开始安装过程。一旦安装完成,点击完成,这将打开Acquia Dev Desktop 控制面板。
![操作时间 – 使用 Acquia Dev Desktop 安装 Drupal]()
Acquia Dev Desktop 控制面板
-
点击访问我的站点来访问你的新站点。使用管理我的数据库浏览到PHPMyAdmin。
![操作时间 – 使用 Acquia Dev Desktop 安装 Drupal]()
设置站点的登录页面
发生了什么?
你已经使用 Acquia Dev Desktop 安装了 Drupal。从这里,你可以开始在你的新 Drupal 站点上工作。
注意,该站点正在使用 Acquia Drupal 运行,这略有不同,因为你将从中下载www.drupal.org/。在 Windows 上安装 Acquia Dev Desktop 几乎相似。
本地主机方式安装 Drupal
现在我们将介绍 Mac OS X 和 Windows 操作系统的 AMP 包的安装步骤。
操作时间 – 安装 Mac OS X AMP 堆栈
对于 Mac OS X,我们将使用 MAMP 包来安装 AMP 堆栈。
-
首先,从
www.mamp.info/en下载最新的 MAMP 版本。一旦 MAMP 下载完成,双击下载的.pkg文件。将文件移动到应用程序文件夹,并双击 MAMP 的.pkg文件。这将启动 MAMP 安装程序。系统安装程序将引导你完成安装过程。 -
完成安装过程后,启动你的本地服务器。
![操作时间 – 安装 Mac OS X AMP 堆栈]()
-
启动 MAMP 并点击启动服务器按钮。你将在右上角看到服务器状态。点击打开 WebStart 页面按钮。你将看到默认的 MAMP 起始页面,其中包含访问如 phpMyAdmin、phpInfo、SQLite Manager、phpLiteAdmin、FAQ、MyFavoriteLink 和 MAMP 网站的链接。
![操作时间 – 安装 Mac OS X AMP 堆栈]()
默认的 MAMP 起始页面
-
安装成功后,你可以启动你的本地服务器。启动 MAMP 并点击 启动服务器 按钮。在右上角的状态显示中,会显示服务器的启动状态。如果需要,你将需要输入你的管理员密码。
小贴士
在 Mac OS X 上设置开发环境的过程与为 Linux 发行版设置开发环境非常相似。它们都是基于 Unix 的操作系统。如果你还没有绑定到特定的 Linux 发行版,并想在 Linux 上设置开发环境,那么我强烈推荐 Ubuntu。有关设置 Drupal 开发环境的优秀指南可在
help.ubuntu.com/community/Drupal找到。
发生了什么?
恭喜!你现在已经在你的 Mac 上安装了一个可工作的 AMP 栈。
行动时间 – 安装 Windows AMP 栈
对于 Windows,我们将使用 XAMP:
小贴士
我的大部分 Drupal 开发都是在 Mac OS 或 Ubuntu 上进行的。基于 Unix 的操作系统更适合 Drupal 开发,因为 Drupal 有许多以 Unix 为中心的开发方面。从 cron 到基于 .htaccess 的干净 URL,drupal.org/ 上的大量文档都会偏向于 Unix 操作系统。
-
从
www.apachefriends.org/download.html下载 XAMPP 的最新版本。下载完成后,双击.exe文件来安装程序。接受默认设置并完成安装过程。你可以在任何时候通过编辑配置文件来更改设置。 -
完成安装过程后,启动 XAMPP 控制面板。
![行动时间 – 安装 Windows AMP 栈]()
XAMPP 控制面板
-
在 XAMPP 控制面板应用程序中,点击 Apache 和 MySQL 旁边的 启动 按钮。现在打开您喜欢的网页浏览器,导航到
http://localhost,你应该会看到以下截图类似的内容:![行动时间 – 安装 Windows AMP 栈]()
-
XAMPP 不适用于生产使用,仅适用于开发环境。在发布任何内容到线上之前,请确保 XAMPP 的安全性。你应该访问 URL
http://localhost/security/。使用安全控制台,你可以为 MySQL 用户root和phpMyAdmin设置密码。
发生了什么?
恭喜!你现在已经在你的 Windows PC 上安装了一个可工作的 AMP 栈。而且你也在你的 Mac 上安装了一个可工作的 AMP 栈!
PHP 配置
Drupal 8 推荐使用 PHP 版本 5.4 或更高版本,并带有 CURL 扩展。MAMP 的最新版本包含 PHP 版本 5.5(它还包含一个较旧的 PHP 版本,并允许你在它们之间切换)。Windows 版本的 XAMPP 最新版本包含 PHP 版本 5.。尽管这个版本的 PHP 满足 Drupal 7 的要求,但在我们安装 Drupal 之前,需要调整一些与 PHP 相关的设置,以确保一切运行顺畅。PHP 的要求列表来自 Drupal 8,网址为 https://www.drupal.org/requirements/php。
行动时间 - 修改 php.ini 设置
要修改 Mac OS X 和 Windows 中的 php.ini 设置:
-
Mac OS X:使用你喜欢的文本编辑器打开位于
/Applications/MAMP/bin/php/php5.4.6/conf的php.ini文件。 -
Windows:使用你喜欢的文本编辑器打开位于
C:\xampp\php的php.ini文件。我喜欢 Notepad++。
在 Mac OS X 和 Windows 中导航到相应的文件夹位置,并编辑设置以匹配以下值:
max_execution_time = 60;
max_input_time = 120;
memory_limit = 128M;
error_reporting = E_ALL & ~E_NOTICE
发生了什么?
恭喜!你已在 Mac OS X 和 Windows 系统中成功修改了 php.ini 文件。
修改 MySQL my.cnf 设置
要修改 Mac OS X 和 Windows 中的 MySQL my.cnf 设置:
-
Mac OS X:MAMP 默认不使用
my.cnf文件。因此,你必须将位于/Applications/MAMP/Library/support-files/my-medium.cnf的文件复制到/Applications/MAMP/conf/my.cnf(注意文件的新名称)。 -
Windows:对于 XAMPP,打开位于
C:\xampp\mysql\bin的my.ini文件。小贴士
在开始编辑此文件之前,你可能想要备份一份。
行动时间 - 为 Drupal 设置 MySQL
在你的文本编辑器中打开 my.cnf/my.ini 文件,找到并编辑以下设置以匹配这些值:
# * Fine Tuning
#
key_buffer = 16M
key_buffer_size = 32M
max_allowed_packet = 16M
thread_stack = 512K
thread_cache_size = 8
max_connections = 300
Drupal MySQL 配置的一个真正目标是 max_allowed_packet 设置。这过去一直是我和其他许多我知道的 Drupal 开发者遇到令人困惑的错误的原因,并且这个设置在 drupal.org/requirements#database 页面的数据库服务器部分被特别提及。
当你准备好将你的网站上线时,在 drupal.org/node/2601 可以找到一些优秀的性能调整技巧。
发生了什么?
恭喜!你已在 Mac OS X 和 Windows 系统中成功更新了 MySQL 设置。
行动时间 - 创建一个空 MySQL 数据库
在我们安装 Drupal 之前,我们需要创建一个新的空 MySQL 数据库。
MAMP 和 XAMPP 都包含 phpMyAdmin - 一个基于 Web 的 MySQL 管理工具。我们将使用 phpMyAdmin 为 Drupal 创建一个空数据库。
-
Mac OS X:当 MAMP 运行时,打开你喜欢的网页浏览器并转到
http://localhost:8888/phpMyAdmin。 -
Windows:当 XAMPP 运行时,打开你喜欢的网页浏览器并转到
http://localhost/phpmyadmin/。
你将看到以下屏幕:

刚才发生了什么?
您已安装了一个专门为 Drupal 配置的完全功能的 AMP 堆栈,并且您已经创建了一个空的 MySQL 数据库作为安装 Drupal 的初步步骤。
安装 Git
Git 是一款免费的源代码控制和版本管理软件,在过去的几年中变得非常流行。2011 年 2 月,Drupal(www.drupal.org/)从过时的 CVS 版本控制系统迁移到 Git。迁移到 Git 为 Drupal 开发者与 Drupal(www.drupal.org/)的交互开辟了全新的方式,我们将在整本书中强调这种增强的交互。然而,我们也将立即开始使用 Git 来简化 Drupal 开发环境的设置。所以,如果您电脑上还没有安装 Git,让我们来设置它。
执行动作 – 安装 Mac OS X 版本的 Git
要为 Mac 安装 Drush,我们将使用 Homebrew(一个 Mac OS X 的开源软件包管理器),安装说明可在brew.sh/找到。
一旦安装了 Homebrew,安装 Git 就像打开终端应用程序(在/Applications/Utilities中)并输入以下命令一样简单:
brew install git
执行动作 – 安装 Windows 版本的 Git
从git-scm.com/downloads下载 Windows 的安装程序。双击.exe文件来安装程序。
-
点击下载的文件链接以开始安装过程。
![执行动作 – 安装 Windows 版本的 Git]()
Git 设置向导
-
点击下一步,然后在GNU 通用公共许可证屏幕上再次点击下一步。
-
在选择目标位置屏幕上,点击下一步以接受默认的目标位置。
-
在选择组件屏幕上,再次接受默认设置并点击下一步。
-
在调整您的 PATH 环境屏幕上,选择从 Windows 命令提示符使用 Git 和可选的 Unix 工具,因为这将允许 Git 与 Drush 一起工作,我们将在下一章中介绍。
![执行动作 – 安装 Windows 版本的 Git]()
从命令行选择如何使用 Git
刚才发生了什么?
您已安装了 Git 版本控制系统——这是一个将极大地简化与 Drupal(www.drupal.org/)现有贡献代码交互的工具。
安装 Drush
Drush 是由 Drupal 和 shell 两个词组合而成的,是一个命令行工具,它可以从您喜欢的 shell(Mac OS X 和 Ubuntu 上的终端应用程序,以及 Windows 上的命令提示符应用程序)中方便地管理 Drupal 环境。例如,在 Drupal 上安装一个贡献模块([www.drupal.org/](https://www.drupal.org/))可以通过在命令行运行以下命令变得非常简单:
drush dl modulename
drush en modulename -y
安装 Drush for Mac OS X 的行动时间
要在 Mac 上安装 Drush,我们将再次使用 Homebrew,详情请见 https://www.drupal.org/node/954766。
在终端中输入以下命令:
brew install drush
安装 Drush for Windows 的行动时间
要在 Windows 上安装 Drush,我们需要使用提供类似 Linux 环境的 Cygwin。
我们现在将使用 Composer 在 Cygwin 中安装 Drush:
-
下载并安装 Cygwin
-
在包部分安装以下包:
-
ncurses:这是一个用于显示基于文本的用户界面的库。 -
git:这是一个版本控制包 -
bsdtar:这是用于解压缩 tar 文件的工具。 -
curl:这是一个客户端 URL 传输包。
-
-
现在安装 Composer,这是一个 PHP 依赖项包管理工具,使用 Composer 安装程序。
-
从 Cygwin 终端,输入以下命令来安装 Drush 8:
composer global require drush/drush:8.*
注意
在 www.cygwin.com/ 了解更多关于 Cygwin 项目的信息。有关安装 Drush 的更多信息,请查看在 Windows 上安装 Drush 的简单方法:https://www.drupal.org/node/594744
安装 Drupal 8
好的,现在我们正在取得进展。现在我们已经创建了数据库,安装了 Git,并安装了 Drush,我们已经为安装 Drupal 8 准备好了一切。
安装 Drupal 8 的行动时间
我们将使用 Drush 和 Git 的组合来安装 Drupal:
-
Mac OS X: 打开终端应用程序,并输入以下命令:
cd /Applications/MAMP/htdocsWindows: 打开命令提示符并输入以下命令:
cd C:\xampp\htdocs -
现在,我们将使用 Git 将 Drupal 核心 Git 仓库本地克隆到新的
d8dev文件夹中(这需要几分钟,具体取决于您的网络带宽):$ git clone --branch 8.1.x http://git.drupal.org/project/drupal.git d8dev Cloning into 'd8dev'... remote: Counting objects: 456756, done. remote: Compressing objects: 100% (95412/95412), done. remote: Total 456756 (delta 325729), reused 445242 (delta 316775) Receiving objects: 100% (456756/456756), 100.04 MiB | 8.60 MiB/s, done. Resolving deltas: 100% (325729/325729), done. cd d8dev -
接下来,我们想使用 Git 切换到最新的 Drupal 8 版本。首先,我们将列出所有可用的版本:
8.0-alpha1 8.0-alpha10 8.0-alpha11 8.0-alpha12 8.0-alpha13 8.0-alpha2 8.0-alpha3 8.0-alpha4 8.0-alpha5 8.0-alpha6 8.0-alpha7 8.0-alpha8 8.0-alpha9 8.0.0 8.0.0-alpha14 8.0.0-alpha15 8.0.0-beta1 8.0.0-beta10 8.0.0-beta11 8.0.0-beta12 8.0.0-beta13 8.0.0-beta14 8.0.0-beta15 8.0.0-beta16 8.0.0-beta2 8.0.0-beta3 8.0.0-beta4 8.0.0-beta5 8.0.0-beta6 8.0.0-beta7 8.0.0-beta8 8.0.0-beta9 8.0.0-rc1 8.0.0-rc2 8.0.0-rc3 8.0.0-rc4 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.1.0 8.1.0-beta1 8.1.0-beta2 8.1.0-rc1 8.1.1 8.1.2 8.1.3 -
您将看到最新版本是
8.1.3,但您应该用您最新的版本替换它,并在以下 Git 命令中使用它:git checkout 8.1.3 Note: checking out '8.1.3'. You are in the detached HEAD state. You can look around, make experimental changes, and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at 079a52b. Revert Issue #2457653 by Gábor Hojtsy: System.site langcode is both used as a file language code and a site language code.提示
通过使用 Git,我们将我们的 Drupal 下载链接到 Drupal 的 Git 仓库,以便于我们本地核心 Drupal 安装。这将简化未来 Drupal 核心更新的更新过程。
Drupal 8 使用 Symfony 组件,这些组件通过供应商目录进行管理,并需要使用 Composer 在 Drupal 仓库中下载。对于通过 Git 克隆的 Drupal 仓库,您需要运行 composer install,这将下载供应商目录。
接下来,我们将通过基于网页的安装过程来设置 Drupal:
-
Mac OS X: 在您的网页浏览器中打开
http://localhost:8888/d8dev/ -
Windows: 在您的网页浏览器中打开
http://localhost/d8dev/
-
-
您应该看到以下屏幕:
![安装 Drupal 8 的行动时间 – 安装屏幕]()
选择您的语言并点击 保存并继续。
-
选择标准配置文件,然后点击保存并继续。
![行动时间 - 安装 Drupal 8]()
-
在数据库配置屏幕上,选择MySQL。将数据库名称输入为
d7dev,将数据库用户名和密码输入为root。点击保存并继续。您将看到网站安装屏幕。对于本地开发网站,使用根 MySQL 用户相当方便。然而,对于实时/生产网站,您应该始终为您的 Drupal 数据库创建一个唯一的 MySQL 用户和密码。
-
在配置网站屏幕上,输入以下值(电子邮件地址不必是真实的,但必须看起来有效):
-
网站名称:
d8dev -
网站电子邮件地址:一个有效的电子邮件地址
-
用户名:
admin -
密码和确认密码:
admin(这只是一个开发环境,所以让它简单一些) -
电子邮件地址:与网站电子邮件地址相同
填写表单的其余部分并点击保存并继续。您的 Drupal 网站安装已完成。因此,点击访问您的网站链接查看网站。
![行动时间 - 安装 Drupal 8]()
-
发生了什么?
您使用 Git 将全新的 Drupal 8 网站搭建并运行起来。在接下来的章节中,您将看到 Git 以及 Drush 对于日常 Drupal 开发是多么有用。
安装 PHPStorm IDE
PHPStorm 已经成为开发 Drupal 模块和主题时最友好的 IDE 之一,详情请见confluence.jetbrains.com/display/PhpStorm/Drupal+Development+using+PhpStorm。
行动时间 - 安装 PHPStorm IDE
前往 www.jetbrains.com/phpstorm/download/,下载适用于您操作系统的 PHPStorm IDE 正确版本的免费 30 天试用版。下载完成后,双击(Windows 和 Mac)文件,并按照页面上的说明按照文档安装 PHPStorm。
小贴士
您绝对不必觉得您必须使用 PHPStorm。市面上有许多其他优秀的 IDE,您可能已经在使用不同的 IDE,或者可能只是喜欢使用您最喜欢的文本编辑器。然而,本书将使用 PHPStorm,如果您也在使用 PHPStorm,可能会更容易跟随。
PHPStorm 默认激活了 Drupal 开发插件。否则,您可以在设置对话框中启用它。
行动时间 - 创建新的 PHPStorm 项目
当您第一次打开 PHPStorm 时,您应该看到以下屏幕:

-
点击创建新项目按钮,选择PHP 项目,然后点击下一步。此时,您将看到新 PHP 项目窗口:
![行动时间 - 创建新的 PHPStorm 项目]()
-
浏览到您的新 Drupal 8 项目。对于 Windows,它是
C:\xampp\htdocs\d7dev,对于 Mac OS X,它是/Applications/MAMP/htdocs/d7dev。在您选择了新 Drupal 7 安装的位置后,点击完成。
Drupalize PHPStorm IDE
一旦在 PHPStorm 中为现有项目或新 Drupal 模块设置了一个项目,IDE 就会检查开发环境是否已配置为 Drupal。它将通过弹出窗口和事件日志中的事件建议您启用 Drupal 支持。

点击启用 Drupal 支持链接将打开配置对话框。您可以更改 Drupal 项目安装路径,并选择版本号8用于 Drupal 8。

您还可以通过打开首选项对话框来稍后更改 Drupal 集成设置。

要获取关于 Drupal 与最新版 PHPStorm 集成的最新信息,请访问confluence.jetbrains.com/display/PhpStorm/Drupal+Development+using+PhpStorm。
从开发者的角度看 Drupal
如果您是 Drupal 开发的初学者,您首先应该理解的是,成为一名优秀的 Drupal 开发者意味着成为 Drupal 社区的一部分。Drupal 开发是一个非常开放源代码的过程,因此它是一个社区驱动的过程。
Drupal 拥有众多资源来帮助所有经验水平的开发者。然而,在您能够利用所有这些资源之前,您需要成为 Drupal(www.drupal.org/)的会员。所以,如果您还没有加入 Drupal,现在是时候在drupal.org/user/register注册了。一旦您成为会员并登录到 Drupal(www.drupal.org/),您将看到以下屏幕:

随着我们继续进行 Drupal 8 开发,我们将多次回到您的 Drupal(www.drupal.org/)仪表板,以跟踪我们使用的贡献模块的问题。我们还将利用 Drupal 在迁移到 Git 时添加的新沙盒开发功能。
安装 Vagrant
作为开发者,很多时候我们需要设置一个与生产 Web 服务器类似的本地 AMP 堆栈版本。Vagrant 允许开发者一次性构建虚拟化环境,并可以持续共享。
Vagrant 是用 Ruby 编写的,所以如果你不熟悉 Ruby,编写 Vagrant 文件可能会很有挑战性。这就是 PuPHPet 发挥作用的地方。使用 PuPHPet,我们可以生成包来创建一个虚拟机,就像我们在 PuPHPet 网站上指定的那样。我们只需配置它即可。
行动时间 - 安装 Vagrant
要安装 Vagrant,从 www.virtualbox.org/wiki/Downloads 下载适用于您的系统适当的 Vagrant 包和 VirtualBox:
-
创建位置:选择您希望保存虚拟机的地方。此位置与您保存虚拟机配置和文件的地方无关。创建一个文件夹,您决定在哪里存储您的虚拟机。例如,在 Windows 中,我们可以使用
C:/drupalvm。 -
安装 VirtualBox:从
www.virtualbox.org/wiki/Downloads下载并安装适用于您的操作系统包的最新版本的 VirtualBox。 -
安装 Vagrant:安装 Vagrant 很简单。只需从
www.vagrantup.com/downloads.html下载您操作系统特定的包。遵循操作系统特定的正常安装过程。 -
在 PuPHPet 中创建您的虚拟机:到目前为止,我们已经安装了 Vagrant 和 VirtualBox。现在让我们在 PuPHPet 中创建我们的虚拟机配置。打开 URL
puphpet.com/。在这里,我们将从我们的虚拟机中选择所需的内容。 -
虚拟机设置:在第一页,点击 立即开始。这将引导您到以下页面。在这里,我选择了 Ubuntu Trusty 14.04 LTS x64 作为发行版。您可以根据您的服务器选择选择您的发行版和其他配置。如果您不了解它们,可以保留其他设置为默认值。
![执行时间 – 安装 Vagrant]()
选择适合您的机器的发行版
一旦您选择了您喜欢的软件包,请点击 系统软件包 按钮。
在接下来的四个步骤中,您只需保持设置不变,然后点击右侧的按钮继续前进。
您应该会得到 Nginx 选项。现在我不想用 Nginx 作为 Web 服务器。相反,我选择了 Apache。
只需浏览到页面顶部并取消选择 安装 Nginx。这将折叠 Nginx 配置表单,并将按钮带到 Apache 配置页面。

选择您偏好的 Web 服务器
您可以选择您的 Web 服务器配置,然后点击下一步以选择 PHP 配置。在这里,我选择了 安装 Xdebug,这是一个用于调试器的良好工具,以及 PHP 的性能分析工具。
之后,我只是点击了其他语言配置而没有做任何更改,然后继续到 MySQL。

选择数据库
在这里,您可以选择 MySQL 版本和 root 密码,添加更多用户,并创建新的数据库。如果您喜欢,还可以探索其他数据库。
再次,继续前进,直到您到达 创建存档,您就完成了。下载您的自定义服务器配置!这将下载一个包含您自定义虚拟机配置的 ZIP 文件。
-
设置你的虚拟机:解压缩你刚刚下载的 ZIP 文件,并导航到解压缩 ZIP 文件的位置。
在 Mac OS X 或 Ubuntu 中打开终端。在 Windows 中,你应该以管理员权限打开命令行。
在终端中,浏览到文件夹位置并输入以下内容:
vagrant up按Enter键。这可能需要几分钟,你应该会看到一个消息,表示一切安装完成。
-
设置主机:在我们开始使用虚拟机之前,这是设置主机名的最后一步。对于 Drupal 来说,始终使用正确的域名更好。为了创建虚拟主机,我们需要更改计算机上的主机文件。根据操作系统浏览以下文件夹:
-
Windows:
C:\windows\system32\drivers\etc\hosts -
Linux (Ubuntu):
/etc/hosts -
Mac:
/private/etc/hosts192.168.1.32 drupal8.dev www.drupal8.dev
你可以使用具有管理员权限的喜欢的编辑器来更改主机文件。我选择使用
drupal8.dev作为我的域名。所以让我们将以下行添加到主机文件中:我们在 PuPHPet 上创建虚拟机配置文件时使用了这个 IP 地址。如果你现在不记得它,你可以通过在记事本中打开
config.yaml来找到它。config.yaml文件位于你之前放置在虚拟机文件夹中的puphet文件夹内。你应该会在private_network旁边看到 IP 地址。 -
我们刚刚在我们的计算机上完成了一个虚拟机的配置。你可以打开浏览器并输入你的域名—drupal8.dev。
发生了什么?
你刚刚为项目开发安装了 Vagrant。现在你可以构建一个虚拟机,这是你的实际生产服务器。你可以根据不同的项目需求切换到不同的虚拟机。你需要安装一个新的虚拟机的是 PuPHPet 的配置文件。
摘要
你已经到达了本章的结尾!你现在应该有一个可以工作的 Drupal 8 网站和 PHPStorm IDE,可以开始定制 Drupal 开发。我们还没有编写实际的代码,所以我非常期待开始一些开发示例。然而,这一章已经为我们提供了开发过程中所需的工具和配置系统,使开发过程更加容易和愉快。现在,我们准备好开始认真地开发 Drupal 了。在下一章中,我们将创建一个新的内容类型和一个基本的自定义模块,以更改我们内容类型中的一个字段。
第二章. 自定义模块开发
我们在第一章中设置了我们的开发环境,设置 Drupal 开发环境。现在让我们开始创建一个新的内容类型,并创建一个基本的自定义模块来更改内容类型上的一个字段。我们还将探讨测试驱动开发作为良好的编程实践的概念。
在本章中,我们将学习以下内容:
-
定义自定义内容类型(Recipe)
-
Drupal 8 面向对象编程的基础
-
Drupal 8 与 Symfony 的介绍
-
Drupal 8 模块开发的基础
-
字段格式化 API
-
测试驱动开发(TDD)
-
使用内置的 PHPUnit 测试框架编写和运行自定义模块功能的测试
-
在 PHPStorm IDE 中配置和运行单元测试,并生成代码覆盖率报告
创建自定义 Recipe 内容类型
在上一章中,我们在系统中安装了 Drupal 8。现在我们将创建我们的自定义食谱内容类型。在此之前,我们将讨论 Drupal 8 中新增的五个字段:
-
日期:日期字段是 Drupal 7 中的日期模块。我们可以选择记录日期和时间或仅日期作为选项。
-
电子邮件:日期模块很简单,但电子邮件字段更简单。电子邮件字段没有任何设置。
-
链接:链接字段允许内部和外部链接,以及链接文本选项。
-
电话:电话字段在 Drupal 8 中默认禁用,因此我们需要启用模块才能使用它。它没有设置。它实际上只是一个添加
<a href="tel:将文本转换为电话链接的文本字段。 -
引用:引用模块是最强大的字段。我们可以链接到任何实体,这意味着我们可以链接到评论、内容、块、文件、术语和用户。
现在登录到您的 Drupal 8 网站。您应该看到一个新管理工具栏。
动手实践 – 创建自定义内容类型
让我们按照以下步骤创建一个自定义内容类型:
-
点击管理工具栏中的结构,然后点击内容类型。
![动手实践 – 创建自定义内容类型]()
-
在内容类型屏幕上,点击添加内容类型链接。
![动手实践 – 创建自定义内容类型]()
-
在名称文本字段中写入
Recipe。 -
在描述中输入此文本
基于 schema.org base HTML5 Microdata schema 的简单食谱内容类型,位于:http://schema.org/Recipe/。 -
对于标题字段标签,输入
name。![动手实践 – 创建自定义内容类型]()
-
点击保存和管理字段按钮。目前我们将使用默认的内容类型配置来配置其他所有内容。
-
接下来,通过点击删除链接,然后确认点击下一屏幕上的删除按钮,删除自动添加到我们的内容类型中的正文字段。
![创建自定义内容类型的时机]()
-
现在,我们将向我们的食谱内容类型添加一些新的字段。我们将使用食谱模式属性名称作为字段名称。在
http://schema.org/Recipe/表格中列出的第一个属性是description。一旦您点击添加字段按钮,您将获得一个选项添加新字段选择框。在那里您需要选择文本(格式化,长文本,带摘要)。在标签上,您需要写上description。![创建自定义内容类型的时机]()
-
然后点击保存并继续。在下一屏幕上,将允许的值数限制为
1,并点击保存字段设置按钮。![创建自定义内容类型的时机]()
-
在食谱描述设置页面,将
A short description of the item作为帮助文本输入。接受其余的默认设置,然后点击页面底部的保存设置按钮:![创建自定义内容类型的时机]()
-
现在,我们将继续到图像属性。我们将为此属性使用一个现有的字段。在重用现有字段部分,选择Image: field_image (Image)。
![创建自定义内容类型的时机]()
-
点击保存并继续按钮以接受字段设置页面上的默认设置。
-
在下一页,点击保存设置按钮以接受食谱的图像设置和图像字段设置。
-
DatePublished和Author属性将由核心 Drupal 节点属性捕获。我们暂时将CreativeWork 属性的其余部分跳过。
-
现在,添加一个新的数字字段
cookTime,标签为cookTime,字段类型为整数,从下拉菜单中选择。 -
点击保存并继续按钮以接受字段设置页面上的默认设置。
-
在下一页,将
The time it takes to actually cook the dish in minutes.作为帮助文本输入。在cookTime 设置页面下,将minute|minutes作为后缀输入,并点击保存设置按钮。![创建自定义内容类型的时机]()
-
除了cookingMethod之外,我们还将暂时跳过nutrition、recipeCategory、recipeCuisine和totalTime属性。我们将在本书的后面添加这些属性。
-
对于ingredients属性,设置将是标签:ingredients,从添加新字段下拉菜单中,你可以选择字段类型:文本(纯文本)。点击保存并继续按钮。在下一屏幕中,接受最大长度为 255 的默认设置和允许的值数量为无限,然后点击保存字段设置。
-
在食谱的成分设置页面,输入
食谱中使用的成分作为帮助文本。接受其余的默认设置,并点击页面底部的保存设置按钮。 -
对于prepTime属性,设置将是标签:prepTime,从添加新字段下拉菜单中,你可以选择字段类型:数字(整数)。点击保存并继续按钮以接受字段设置页面的默认设置。在下一页,输入准备食谱所需的时间(分钟)作为帮助文本,在prepTime 设置页面下输入
minute|minute作为后缀,然后点击保存设置按钮。 -
接下来添加recipeInstructions属性。点击添加字段按钮。在下一屏幕中,选择字段类型为文本(格式化,长文本)和标签为recipeInstructions。然后点击保存并继续按钮。在下一页,保留所有设置默认并点击保存按钮。在下一设置页面,输入
制作菜肴的步骤。作为帮助文本,并保留其余设置,然后点击保存按钮。 -
对于recipeYield属性,设置将是标签:recipeYield,从添加新字段下拉菜单中,你可以选择字段类型:文本(纯文本)。点击保存并继续按钮。在下一屏幕中,接受最大长度为 255 和允许的值数量为限制到1的默认设置,然后点击保存字段设置。
-
在食谱的 recipeYield 设置页面,输入
该食谱产生的数量(例如,服务人数,份量等)。作为帮助文本。接受其余的默认设置,并点击页面底部的保存设置按钮。 -
您现在应该有一个类似于以下截图的管理字段屏幕,用于我们的食谱内容类型:
![行动时间 – 创建自定义内容类型]()
发生了什么?
您已创建新的食谱内容类型并添加了新字段。
行动时间 – 添加新食谱
现在我们已经创建了新的食谱内容类型并修改了其字段,让我们通过点击快捷栏中的添加内容链接来创建一个新的食谱,然后点击食谱链接。
前往路径管理 | 内容并点击以下截图所示的添加内容按钮:

这里是我的 Awesome Sauce 食谱,你可以使用它,但欢迎你添加任何你喜欢的食谱:
-
名称:
Awesome Sauce -
描述:
一种美味甜辣酱,让你的任何食物都变得更加出色。一点就能走得很远... -
配料:
-
一个鬼椒(可选) -
两个哈瓦那辣椒 -
三个泰国辣椒 -
四个墨西哥辣椒 -
四个大蒜瓣 -
三杯米醋 -
一茶匙鱼露 -
一杯糖
-
-
食谱说明:
-
1. 剥去辣椒的茎。 -
2. 将辣椒和大蒜加入食品加工机中,搅拌至成泥。 -
3. 将醋、糖、鱼露和果泥加入一个小锅中,用低温煮沸。 -
4. 煮酱 20 到 30 分钟,直到糖完全溶解。 -
5. 将锅从炉子上取下,静置 10 分钟。 -
6. 你的 Awesome Sauce 已经准备好供食用,或者可以冷藏长达三周。
泰国辣椒和鱼露通常在大多数亚洲市场都有售。鬼椒通常被认为是世界上最辣的辣椒,对于那些对辣味不太耐受的人或者找不到它们的情况下可以省略。
-
-
产量:
12 份 -
准备时间:10 分钟
-
烹饪时间:30 分钟
![行动时间 – 添加新食谱]()
当你完成时,保存它以查看你的新食谱页面。
刚才发生了什么?
我们已经将我们的食谱添加到了网站上。
Drupal 中的 OOP 概念
在我们深入开发第一个自定义模块之前,让我们了解现代面向对象编程(OOP)的基础,这是 Drupal 8 采用的,以便让使用其他纯 PHP 框架的开发者更加熟悉。面向对象的设计模式已被用于实现各种 Drupal 概念,如字段、视图、实体和节点。
虽然 OOP 的学习曲线很陡峭,包括掌握诸如继承和多态等关键编程技术,但与过程式程序相比,它被发现更容易扩展、重构和维护,从长远来看,这让你可以专注于编程部分,而不是浪费时间在维护问题上。依赖注入是 Drupal 8 中广泛使用的 OOP 设计模式之一。对这个概念的基本理解对于访问和使用一些核心 API 至关重要。以下是它们:
-
对象:一个对象是一个由类定义的数据结构的单个实例。一个类只定义一次,然后你创建一个对象使其属于一个类。换句话说,一个类可以表示为一种对象类型。它是一个蓝图,你可以从中创建一个单独的对象。一个类由三个主要组件组成:属性、名称和操作。以下是一个小的 PHP 示例:
<?php class foo { function call_foo() { echo "Calling foo."; } } $bar = new foo; $bar->call_foo(); ?> -
抽象:面向对象编程的核心原则之一,抽象指的是以任何类型的数据表示,你可以保持实现的细节抽象或隐藏。它允许你编写与列表、数组和其他数据类型等抽象数据结构无缝工作的代码。由于代码在处理不同数据类型时保持不变,你可以编写一个新的数据类型,并使其与程序一起工作,而无需对其进行更改。以下是一个小的 PHP 示例:
<?php abstract class AbstractClass { // Force Extending class to define this method abstract protected function getVal(); abstract protected function prefixVal($prefix); // Common method public function printOut() { print $this->getVal() . "\n"; } } class ConcreteClass1 extends AbstractClass { protected function getVal() { return "ConcreteClass1"; } public function prefixVal($prefix) { return "{$prefix}ConcreteClass1"; } } $class1 = new ConcreteClass1; $class1->printOut(); echo $class1->prefixVal('FOO_') ."\n"; ?> -
封装:也等同于信息隐藏,封装基本上是将对象功能所需的全部资源合并在一起的过程,包括方法和数据。这个过程是通过创建帮助您公开方法和属性的类来实现的。一个类被视为一个胶囊或容器,它封装了属性和属性以及一组方法,以便为其他类提供特定的功能。以下是一个小的 PHP 示例:
<?php class Application { private static $_user; public function User( ) { if( $this->_user == null ) { $this->_user = new UserData(); } return $this->_user; } } class UserData { private $_name; public function __construct() { $this->_name = "Krishna kanth"; } public function GetUserName() { return $this->_name; } } $app = new Application(); echo $app->UserData()->GetUserName(); ?> -
多态:多态的名字来源于多种形状,指的是请求可以由不同类型的事物执行的同一种操作的功能。有三种主要技术可以用来实现多态:方法重载、方法覆盖和运算符重载。以下是一个小的 PHP 示例:
文件
ShpeInterface.php的内容:<?php // Create Shape interace with calculateArea() method. interface Shape { public function calculateArea(); } ?>文件
Circle.php的内容:<?php require ShapeInterface.php; // Create Circle class that implement Shape interface. class Circle implements Shape { private $radius; public function __construct($radius) { $this -> radius = $radius; } // calcArea calculates the area of circles public function calculateArea() { return $this -> radius * $this -> radius * pi(); } } ?> -
继承:你可以通过扩展现有类来创建一个新的类,这个过程被称为继承。在对象之间使用的关键关系之一是特殊化,它是通过继承原则实现的。以下是一个小的 PHP 示例:
<?php // Declare the interface 'iTemplate' interface iTemplate { public function setVariable($name, $var); public function getHtml($template); } // Implement the interface // This will work class Template implements iTemplate { private $vars = array(); public function setVariable($name, $var) { $this->vars[$name] = $var; } public function getHtml($template) { foreach($this->vars as $name => $value) { $template = str_replace('{' . $name . '}', $value, $template); } return $template; } } ?>
现在,我们将通过开发我们新的自定义模块来展示前面提到的概念。
动手实践 – 在 Drupal 8 中开发自定义模块
在这里,我们将创建一个自定义模块,该模块将在路径mypage/page下的页面上打印Hello World。在讨论 Drupal 根目录结构之前,我们将讨论在 Drupal 8 中自定义模块开发的基本步骤。

查看前面的截图。我们将看到每个目录包含的内容:
-
/core:所有由核心提供的文件,没有明确理由放在/目录下。 -
/modules:所有自定义和贡献模块放入的目录。将它们分成子目录 contrib 和 custom 可以使跟踪模块更容易。 -
/profiles:这个文件夹包含贡献的和自定义的配置文件。 -
/themes:- 贡献的和自定义的(子)主题。 -
/sites/[domain OR default]/{modules,themes}:可以将特定站点的模块和主题移动到这些目录中,以避免它们在所有站点上显示。 -
sites/[domain OR default]/files: 站点特定的文件通常会放在这里。这可能包括用户上传的文件,如图片,也包括配置文件,包括活动配置和暂存配置。配置由 Drupal 读取和写入,并且应该具有最小的权限,仅允许 Web 服务器读取和修改它们。 -
/vendor: 这个文件夹包含 Drupal 核心所依赖的所有后端库。
现在,让我们开始:
-
让我们先添加您的模块文件夹名称。与 Drupal 7 不同,我们曾经将模块文件夹放在
site/all内,在 Drupal 8 中,我们必须将我们的自定义或贡献模块保存在根目录下的模块中。modules/contrib/ modules/custom/注意
在多站点配置的情况下,我们需要使用不同的文件结构如下。
sites/ site_name_a/modules/ sites/ site_name_b/modules/让我们的模块命名为
d8dev,并在modules/custom目录下创建一个名为d8dev的文件夹。 -
创建
.info.yml文件。在 Drupal 7 中,我们曾经使用过.info文件。在 Drupal 8 中,这已经被改为.info.yml。现在,我们不再使用.info解析器,而是使用 Symfony YML 组件。新的扩展.info.yml适用于模块、主题和配置文件。注意
Drupal 使用了一些 Symfony 组件来支持这个版本。通过使用 HTTP Kernel 组件,Drupal 和 Symfony 项目变得更加互操作。我们将能够轻松地将自定义 Symfony 应用程序与 Drupal 集成,反之亦然。您可以了解更多关于 Symfony 结构以及 Drupal 如何使用它的信息。请点击此链接
symfony.com/projects/drupal。YAML: 与 PHP 类似,YML 是一种简单的语言,并且有简单类型的语法;例如整数、字符串、浮点数或布尔值。
在 Drupal 8 中,新的
info.yml文件是必需的,用于更新 Drupal 核心关于不同的模块、主题或任何安装配置文件。这也提供了额外的标准来管理模块以及版本兼容性。 -
在第一步中创建的
d8dev目录下创建一个d8dev.info.yml文件:name: Drupal 8 custom module d8dev type: module description: 'Example for Drupal 8 modules.' package: Custom version: 8.1 core: 8.x在
d8dev.info.yml中我们添加了什么:-
name: 如同显而易见,name字段是我们模块的标识符,将在模块页面上显示。我们应该遵循大写和 Symantec 命名约定。 -
description: 我们添加了一条简短的一行描述,以帮助管理员了解此模块的功能。 -
package: 如果这将成为其他模块的一部分,我们应该使用包的名称。由于我们正在开发一个自定义模块,我们保留了Custom,或者如果您不确定,可以使用Other。 -
type: 这是一个必需的属性,用于让 Drupal 知道它是一个模块、一个主题还是一个安装配置文件。 -
version: 我们应该指定我们正在开发的模块的版本号。 -
core: 我们需要指定此模块将要使用的 Drupal 版本,8.x。这是一个强制属性。
-
-
创建
.routing.yml-
路由系统:路由是指为 Drupal 定义的路径,用于返回某种内容。例如,默认的前页 /node 就是一个路由。当 Drupal 收到一个请求时,它会尝试将请求的路径与它所知道的某个路由进行匹配。如果找到路由,则使用该路由的定义来返回内容。否则,Drupal 将返回 404 错误。
-
路由和控制器:Drupal 的路由系统与 Symfony HTTP 内核协同工作。对 Symfony 的了解将有所帮助。对 Symfony HTTP 内核的基本了解就足以进行一般的路由操作。以下图表解释了不同组件之间的关系:
d8dev.my_page: path: '/mypage/page' defaults: _controller: '\Drupal\d8dev\Controller\d8devController::myPage' _title: 'My first page in Drupal8' requirements: _permission: 'access content'
![行动时间 - 在 Drupal 8 中开发自定义模块]()
我们将在
.routing.yml文件中编写路径。同样,这里我们将使用 Symfony2 组件来处理路由。以下是我们d8dev.routing.yml代码,它包括定义路由作为配置,并在控制器中管理回调,控制器是控制器类的一个方法:在前面的代码中,第一行
d8dev.my_page是路由,这是一个将 Drupal 中的 HTTP 请求映射到一组配置变量的 Symfony 组件。路由被定义为机器名,作为module_name.route_name。接下来是路径,我们在这里指定我们想要此路由注册的 URL 路径。不要忘记添加一个前置的正斜杠。
在默认设置下,我们有两种配置:
_controller,它引用了d8devController类上的一个方法,以及_title,在这里我们添加默认页面标题,例如My first page in Drupal 8。作为要求配置的一部分,我们指定了权限,谁可以访问页面。
你可以在
www.drupal.org/node/2092643了解更多关于路由文件的信息。 -
-
创建路由控制器类。我们必须根据已经在 Drupal 8 中实施的 PSR-4 命名标准创建我们的
ModuleController.php文件,该标准由 PHP 框架互操作性小组用于基于包的 PHP 命名空间自动加载。注意
PSR 描述了从文件路径编写自动加载类的规范。它还描述了根据规范放置将被自动加载的文件的位置。有关 PSR-4 命名空间和自动加载的更多信息,请参阅
www.php-fig.org/psr/psr-4/。创建一个名为
modules/custom/d8dev/src/Controller的文件夹。在这个文件夹内,创建一个名为d8devController.php的文件,内容如下:<?php /** * @file * @author My Name * Contains \Drupal\d8dev\Controller\d8devController. * Please include this file under your * d8dev(module_root_folder)/src/Controller/ */ namespace Drupal\d8dev\Controller; /** * Provides route responses for the d8dev module. */ class d8devController { /** * Returns a simple page. * * @return array * A simple renderable array. */ public function myPage() { $element = array( '#type' => 'markup', '#markup' => 'Hello world!', ); return $element; } } ?>控制器是我们添加的 PHP 函数,它从 HTTP 请求中获取信息,构建并返回 HTTP 响应。
控制器包含了我们的应用程序需要显示页面内容的所有逻辑。根据匹配的路由,执行特定的控制器,并创建并返回一个响应对象。
例如,如果浏览器请求路径为
/mypage/page的页面,它将执行d8devController::myPage()控制器,并显示一个简单地打印Hello world!的页面。 -
启用模块。转到路径
admin/modules![行动时间 – 在 Drupal 8 中开发自定义模块]()
一旦您启用了模块并在您最喜欢的浏览器中打开了路径http://localhost/d8dev/mypage/page,您将看到从我们的模块打印出的Hello world!文本。
发生了什么?
我们完成了我们的第一个自定义模块,并在自定义页面 URL 上显示了Hello World!消息。在深入模块开发和介绍 Drupal API 之前,给自己一杯热咖啡作为奖励。
行动时间 - 开发自定义字段格式化器
让我们打开我们在开发自定义模块之前创建的食谱页面。在我们的食谱页面上,我们使用自定义内容类型创建,所有时间字段(cookTime和prepTime)的值都以分钟显示,例如,60 分钟和 90 分钟。如果 60 分钟显示为 1 小时,90 分钟显示为 1 1⁄2 小时,那就更好了。
我们可以实现的其中一种方式是开发一个自定义模块来创建一个自定义字段格式化器,该格式化器将cookTime和prepTime相关字段以小时而不是分钟的形式显示。
创建新插件或创建自定义字段格式化器涉及以下基本步骤:
-
创建自定义格式化器类。这个类在其注解块中定义了其元信息,包括格式化器的 ID、标签和字段类型。
核心模块定义在
Drupal\Core\Field\Plugin\Field\FieldFormatter中,而命名空间插件放置在我们的模块的src/Plugin/文件夹中。在字段格式化器的案例中,这将是在src/Plugin/Field/FieldFormatter目录中。为了创建格式化器,您需要遵循以下步骤:
创建
RecipeFormatter.php。将其复制到我们的模块文件夹中的d8dev/src/Plugin/Field/FieldFormatter/RecipeFormatter.php,并添加以下行:<?php /** * @file * Contains \Drupal\ d8dev\Plugin\field\formatter\RecipeFormatter. */ namespace Drupal\d8dev\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface;在 Drupal 7 中,我们曾经使用
hook_field_formatter_info。在 Drupal 8 中,我们引入了一个新的注解系统。现在,我们需要使用注解来定义格式化器。将以下行添加到我们在步骤 1 中创建的
RecipeFormatter.php文件中:/** * Plugin implementation of the 'recipe_time' formatter. * * @FieldFormatter( * id = "recipe_time", * label = @Translation("Duration"), * field_types = { * "integer", * "decimal", * "float" * } * ) */注意
在您的格式化器类之前使用
@FieldFormatter注解的 PHP 注释 docblock 非常重要:class RecipeFormatter extends FormatterBase { }注解属性相当直观。我们所做的就是定义了一个 ID、标签以及此格式化器应在哪种字段类型上可用。
field_types属性是最重要的部分,如果您不添加integer、decimal或float,则此格式化器将不会出现在管理显示页面上。我们将在格式化器上进行的最后一点工作是添加
viewElements()方法。这个方法将用于显示实际的格式化器,如果格式化器类扩展了FormatterBase,则这是唯一需要的方法。将以下行追加到
RecipeFormatter.php类中:public function viewElements(FieldItemListInterface $items) { $elements = array(); foreach ($items as $delta => $item) { //$hours = $item->value; $hours = floor($item->value / 60); //divide by minutes in 1 hour and get floor $minutes = $item->value % 60; //remainder of minutes //get greatest common denominator of minutes to convert to fraction of hours $minutes_gcd = gcd($minutes, 60); //⁄ is the html entity for the fraction separator, and // we use the sup and sub html element to give the // appearance of a fraction. //$minutes_fraction = '<sup>' . $minutes/$minutes_gcd .'</sup>⁄<sub>' . 60/$minutes_gcd . '</sub>'; $minutes_fraction = $minutes/$minutes_gcd ."/" . 60/$minutes_gcd ; $markup = $hours > 0 ? $hours . ' and ' . $minutes_fraction . ' hours' : $minutes_fraction . ' hours'; $elements[$delta] = array( '#theme' => 'recipe_time_display', '#value' => $markup, ); } return $elements; }我们在这里所做的就是将处理过的值传递到一个自定义模板中,该模板将用于显示嵌入的 HTML 代码。
我们还需要创建一个辅助函数来处理值。
将以下行追加到
RecipeFormatter.php类中:** * Simple helper function to get gcd of minutes */ function gcd($a, $b) { $b = ( $a == 0) ? 0 : $b; return ( $a % $b ) ? gcd($b, abs($a - $b)) : $b; }最后,我们的
RecipeFormatter.php类看起来像以下代码:<?php * @file * Contains \Drupal\d8dev\Plugin\field\formatter\RecipeFormatter. */ namespace Drupal\d8dev\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface; /** * Plugin implementation of the 'number_decimal' formatter. * * The 'Default' formatter is different for integer fields on the one hand, and * for decimal and float fields on the other hand, in order to be able to use * different settings. * * @FieldFormatter( * id = "recipe_time", * label = @Translation("Duration"), * field_types = { * "integer", * "decimal", * "float" * } * ) */ class RecipeTimeFormatter extends FormatterBase { /** * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items) { $elements = array(); foreach ($items as $delta => $item) { //$hours = $item->value; $hours = floor($item->value / 60); //divide by minutes in 1 hour and get floor $minutes = $item->value % 60; //remainder of minutes //get greatest common denominator of minutes to convert to fraction of hours $minutes_gcd = gcd($minutes, 60); //⁄ is the html entity for the fraction separator, and we use the sup and sub html element to give the appearance of a fraction. //$minutes_fraction = '<sup>' . $minutes/$minutes_gcd .'</sup>⁄<sub>' . 60/$minutes_gcd . '</sub>'; $minutes_fraction = $minutes/$minutes_gcd ."/" . 60/$minutes_gcd ; $markup = $hours > 0 ? $hours . ' and ' . $minutes_fraction . ' hours' : $minutes_fraction . ' hours'; $elements[$delta] = array( '#theme' => 'recipe_time_display', '#value' => $markup, ); } return $elements; } } /** * Simple helper function to get gcd of minutes */ function gcd($a, $b) { $b = ( $a == 0) ? 0 : $b; return ( $a % $b ) ? gcd($b, abs($a - $b)) : $b; } -
创建模板。到目前为止,我们已经创建了格式化器。现在我们需要创建一个自定义模板来完成这个模块的开发。该模板将被称为
recipe_time_display,它将接受value作为单个参数。为了创建模板,我们将遵循以下下一步:-
打开
d8dev.module并添加以下函数:/** * Implements hook_theme(). */ function d8dev_theme() { return array( 'recipe_time_display' => array( 'variables' => array('value' => NULL), 'template' => 'recipe-time-display', ), ); } -
在模块中创建一个名为
templates的文件夹和一个名为recipe-time-display.html.twig的文件。路径是:d8dev/templates/recipe-time-display.html.twig。{{ value }}
备注
你可以简要了解 Twig 在
symfony.com/doc/current/book/templating.html。在这里,我们只是直接打印值。如果你想,你可以通过这个文件向值中添加更多的 HTML。将以下内容添加到
recipe-time-display.html.twig: -
-
在保存上一步骤中所有文件后,打开浏览器中的你的网站,并从路径
admin/config/development/performance中清除所有缓存。![动手实践 – 开发自定义字段格式化器]()
-
清除缓存后,你可以转到路径
admin/structure/types/manage/recipe/display,并将格式字段cookTime和prepTme从默认更改为持续时间。![动手实践 – 开发自定义字段格式化器]()
-
然后,你点击管理显示页面上的保存按钮。现在查看我们之前创建的食谱内容项,你应该看到新的时间格式。
![动手实践 – 开发自定义字段格式化器]()
发生了什么?
我们创建了一个自定义模块,允许我们以我们想要的方式格式化我们的食谱内容持续时间字段——整数转换为小时和小时的分数。
测试驱动开发(TDD)
很多次我们听到 Drupal 不适合编写复杂的 Web 应用程序。其中一个主要原因是 Drupal 不支持 TDD 或自动化测试。但在 Drupal 8 中,核心中包含了 PHPUnit 测试和 Simpletest 测试。
Drupal 8 的开发考虑到自动化测试的使用,包括单元测试以及功能测试。单元测试处于较低级别,主要用于测试类的功能,而功能测试则处于较高级别,用于检查 Web 输出。
在对现有 Drupal 系统进行任何更改之前,对大多数我们开发的功能运行这两种类型的测试非常重要,以确保不会破坏现有功能。我们也将此称为回归测试。

要遵循 TDD,我们需要遵循以下两个过程:
-
每当你修复一个错误或更改现有功能时,确保编写一个在更改代码之前失败的测试,并在你更改代码后通过测试。这个测试的目的是帮助审阅者理解他/她遇到的错误,突出显示编写的代码已修复预期的错误,并确保它不会在代码的任何新更改中出现。
-
类似地,在编写代码以实现新功能的情况下,确保在代码中包含单元测试和/或功能测试。这将确保审阅者知道你编写的代码是有效的,并且更改不会破坏新功能。
PHPUnit 测试用例用于 Drupal 类
我们将使用行业标准 PHPUnit 框架来编写我们的 Drupal 类的测试。通常,当我们不需要 Drupal 环境(数据库、设置等)和浏览器时,我们编写 PHPUnit 测试用例来测试类的功能。
要编写 PHPUnit 测试,请遵循以下步骤:
-
定义一个扩展
\Drupal\Tests\UnitTestCase的类。确保类名以单词test结尾。 -
将你的测试类文件放置在
ind8dev/tests/src/Unit目录下。 -
包含一个 phpDoc 注释,提供有关测试的描述信息。
-
开始编写你的测试类,类名应以
test开头。每个测试类应包括要测试的功能的一部分。注意
在
www.drupal.org/phpunit上了解更多关于 PHPUnit 的信息。有关如何为 Drupal 编写 PHPUnit 测试的完整文档,请访问phpunit.de,以及有关 PHPUnit 框架、面向对象编程主题的通用信息,更多关于 PSR-4、命名空间以及放置类的地方。
功能测试
我们使用内建的 Simpletest 作为 Drupal 核心的一部分来编写功能测试用例。与单元测试用例不同,我们使用功能测试来测试 Drupal 不同的系统功能,当它依赖于数据库以及配置或测试浏览器输出时。
执行时间 – 从我们的 d8dev 自定义模块编写和测试功能测试
-
从路径
admin/modules启用模块 Testing:![执行时间 – 从我们的 d8dev 自定义模块编写和测试功能测试]()
-
在
d8dev/src/tests/目录下的D8devTest.php文件中创建D8devTest类,并添加以下代码行:<?php /** * Created by PhpStorm. * User: Neeraj * Date: 8/7/15 * Time: 10:59 AM */ namespace Drupal\d8dev\Tests; use Drupal\simpletest\WebTestBase; /** * Tests the d8dev module functionality * * @group d8dev */ class D8devTest extends WebTestBase { /** * Tests that the 'mypage/page' path returns the right content */ public function testCustomPageExists() { $this->drupalGet('mypage/page'); $this->assertResponse(200); } }注意
在
Test类的 phpDoc 注释块中,必须有这个带有@group注解的块。/** * Tests the d8dev module functionality * * @group d8dev */测试用例: 在
D8devTest类中,我们使用函数testCustomPageExists()检查由我们的 d8dev 模块创建的自定义页面mypage/page。 -
从路径
admin/config/development/performance清除缓存。 -
您可以从路径
admin/config/development/testing打开测试模块用户界面,或者您可以通过 配置 | 测试 导航到。![执行动作 – 从我们的 d8dev 自定义模块编写和测试功能测试]()
-
选择 \Drupal\d8dev\Tests\D8devTest 并点击 运行测试 按钮。您将看到以下截图中的结果:
![执行动作 – 从我们的 d8dev 自定义模块编写和测试功能测试]()
发生了什么?
恭喜!您现在应该熟悉了 Drupal 8 中测试驱动开发(TDD)方法的实现。我们学习了如何为我们的自定义模块功能编写功能测试用例。我们还学习了如何在 Drupal 8 中通过测试模块用户界面来编写和运行测试。
摘要
在本章中,我们学习了如何在 Drupal 中创建新的内容类型,并开发我们的自定义模块以使用字段格式化器 API 修改其中一个字段。我们还探讨了测试驱动开发(TDD)是如何被纳入新 Drupal 核心的。
在下一章中,我们将学习更多关于 Drupal 8 中新引入的配置管理以及如何使用已作为核心模块移动的视图。
第三章。Drupal Views 和配置管理
在上一个章节中,我们学习了如何创建基本的自定义模块以及创建新的内容类型。现在让我们探索 Drupal 8 中引入的新配置管理,以实现更好的版本控制。我们将学习如何使用 Devel 模块生成内容以进行测试,同时学习新和改进的视图的基础知识。
在本章中,我们将学习以下主题:
-
使用内置的 Drupal 8 视图
-
Drupal 中的新配置管理是什么?
-
使用配置管理模块通过用户界面导出和导入网站配置
-
使用 Devel 模块自动生成内容以进行测试目的
Views 的快速介绍
Drupal 7 中的 Views 是安装和使用最广泛的贡献模块。使用 Views,您可以轻松创建列出网站上各种内容的页面和块。您可以创建一个照片画廊或事件日历,或者列出登录到您网站的用户。以下是我们需要 Views 时列出的一些要点:
-
我们喜欢默认的前页视图,但希望以不同的方式排序它
-
我们希望提供一个“未读论坛帖子”选项
-
我们喜欢默认的分类/术语视图,但希望以不同的方式排序它,例如按字母顺序
-
我们希望有一种方式来显示包含特定类型最近五篇帖子的块
在 Drupal 8 中,Views 已被移动到核心模块系统。在 Drupal 8 中创建视图的过程与 Drupal 7 中遵循的过程相似。现在,Views 模块将作为默认核心模块安装的一部分进行安装。
动手实践时间 – 使用视图创建食谱列表块
我们将看到新的 Views 模块如何使在网站上显示食谱列表变得多么简单:
-
如果您还没有登录到您的网站,请以管理员身份登录并导航到
http://yourdrupal8site.com/admin/structure的管理 | 结构。![动手实践时间 – 使用视图创建食谱列表块]()
-
选择Views链接创建一个新的视图。它将带您到Views配置页面,
http://yourdrupal8site.com/admin/structure/views。![动手实践时间 – 使用视图创建食谱列表块]()
在这里,您会发现与 Drupal 7 Views 配置页面在风格上的细微差别。有许多默认视图。根据您的需求,您可以选择复制或编辑并使用这些预定义视图。或者,您可以点击添加新视图按钮创建一个全新的视图。
-
现在我们将创建自己的视图来列出文章内容类型。转到此路径:
http://yourdrupal8site.com/admin/structure/views/addorhttp://yourdrupal8site.com/admin/structure/views。点击添加新视图按钮。 -
您将获得一个类似于下面所示的形式。填写视图名称字段。在这个例子中,我给视图命名为
Recipe List。对于显示和类型字段,我分别选择了内容和食谱。 -
选中创建一个块复选框。
-
点击保存并编辑按钮。接受创建块的所有默认值,您的页面应该看起来类似于这个:
![执行时间 – 使用视图创建食谱列表块]()
-
这将带您进入
http://yourdrupal8site.comadmin/structure/views/view/recipe_list的视图配置页面。在那里,您需要在页面底部点击保存按钮:![执行时间 – 使用视图创建食谱列表块]()
现在,我们需要配置我们的 d8dev 站点,以便我们的基于 Recipe List 视图的块显示在首页上。
-
点击管理工具栏中的结构链接,并选择块布局:
![执行时间 – 使用视图创建食谱列表块]()
-
在下一页,点击页面右侧列表(视图)下的+食谱列表:
![执行时间 – 使用视图创建食谱列表块]()
-
在下一页,将块标题字段留空,因为这样标题将默认为我们之前在视图创建向导中添加的标题。
-
在区域设置下,从下拉菜单中选择侧边栏第二:
![执行时间 – 使用视图创建食谱列表块]()
发生了什么?
您现在已经创建了一个基于视图的食谱块,并配置了它,以便它只会在首页上显示。现在,当您访问 d8dev 网站的首页时,您将在页面的右侧看到一个漂亮的食谱列表块:

Drupal 8 的配置管理
作为开发者,我们一直面临如何进行增量开发或解决 Drupal 项目中预演和部署问题的挑战。更大的挑战是,“什么应该归类为内容,什么应该归类为配置?”虽然我们有配置作为一个独立的菜单来识别这个问题,但关于视图、分类法或主题配置怎么办呢?
并非我们没有在 Drupal 7 中找到解决方案。为了解决这个问题,在 Drupal 6 时代引入了 Features & Strongarm 模块,在过去几年中它取得了很大的改进。这在大多数情况下都是一个救星。尽管如此,我们仍然不得不手动重新创建一些配置,这使得它容易出错。
但是,当定期添加用户生成内容,并且有多个团队在同一项目上工作,有时在不同的时区时,记录特征导出所需的配置始终是一个挑战。我们不能从一台安装导出完整的站点配置。
此外,还有许多贡献的模块以自己的方式存储特定的配置,这使得以增量方式合并功能导出变得更加困难。
为了提高开发过程,Drupal 8 启动了一个新的配置管理倡议。现在,由于这个原因,我们有了在 Drupal 网站上管理配置的更简单方法。我们将整个配置保存在文件中而不是数据库表中。这使得真正遵循版本控制系统变得更容易。现在,如果需要,我们可以存储历史记录并回滚更改。
让我们先了解在 Drupal 项目中什么被定义为内容,什么被定义为配置:
-
内容:我们在网站前端显示的所有内容都是内容,例如,文章、基本页面、博客文章、图片、视频和音频
-
会话:会话存储有关特定用户在登录并与其 Drupal 网站交互时的信息
-
状态:这包含有关网站在不同场景中的信息,这些场景本质上是临时的,例如,在重建网站权限之前或运行 cron 作业之前的状态
-
配置:这是所有不是内容且特定于您网站的信息,例如,网站名称、使用的内容类型、字段、词汇表、图像样式、文本格式、菜单、菜单链接、权限系统,甚至您创建的视图
最好使用版本控制系统,例如
git或svn,以充分利用配置管理,尤其是在与分布式团队协作时。除了在文件中添加配置外,您还可以手动添加配置。但最好避免这样做,因为您将不得不记住在从预发布到部署迁移过程中需要重新创建的每个步骤。您甚至无法使用任何版本控制系统。
注意
避免使用手动配置管理。
使用配置管理界面
在 Drupal 8 中,我们有一个配置管理模块,它提供了一个简单的界面来导出和导入网站配置,并在项目的不同环境之间共享,例如开发、预发布和部署。您可以使用此模块轻松验证生产环境中的更改。
此模块假定您的不同环境;例如,dev、预发布、测试和生产是同一个网站。每个网站都使用通用唯一标识符(UUID)进行标识。只有当 UUID 与目标网站匹配时,模块才会允许在不同网站之间导入配置。
默认情况下,Drupal 8 将所有网站配置保存在数据库中。在安装新的 Drupal 网站时,它会在 sites/default/files 中创建一个新的文件夹,命名为 config_HASH。在这里,HASH 是一个随机生成的长数字和字母字符串。请参考以下截图:

这是保存网站不同状态配置的地方。
让我们使用模块 UI 在下一节中导出我们在上一章创建的配置。
操作时间 - 导入、导出和同步配置
Drupal 8 中配置系统的目标是使复制网站配置变得容易,以便设置一个开发站点,在那里你可以进行更改。然后,将这些更改导入到其他站点也变得简单。
要导出 Recipe 内容类型,请按照以下步骤操作:
-
在第二章中,我们创建了“自定义模块开发”的 Recipe 内容类型。我们将使用配置管理来导出此内容类型。浏览到模块页面
http://yourdrupal8site.com/admin/config/development/configuration![操作时间 - 导入、导出和同步配置]()
-
点击单个导入/导出选项卡,然后点击导出。
在配置管理模块 UI 中,第一个选项卡是同步。当你第一次使用此功能时,你会看到:没有配置更改要导入。这意味着你的开发站点正在使用数据库本身的配置。
你会看到没有配置更改,这意味着你的网站正在使用数据库中的配置文件,并且没有对网站进行任何更改。
-
选择配置类型为内容类型,配置名称为Recipe。你将在文本区域框中看到 YAML 格式的配置。将这些 YAML 配置保存在一个单独的文件中,
node.type.recipe.yml。此文件包含与我们内容类型相关的所有配置。![操作时间 - 导入、导出和同步配置]()
然而,
node.type.recipe.yml不包含任何在相同内容类型中使用的字段。我们还需要导出它们。 -
要导出,从同一页面的下拉菜单中选择字段。
![操作时间 - 导入、导出和同步配置]()
-
以同样的方式,导出 Recipe 内容类型的表单显示。选择配置类型为实体表单显示,配置名称为node.recipe.default。将 YAML 配置保存到
core.entity_form_display.node.recipe.default.yml。![操作时间 - 导入、导出和同步配置]()
-
导出 Recipe 内容类型的默认视图摘要。选择配置类型为实体视图显示,配置名称为node.recipe.teaser。将 YAML 配置保存到
core.entity_view_display.node.recipe.default.yml。 -
导出 Recipe 列表视图。选择配置类型为视图,配置名称为Recipe List。将 YAML 配置保存到
views.view.recipe_list.yml。![操作时间 - 导入、导出和同步配置]()
在其他开发站点导入配置
在您的本地机器上安装另一个 Drupal 网站,我们将将其用作目标网站以导入所有功能。现在从配置管理中导入内容类型 Recipe:
-
请访问
http://yourstagingsite.com/admin/config/development/configuration/single/import中的 管理 | 配置 | 开发 | 同步 | 导入。选择 配置类型 为 内容类型,粘贴node.type.recipe.yml中的 YAML,然后点击 导入。![操作时间 – 导入、导出和同步配置]()
您将获得一个确认页面,要求您创建一个新的内容类型。
![操作时间 – 导入、导出和同步配置]()
成功导入后,您应该看到以下屏幕:
![操作时间 – 导入、导出和同步配置]()
-
以类似的方式,导入 Recipe 内容类型的字段。选择 配置类型 为 字段,粘贴 YAML 配置,然后点击 导入。
-
使用我们从初始开发网站导出后保存的文件导入视图模式和表单模式。
在前面的步骤中,我们看到了 单个导入/导出 的工作方式。让我们探索 完整导入/导出 的其他选项:
-
再次浏览到模块配置页面:
http://yourdrupal8site.com/admin/config/development/configuration。这次我们将关注第三个选项卡 完整导入/导出。与之前一样,有两个选项,导入 和 导出。![操作时间 – 导入、导出和同步配置]()
-
通过点击选项导出您的网站配置。这将创建一个
config.tar.gz文件,并提示您在您的机器上下载。 -
下一步将是创建另一个 Drupal 8 网站(如果您还没有创建的话),我们将在此网站上导入
config.tar.gz文件。 -
浏览到
http://yourstagingsite.com/admin/config/development/configuration/full/import。选择config.tar.gz并按照此截图所示上传:![操作时间 – 导入、导出和同步配置]()
-
现在我们已将网站配置导入到新网站,让我们再次查看第一个选项卡 同步。访问
http://yourstagingsite.com/admin/config/development/configuration。现在它应该显示如下:![操作时间 – 导入、导出和同步配置]()
此页面基本上显示所有配置更改。这有助于我们验证所有内容是否按预期导入。
您可以点击 查看差异,这将打开一个弹出窗口,列出已导入/更改的各种配置。在确认一切如预期后,关闭弹出窗口并选择 导入全部。
这将根据任务量花费几分钟时间。
![操作时间 – 导入、导出和同步配置]()
-
一旦导入完成,您应该回到配置页面,第一个标签页,并显示成功消息。
![操作时间 – 导入、导出和同步配置]()
现在,只需验证新配置是否已实施。
刚才发生了什么?
您已使用易于使用的配置管理模块用户界面,从您的开发站点将食谱内容类型导入到 Drupal 站点的全新实例中,而没有使用数据库导入或功能模块。
Drupal 8 中的配置管理工作原理
作为开发者,让我们探索 Drupal 8 中配置管理是如何工作的。如本章前面所述,默认情况下,Drupal 将所有配置保存在数据库中。在安装过程中,它会在/sites/default/files中创建一个名为config_HASH的文件夹,其中HASH是一个由数字和字母组成的随机生成的长字符串。随机生成的 HASH 为网站提供了额外的保护。

在Config文件夹中,有两个额外的文件夹:active和staging。默认情况下,它们都是空的,只包含.htaccess和README.txt。
通常,Drupal 8 使用数据库来存储活动配置,除非我们更改它。如果我们更改默认行为,active文件夹将包含默认 Drupal 安装的配置。
另一个目录staging用于存储从您的开发环境导入的 Drupal 站点的配置。
默认情况下,Drupal 和相关模块在安装期间将配置存储在活动存储中。这些配置是一组文件,用于维护运行您的 Drupal 站点所需的配置。这些配置以 YAML 格式存储。
如果您在您的机器上探索 Drupal 安装文件夹,您会发现每个模块和配置文件下都有一个config文件夹。这个文件夹包含两个或多个文件夹,install和schema。

这两个文件夹都包含一组.yml文件,这些文件保存了模块或配置文件的相应配置。
在站点安装过程中,这些文件中存储的值将被复制到您的 Drupal 站点的活动配置中。如果我们使用默认的 Drupal 配置存储,这些值将被复制到配置表中,如果我们选择了基于文件的存储,这些配置值将被复制到活动目录中的相应目录。
更改活动配置存储
尽管不建议将默认的活动配置存储从数据库更改为文件,但您可以在settings.php文件中执行此更改。
小贴士
您只能在安装您的 Drupal 站点之前进行此更改。安装过程完成后,您将无法更改配置存储。
在您的编辑器中打开settings.php并搜索活动配置设置。现在您只需取消注释行$settings['bootstrap_config_storage']以启用基于文件的系统用于活动配置。除此之外,您还必须将default.services.yml复制到services.yml并启用基于文件的配置存储:
services:
# Override configuration storage.
config.storage:
class: Drupal\Core\Config\CachedStorage
arguments: ['@config.storage.active', '@cache.config']
config.storage.active:
# Use file storage for active configuration.
alias: config.storage.file
这将使 Drupal 能够将默认的配置存储设置从数据库更改为基于文件的系统,并使用config.storage.file作为活动存储。
在更改配置存储设置后,按照相同的标准步骤安装您的 Drupal 8 站点。现在让我们看看sites/default/files下的active文件夹。

现在活动目录包含整个站点的站点配置。这些文件在安装过程中被复制到这里。现在,每次您更改站点配置时,这些文件也会进行更改。您可以使用版本控制系统来跟踪您的站点配置的更改。
但更改存储系统不会改变我们导出/导入站点配置的方式。
注意
在www.drupal.org/documentation/administer/config了解更多关于配置管理的知识。
介绍 Devel 模块
因此,在前一节中,我们看到了创建一个基于视图的自定义块以在首页显示食谱列表是多么容易。您会立即注意到,只有一个食谱显示出来,因为我们到目前为止只创建了一个。
你可能还记得,当我们使用视图创建食谱列表块时,我们保留了每页项目数量的默认值 5。现在,能够测试这个设置而不需要手动创建四个更多的食谱项目会很好。进入 Devel 模块的drupal.org/project/devel。
Devel 模块包含一些子模块,使 Drupal 开发更容易;而我们感兴趣的是帮助我们在开发目的下创建内容的devel_generate模块。
安装 Devel 模块
这应该是一个简单的步骤。只需下载模块并启用扩展页面。

Devel 已安装并准备好进行一些魔法操作。
使用 devel_generate 模块生成虚拟内容的时间
现在,随着devel_generate模块的启用,我们将生成一些食谱内容,以便我们可以测试我们的食谱列表块中的项目数量:
-
首先,点击管理工具栏中的配置链接,然后点击开发部分下的生成内容链接。
-
在生成内容页面,取消选中除了食谱之外的所有内容类型复选框。
![使用 devel_generate 模块生成虚拟内容的行动时间]()
-
对于所有其他设置,请保持默认值,并在页面底部点击生成按钮。
-
现在,导航到主页,你会看到食谱列表块已经完全填充。
![使用 devel_generate 模块生成虚拟内容的行动时间]()
发生了什么事?
尽管这是一个使用devel_generate模块的非常简单的例子,但在测试需要多个内容项的自定义代码时,能够生成内容可以节省大量时间。我们只是使用了devel_generate模块来生成一些基于我们自定义的食谱内容类型的虚拟内容,现在主页上的食谱列表块已经完全填充了五个食谱。
摘要
在本章中,我们学习了如何使用视图模块创建视图块,该模块现在是 Drupal 8 核心的一部分。我们还探索了为我们的 Drupal 站点提供更好版本控制的全新配置管理。现在我们可以分别设置开发、测试和生产环境,并逐步向站点添加新功能。我们探讨了如何使用 Devel 模块生成大量内容以供测试目的。
在下一章中,我们将学习 HTML5,这是 Dries 为 Drupal 8 概述的五大主要倡议之一。
第四章. 字段类型 API 简介和自定义字段模块开发
在上一章中,我们学习了如何使用视图和配置管理。现在让我们更深入地了解 Field Types API 以及如何使用它来开发自定义字段模块。
在本章中,我们将学习以下主题:
-
使用字段 API 创建字段类型、小部件和格式化器
-
开发自定义字段模块
介绍 NutritionInformation 模块
在上一章中,我们没有包含在我们的 Drupal Recipe 内容类型中的schema.org/Recipe属性之一是NutritionInformation属性。这是因为NutritionInformation属性本身是来自schema.org的itemType,因此它由许多自己的单独属性组成。为了将NutritionInformation添加到我们的自定义 Recipe 内容类型中,我们需要创建一个基于schema.org/NutritionInformation规范的定制 Drupal 复合字段模块。
动手实践 – 开发一个用于复合 NutritionInformation 字段的自定义模块
我们不是将创建这个复合字段的代码添加到我们现有的模块中,而是将创建一个新的模块,因为它可能是整个 Drupal 社区都可能有用的事情,我们可能最终希望将其贡献给 Drupal (www.drupal.org/)。我们需要创建一个由几个不同的表单元素(一个选择数字和几个文本字段)组成的复合字段。现在,我们可以通过三个基本步骤创建一个基本的复合字段:首先定义字段(信息和模式),其次是定义字段表单(小部件),然后是定义输出(格式化器)。所有这些步骤都在这里解释:
-
在 Phpstorm 中,在
/modules/custom目录下创建一个名为nutritioninfo的新文件夹。 -
创建与文件夹同名的
nutritioninfo.module和nutritioninfo.info.yml文件——nutritioninfo。创建模板src/Plugin/Field文件夹,你应该有一个类似于以下截图的文件夹结构:![动手实践 – 开发一个用于复合 NutritionInformation 字段的自定义模块]()
-
现在,打开
nutrtioninfo.info文件并添加以下配置:name: Nutrition Information type: module description: 'Defines a nutrition information field type based on the Microdata spec at http://schema.org/NutritionInformation ' package: Fields core: '8.x' -
在 Drupal 7 中,
hook_field_schema钩子允许我们定义一个数据库模式来存储我们的自定义字段信息。但在 Drupal 8 中,字段类型变为插件系统(www.drupal.org/node/2064123)。 -
接下来,我们将使用来自电话核心模块(
/core/modules/telephone/src/Plugin/Field/FieldType/TelephoneItem.php)的以下代码。我们只是复制此文件并将其更改为NutritioninfoItem.php文件。在下一步中,我们创建/modules/nutritioninfo/src/Plugin/Field/FieldType/NutritioninfoItem.php文件和核心TelephoneItem.php文件,如下所示:<?php /** * @file * Contains \Drupal\telephone\Plugin\Field\FieldType\TelephoneItem. */ namespace Drupal\telephone\Plugin\Field\FieldType; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; /** * Plugin implementation of the 'telephone' field type. * * @FieldType( * id = "telephone", * label = @Translation("Telephone number"), * description = @Translation("This field stores a telephone number in the database."), * category = @Translation("Number"), * default_widget = "telephone_default", * default_formatter = "basic_string" * ) */ class TelephoneItem extends FieldItemBase { /** * {@inheritdoc} */ public static function schema(FieldStorageDefinitionInterface $field_definition) { return array( 'columns' => array( 'value' => array( 'type' => 'varchar', 'length' => 256, ), ), ); } /** * {@inheritdoc} */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { $properties['value'] = DataDefinition::create('string') ->setLabel(t('Telephone number')) ->setRequired(TRUE); return $properties; } /** * {@inheritdoc} */ public function isEmpty() { $value = $this->get('value')->getValue(); return $value === NULL || $value === ''; } /** * {@inheritdoc} */ public function getConstraints() { $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); $constraints = parent::getConstraints(); $max_length = 256; $constraints[] = $constraint_manager->create('ComplexData', array( 'value' => array( 'Length' => array( 'max' => $max_length, 'maxMessage' => t('%name: the telephone number may not be longer than @max characters.', array('%name' => $this->getFieldDefinition()->getLabel(), '@max' => $max_length)), ) ), )); return $constraints; } /** * {@inheritdoc} */ public static function generateSampleValue(FieldDefinitionInterface $field_definition) { $values['value'] = rand(pow(10, 8), pow(10, 9)-1); return $values; } }正如我们在第 4 步中讨论的那样,Drupal 8 引入了插件系统。Drupal 8 中的大多数插件使用注解来注册自己并描述其元数据。在这里,
@FieldType是它注册到telephone字段类型的注解。schema函数包含列及其属性,如type和length。PropertyDefinitions函数用于设置列属性值,例如label和description。isEmpty函数用于确定列表中是否包含任何非空项。 -
Drupal 8 通过 PHP 框架实现了基于包的 PHP 命名空间自动加载的 PSR-4 标准。每个模块都有一个与其模块名称对应的命名空间(例如
namespace Drupal\telephone\Plugin\Field\FieldType)。在我们的模块中,它应该是namespace Drupal\nutritioninfo\Plugin\Field\FieldType。并且模块的命名空间映射到模块目录中的./src/文件夹。在完全限定名称(例如,使用Drupal\Core\Field\FieldItemBase;)内部带有反斜杠(\)的类和接口,不得在代码中使用它们的完全限定名称。如果命名空间与当前文件的命名空间不同,请在文件顶部放置一个use语句。以下是一个示例:namespace Drupal\nutritioninfo\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; -
现在,我们将创建
/modules/nutritioninfo/src/Plugin/Field/FieldType/NutritioninfoItem.php文件。我们需要添加NutritioninfoItem类。NutritioninfoItem.php代码如下,尚未添加任何方法:<?php /** * @file * Contains \Drupal\custom_field\Plugin\Field\FieldType\NutritioninfoItem. */ namespace Drupal\nutritioninfo\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; /** * Plugin implementation of the 'nutritioninfo' field type. * * @FieldType( * id = "nutritioninfo", * label = @Translation("Nutrition Information"), * description = @Translation("A field type used for storing nutrition information as defined by the Microdata spec at http://schema.org/ NutritionInformation."), * default_widget = "nutritioninfo_standard", * default_formatter = "nutritioninfo_default" * ) */ class NutritioninfoItem extends FieldItemBase { }在这里,
@FieldType注解注册了nutritioninfo字段类型。 -
接下来,我们需要在
schema函数中添加模式列。在TelephoneItem中,我们只有一个列值。但在NutritioninfoItem中,需要添加 12 个列。因此,schema函数看起来如下。将这些函数添加到NutritioninfoItem类中:/** * {@inheritdoc} */ public static function schema(FieldStorageDefinitionInterface $field) { return array( 'columns' => array( 'calories' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'carbohydrate_content' => array( 'type' => 'text', 'length' => 256, 'not null' => FALSE, ), 'cholesterol_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'fat_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'fiber_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'protein_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'saturated_fat_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'serving_size' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'sodium_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'sugar_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'trans_fat_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), 'unsaturated_fat_content' => array( 'type' => 'varchar', 'length' => 256, 'not null' => FALSE, ), ), ); } -
接下来,我们需要添加
isEmpty()函数。这个函数用于确定列表中是否包含任何非空项。将这些函数添加到NutritioninfoItem类中:/** * {@inheritdoc} */ public function isEmpty() { $calories = $this->get('calories')->getValue(); $carbohydrate_content = $this ->get('carbohydrate_content')->getValue(); $cholesterol_content = $this ->get('cholesterol_content')->getValue(); $fat_content = $this->get('fat_content')->getValue(); $fiber_content = $this->get('fiber_content') ->getValue(); $protein_content = $this->get('protein_content') ->getValue(); $saturated_fat_content = $this ->get('saturated_fat_content')->getValue(); $serving_size = $this->get('serving_size')->getValue(); $sodium_content = $this->get('sodium_content') ->getValue(); $sugar_content = $this->get('sugar_content') ->getValue(); $trans_fat_content = $this->get('trans_fat_content') ->getValue(); $unsaturated_fat_content = $this ->get('unsaturated_fat_content')->getValue(); //the nutrition field is empty if all of its properties are empty return empty($calories) && empty($carbohydrate_content) && empty($cholesterol_content) && empty($fat_content) && empty($fiber_content) && empty($protein_content) && empty($saturated_fat_content) && empty($serving_size) && empty($sodium_content) && empty($sugar_content) && empty($trans_fat_content) && empty($unsaturated_fat_content); } -
这个类中的最后一个方法是
propertyDefinitions(),用于设置列属性值,例如label和description。将这些函数添加到NutritioninfoItem类中:/** * {@inheritdoc} */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { $properties['calories'] = DataDefinition::create('string') ->setLabel(t('Calories')) ->setDescription(t('The number of calories.')); $properties['carbohydrate_content'] = DataDefinition::create('string') ->setLabel(t('Carbohydrate Content')) ->setDescription(t('The number of grams of carbohydrates.')); $properties['cholesterol_content'] = DataDefinition::create('string') ->setLabel(t('Cholesterol Content')) ->setDescription(t('The number of milligrams of cholesterol.')); $properties['fat_content'] = DataDefinition::create('string') ->setLabel(t('Fat Content')) ->setDescription(t('The number of grams of fat.')); $properties['fiber_content'] = DataDefinition::create('string') ->setLabel(t('Fiber Content')) ->setDescription(t('The number of grams of fiber.')); $properties['protein_content'] = DataDefinition::create('string') ->setLabel(t('Protein Content')) ->setDescription(t('The number of grams of protein.')); $properties['saturated_fat_content'] = DataDefinition::create('string') ->setLabel(t('Saturated Fat Content')) ->setDescription(t('The number of grams of saturated fat.')); $properties['serving_size'] = DataDefinition::create('string') ->setLabel(t('Serving Size')) ->setDescription(t('The serving size, in terms of the number of volume or mass.')); $properties['sodium_content'] = DataDefinition::create('string') ->setLabel(t('Sodium Content')) ->setDescription(t('The number of milligrams of sodium.')); $properties['sugar_content'] = DataDefinition::create('string') ->setLabel(t('Sugar Content')) ->setDescription(t('The number of grams of sugar.')); $properties['trans_fat_content'] = DataDefinition::create('string') ->setLabel(t('Trans Fat Content')) ->setDescription(t('The number of grams of trans fat.')); $properties['unsaturated_fat_content'] = DataDefinition::create('string') ->setLabel(t('Unsaturated Fat Content')) ->setDescription(t('The number of grams of unsaturated fat.')); return $properties; } -
最后,我们的
NutritioninfoItem.php代码如下所示:raw.githubusercontent.com/valuebound/nutritioninfo/master/src/Plugin/Field/FieldType/NutritioninfoItem.php。 -
接下来,我们需要告诉 Drupal 如何在节点编辑表单上处理我们的复合字段。在 Drupal 7 中,我们有
hook_field_widget_info来让 Drupal 了解我们的自定义小部件,然后是hook_field_widget_form来实际将表单组件添加到节点表单中。在 Drupal 8 中,字段小部件已成为插件。现在,我们将把FieldWidget/NutritioninfoDefaultWidget.php文件添加到/modules/nutritioninfo/src/Plugin/Field/FieldWidget文件夹中。文件看起来如下,没有任何方法:<?php /** * @file * Contains \Drupal\custom_field\Plugin\Field\FieldWidget\NutritioninfoDefaultWidget. */ namespace Drupal\nutritioninfo\Plugin\Field\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; /** * Plugin implementation of the 'nutritioninfo_default' widget. * * @FieldWidget( * id = "nutritioninfo_default", * label = @Translation("Nutrition Field Widget"), * field_types = { * "nutritioninfo" * } * ) */ class NutritioninfoDefaultWidget extends WidgetBase { }FieldItemListInterface接口用于字段,是字段项的列表。每个实体字段都必须实现此接口,而包含的字段项必须实现FieldItemInterface。此外,FormStateInterface提供了一个包含表单当前状态的接口。它将用于存储与表单中处理的数据相关的信息。在这里,
@FieldWidget注解注册了nutritioninfo_default小部件。 -
接下来,我们需要添加
formElement()函数。在这个函数中,将添加表单元素,这些元素将在节点编辑/添加表单中显示营养字段。我们用一个表单元素来理解:$element['calories'] = array( '#title' => t('Calories'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->calories) ? $items[$delta]->calories : NULL, );这是卡路里
textfield。在编辑/添加表单时,它将显示为textfield。formElement()函数的代码如下:/** * {@inheritdoc} */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $element['calories'] = array( '#title' => t('Calories'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->calories) ? $items[$delta]->calories : NULL, ); $element['carbohydrate_content'] = array( '#title' => t('Carbohydrate Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->carbohydrate_content) ? $items[$delta]->carbohydrate_content : NULL, ); $element['cholesterol_content'] = array( '#title' => t('Cholesterol Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->cholesterol_content) ? $items[$delta]->cholesterol_content : NULL, ); $element['fat_content'] = array( '#title' => t('Fat Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->fat_content) ? $items[$delta]->fat_content : NULL, ); $element['fiber_content'] = array( '#title' => t('Fiber Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->fiber_content) ? $items[$delta]->fiber_content : NULL, ); $element['protein_content'] = array( '#title' => t('Protein Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->protein_content) ? $items[$delta]->protein_content : NULL, ); $element['saturated_fat_content'] = array( '#title' => t('Saturated Fat Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->saturated_fat_content) ? $items[$delta]->saturated_fat_content : NULL, ); $element['serving_size'] = array( '#title' => t('Serving Size'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->serving_size) ? $items[$delta]->serving_size : NULL, ); $element['sodium_content'] = array( '#title' => t('sodium Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->sodium_content) ? $items[$delta]->sodium_content : NULL, ); $element['sugar_content'] = array( '#title' => t('Sugar Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->sugar_content) ? $items[$delta]->sugar_content : NULL, ); $element['trans_fat_content'] = array( '#title' => t('Trans Fat Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->trans_fat_content) ? $items[$delta]->trans_fat_content : NULL, ); $element['unsaturated_fat_content'] = array( '#title' => t('Unsaturated Fat Content'), '#type' => 'textfield', '#default_value' => isset($items[$delta]->unsaturated_fat_content) ? $items[$delta]->unsaturated_fat_content : NULL, ); return $element; } -
完整的文件,
NutritioninfoDefaultWidget.php,看起来如下所示:raw.githubusercontent.com/valuebound/nutritioninfo/master/src/Plugin/Field/FieldWidget/NutritioninfoDefaultWidget.php. -
现在,我们需要在显示内容项时格式化我们的复合字段。在 Drupal 7 中,我们使用了
hook_field_formatter_info和hook_field_formatter_view钩子。现在在 Drupal 8 中,这些钩子被用作插件。我们将把NutritioninfoDefaultFormatter.php文件添加到/modules/nutritioninfo/src/Plugin/Field/FieldFormatter文件夹中。代码如下,没有任何方法:<?php /** * @file * Contains \Drupal\custom_field\Plugin\field\formatter\NutritioninfoDefaultFormatter. */ namespace Drupal\nutritioninfo\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface; /** * Plugin implementation of the 'nutritioninfo_default' formatter. * * @FieldFormatter( * id = "nutritioninfo_default", * label = @Translation("Nutritioninfo Formatter"), * field_types = { * "nutritioninfo" * } * ) */ class NutritioninfoDefaultFormatter extends FormatterBase { }在这里,
@FieldFormatter注解正在注册nutritioninfo格式化器。 -
接下来,我们需要添加
viewElements()函数。这个函数构建了一个可渲染的数组,用于表格主题标记。viewElements()函数的代码如下:/** * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items) { $rows = array(); foreach ($items as $delta => $item) { $rows[] = array( 'data' => array( $item->calories, $item->carbohydrate_content, $item->cholesterol_content, $item->fat_content, $item->fiber_content, $item->protein_content, $item->saturated_fat_content, $item->serving_size, $item->sodium_content, $item->sugar_content, $item->trans_fat_content, $item->unsaturated_fat_content ) ); } $headers = array( t('Calories'), t('Carbohydrate Content'), t('Cholesterol Content'), t('Fat Content'), t('Fiber Content'), t('Protein Content'), t('Saturated Fat Content'), t('Serving Size'), t('Sodium Content'), t('Sugar Content'), t('Trans Fat Content'), t('Unsaturated Fat Content'), ); $table = array( '#type' => 'table', '#header' => $headers, '#rows' => $rows, '#empty' => t('No calories information available'), '#attributes' => array('id' => 'nutrition-info'), ); return $elements = array('#markup' => drupal_render($table)); } -
完整的
NutritioninfoDefaultFormatter.php文件的代码如下所示:github.com/valuebound/nutritioninfo/blob/master/src/Plugin/Field/FieldFormatter/NutritioninfoDefaultFormatter.php. -
好的,现在是我们启用新模块的时候了。在你的浏览器中打开你的 d8dev Drupal 站点,点击管理工具栏中的模块链接。搜索
Nutrition,并勾选你新创建的营养信息字段模块旁边的复选框:![操作时间 – 开发用于复合 NutritionInformation 字段的自定义模块]()
-
最后,滚动到页面底部并点击安装按钮。
发生了什么?
这是一项相当复杂的发展。我们创建了一个相当复杂的自定义模块,现在我们有一个提供更完整食谱内容类型的字段。我们使用了字段类型 API 并创建了一个新的自定义字段,营养。在这个过程中,我们学习了如何创建 FieldType、Field 和 Field formatter,这些都是营养字段所需的。我们还了解了 Drupal 命名空间以及如何在自定义模块中使用它们。
操作时间 – 更新食谱内容类型以使用 NutritionInformation 字段
现在,让我们将我们的新模块投入使用,并将我们的新复合字段添加到我们的食谱内容类型中,使用我们新的自定义nutritioninfo字段来为schema.org/Recipe定义的NutrionInformation属性:
-
前往食谱内容类型的管理字段配置页面:
http://localhost/d7dev/admin/structure/types/ manage/recipe/fields。 -
现在,添加一个具有以下设置的新字段——标签:
nutrition,名称:field_nutrition_information,类型:Nutrition Information。点击保存并继续按钮:![操作时间 – 更新食谱内容类型以使用 NutritionInformation 字段]()
-
对于字段设置没有需要设置的,所以只需点击保存字段设置按钮。
-
接下来,点击快捷方式工具栏中的查找内容链接,然后点击列表中第一个食谱内容项的编辑链接。
-
在节点编辑表单的底部,你会看到我们新复合字段的一些输入。
发生了什么?
我们完成了新模块的开发。它将程序性地创建一个营养字段,并将数据以所需的具体格式显示。
摘要
在本章中,我们基于schema.org/NutritionInformation的微数据规范开发了一个自定义复合字段模块,使我们能够增强我们的食谱内容类型。然而,到目前为止,我们的营养信息字段模块仍然有些粗糙。
在下一章中,我们将介绍一些更多的代码示例,并清理一些那些粗糙的边缘。
第五章:Drupal 8 的主题化
在上一个章节中,我们学习了字段类型 API 以及如何开发自定义字段模块。现在让我们通过创建自定义主题并了解 Drupal 8 主题层的全新功能来探索前端。
在本章中,我们将学习以下内容:
-
主题是什么以及如何创建子主题
-
Drupal 8 中主题的新特性是什么
-
移动优先和响应式这两个术语的含义以及为什么 D8 基础主题是这样构建的
-
如何安装 Drush
-
如何将资源添加到你的主题中
-
Twig 模板语言的介绍
-
理解贡献模块的好处以及何时使用它们
什么是主题?
主题调整或覆盖了你的 Drupal 网站的默认外观。它通过给我们改变标记(使用模板)、功能(使用 JavaScript)和外观(使用 CSS)的能力来实现这一点。主题化是用户查看工作之前的最后开发层。是点睛之笔!
默认情况下,Drupal 8 在核心中包含了许多主题。让我们来看看它们。
在 PhpStorm 中,前往你项目的根目录,然后导航到core | themes。在这个目录中,你可以找到五个主题:Bartik、Classy、Seven、Stable 和 Stark。它们是什么,为什么它们在这里?
让我们讨论一下我们默认可用的核心主题,因为它们各自有独特的用途:
-
Bartik 一直是 Drupal 7 和 Drupal 8 的默认主题。它有很好的文档记录,维护得很好,并包含一些有用的主题功能。
-
Seven 一直是 Drupal 7 和 8 的默认管理主题。它包含的样式应用于管理界面。
-
Stark 没有任何 CSS,旨在演示来自 Drupal 的默认 HTML 和 CSS。
-
如果没有定义,Stable 将作为后备基础主题存在。
-
Classy 是一个基础主题,旨在提供 Drupal 的标准类,如果你与 Drupal 7 合作过,你会很熟悉这些类。Bartik 是 Classy 的子主题。
Classy 的描述提到它是一个基础主题。这是什么意思?基础主题的存在是为了给你提供一个更快的起点,通过为你做一些基础工作来定制你网站的样式。它们提供了一个符合标准的起点,一些有用的文档,以及如何执行某些主题任务的良好参考,例如创建新的模板文件或添加新的 CSS。如果需要,可以将多个主题链接在一起以继承功能。
虽然这些主题在 Drupal 的多个版本中存在,但 Drupal 8 的主题系统有一些显著的变化。这包括语义 HTML5 标记,Twig 模板系统(我们将在本章后面讨论),默认包含的更多 JavaScript 库,以及 CSS 类从预处理移动到模板。访问www.drupal.org/node/2356951以获取更完整的列表。
你可以下载并用作基础主题的许多许多主题。访问 www.drupal.org/project/project_theme 以查找列表。
重要的是要注意,核心中的主题不是为了调整而存在的。通过保持它们不变,我们可以在未来(如果需要)更新它们,而不会丢失任何工作。所以如果我们不能调整它们,我们如何定制我们网站的外观?
行动时间 - 创建子主题
我们需要做的是设置一个主题,以便继承我们从所选主题获得的好处,然后我们可以调整以满足我们的需求。这被称为子主题。让我们开始吧,使用 Bartik 作为父主题。
回到 PhpStorm,导航到项目的根目录,进入 themes 目录。目前这个目录除了一个 README 文件外是空的。我们将首先创建一个目录来放置我们的子主题。将自定义主题放在名为 custom 的文件夹中,将贡献的主题放在名为 contrib 的子文件夹中是一种良好的做法(关于贡献主题的更多内容将在后面介绍)。在新建的 custom 文件夹内,我们将开始创建我们的子主题。我们需要遵循一些命名规则:它必须以字母开头,并且只能使用小写字母数字和下划线。我们将命名为 recipes:
-
创建一个名为
recipes的目录。 -
在其中,我们将定义我们的主题。如果你之前使用过 Drupal 7,你可能习惯于使用
.info文件。在 Drupal 8 中,我们现在使用.info.yml文件来定义主题。除了其他事情之外,.info.yml文件可以定义元数据、样式表和块区域。这是主题中唯一必需的文件。创建文件,命名为recipes.info.yml,然后打开它。 -
将以下内容复制到您的
recipes.info.yml文件中并保存:name: Recipes type: theme base theme: bartik description: A theme for styling up some delicious recipes! core: 8.x -
将名为
logo.svg的文件从/core/themes/bartik复制到/themes/recipes。 -
现在回到我们的网站,点击快捷栏中的外观按钮。这将带您到
/admin/appearance。这是我们启用或禁用主题的地方。滚动到未安装的主题部分,找到我们的 Recipes 主题。点击安装并设置为默认按钮:![行动时间 - 创建子主题]()
-
页面刷新后,通知您 Recipes 是我们的默认主题,然后点击管理员菜单左上角的返回网站按钮。
发生了什么?
我们创建了一个使用 Bartik 作为父主题的子主题。但是 Bartik 主题内部是什么?接下来,我们将查看 Bartik 的内部工作原理,以了解主题是如何组合在一起的。
Bartik 概述
让我们看看 Bartik 目录内部有什么,并了解每个文件的作用(通过导航到 core/themes/bartik 来跟随)。该目录包括:
-
bartik.info.yml: 与我们的recipes.info.ml文件类似,此文件定义了主题所需的元数据,例如其名称、其父主题以及描述。此文件还描述了一些库、样式表和主题区域。您可以在www.drupal.org/documentation/themes/bartik找到有关包含区域的信息以及更多关于主题定制的信息。 -
bartik.libraries.yml: 此文件定义了可以加载到主题中的 CSS 库(如果 Bartik 默认有 JavaScript,它也会定义)。有关更多信息和实践示例,请参阅 添加资产到您的主题 部分。 -
bartik.breakpoints.yml: 此文件包含 Breakpoints 模块的配置(www.drupal.org/project/breakpoints)。这允许主题管理断点,以便其他模块可以利用响应式断点功能。例如,Picture (www.drupal.org/project/picture) 可以根据浏览器的大小提供调整大小的图片。有关该模块的更多信息,请参阅 移动优先,响应式主题 部分。 -
bartik.theme: 此文件包含一些支持主题化的有用函数。这些包括向标记中添加表示侧边栏或区域使用的类,向某些系统元素添加 clearfix 类,以及向表单元素添加类。 -
logo.svg: 这是默认在页眉中显示的库存标志。标志也可以在/appearance/settings中上传。 -
screenshot.png: 此图片显示在/admin/appearance页面上,在切换主题时可用于快速识别主题。 -
color目录:此目录包含 Bartik 与Color模块的集成预览功能。此模块允许您在不更改 CSS 的情况下更改主题的颜色方案。 -
css目录:此目录包含多个模块化 CSS 文件,包含主题所有组件的样式,如按钮、表单和页眉。然后根据其目的,这些文件要么组合到bartik.libraries.yml中的库中,要么直接导入到batik.info.yml中。 -
config目录:此目录包含 Bartik 配置文件的架构。 -
images目录:此目录包含主题使用的图片。 -
templates目录:此目录包含主题用于结构化网站不同区域输出的模板。模板提供 HTML 结构和一些条件逻辑。模板针对网站的具体区域,范围从相对较小的独立部分如status-messages.html.twig,到具有更广泛影响的部分,如page.html.twig,它为所有页面提供结构。您将在本章的后面部分学到更多关于模板及其编写语言(Twig)的知识。
我们现在可以开始对 Bartik 默认外观进行一些更改,因此在下节中我们将添加一些自定义 CSS 来测试我们是否在业务中。在到达那里之前,让我们讨论响应式主题和移动优先原则。
移动优先,响应式主题
Drupal 8 主题默认既支持移动优先又支持响应式设计。这些术语的含义是什么,为什么主题会这样设置?
2000 年 4 月,约翰·奥尔斯普在 alistapart.com/ 上发表了一篇名为 《网络设计之道》 的文章。这篇文章描述了一个新兴的网络设计问题——设计师和开发者希望尽可能严格地控制用户的体验,就像在印刷品中一样。他们希望网站在功能和布局方面尽可能相似,无论用户的设置如何。文章提倡一种新的方法:
| *"制作可适应的页面。制作无论读者选择或必须使用的浏览器、平台或屏幕都能访问的页面。" | ||
|---|---|---|
| --约翰·奥尔斯普 |
今天,这一概念得到了进一步扩展,因为我们还必须考虑不同的屏幕分辨率和互联网连接速度,尤其是对于移动设备。网站必须适应其用户的各种需求,并且最好提供卓越的体验,无论使用何种设备。
两种试图解决布局/屏幕尺寸问题的方法开始变得流行:自适应设计和响应式设计。自适应设计基于一系列静态布局,在检测到屏幕尺寸后向用户交付。响应式设计基于布局的流动性,能够灵活地适应设备。
如果用户调整了浏览器窗口大小,自适应网站会在静态布局之间跳转,而响应式网站则会平滑缩放。这两种方法都有其优点:自适应设计在布局控制方面提供了一种有时无法通过响应式设计实现的程度,但响应式设计在不同设备上运行得更加无缝,因此现在变得更加流行。
从历史上看,设计师和开发者首先关注桌面体验,有时使用在移动设备上效果不佳的布局和功能。这种情况偶尔还会发生,设计师将移动设计视为次要问题。有很好的理由要摒弃这种做法:在 2015-2016 年,全球大约有 45 亿手机用户。其中相当一部分将拥有互联网接入能力,当我们考虑到所有平板电脑、游戏机、手表、车载设备,甚至具有浏览网页功能的厨房电器时,仅仅关注桌面体验是短视的。
为了解决某些设备功能降低的问题,两种方法已经变得流行:优雅降级和渐进增强。结果是相同的;较老或功能较弱的设备提供较低的用户体验,但重要的是,一个可以工作的网站。然而,实现这一点的起点是相反的。优雅降级从在现代设备上提供高水平体验的起点开始,当较老设备无法提供支持时,功能会优雅地降级。渐进增强采取的方法是首先创建一个较低级别用户体验的网站,如果设备支持,则逐步增强功能。两种方法都有其优点,这两种实践的优秀例子可以在www.w3.org/wiki/Graceful_degradation_versus_progressive_enhancement找到。
这里的重要点是提供能够适应任何设备的网站,我们将讨论 Drupal 8 如何帮助您实现这一点。
移动优先是一种意味着在较小设备上智能布局内容的方法。小屏幕上的内容通常需要单列布局,因此是线性顺序,最重要的内容位于页面顶部。这并不总是如此,但这是一个有助于网络专业人士首先考虑较小设备最佳体验的心态,这通常也会导致大屏幕体验的改善。
Drupal 8 可以帮助您创建一个响应式、以移动优先的网站,无论是通过渐进增强还是优雅降级。首先,在您的浏览器中查看主页——尝试调整页面大小,看看元素是如何调整的。在移动端,我们的管理菜单使用图标而不是文本显示链接,以确保布局适应。我们网站的主要菜单位于一个可点击(或可触摸)的下拉菜单中。内容是单列布局,侧边栏位于主要内容区域下方。当浏览器窗口扩大时,管理菜单包含文本,主菜单从下拉菜单中消失,侧边栏位于主要内容区域的两侧。这是一个响应式、以移动优先行为的良好例子。这是通过 CSS 实现的。例如,查看核心 | 主题 | bartik | css | components | header.css中的样式。您可以看到默认样式在文件顶部设置,然后文件进一步深入,出现媒体查询,为布局在更大尺寸下继续良好工作提供必要的更改。如果您不熟悉媒体查询,可以在developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries找到良好的文档。
通过提供响应式、移动优先样式的良好示例,Drupal 8 为你开始自己的工作提供了帮助。
Drupal 8 现在在核心中包含了更多的 JS 库,我们稍后会讨论。目前,只需知道 Modernizr 现在可用,这是一个检测浏览器功能并提供相应调整功能性和样式的强大工具。更多信息请见modernizr.com/。
Drupal 核心现在还附带了一个名为Breakpoint的模块。这允许你根据主题定义你想要进行功能更改的浏览器宽度。这些设置与任何你可能使用的 CSS 媒体查询无关。Breakpoint 设置有助于配置其他模块,例如 Responsive Images,另一个现在在核心中的模块。这两个模块的组合允许你在网站上切换图片。你可以为小屏幕设备提供低分辨率图片,然后为更大的屏幕交换高分辨率图片。这对网站性能有很大帮助,尤其是对于移动互联网用户来说非常重要。断点设置位于主题的根目录中。Bartik 的可以在bartik.breakpoints.yml中找到。
我们现在已经了解了“移动优先”和“响应式”这两个术语的含义,以及 Drupal 8 如何为你提供开始遵循这些实践所需的工具。
在我们开始使用主题之前,我们将安装一个名为Drush的工具,这是一个用于 Drupal 的命令行工具。它将帮助我们快速通过接下来的几个部分。
行动时间 - 安装 Drush
以下部分需要具有 Unix 或 Unix-like 功能的命令行。如果你是 Mac 或 Linux 用户,Bash 将预先安装,但如果你是 Windows 用户,你需要安装 Cygwin,可以从www.cygwin.com下载。网上有许多教程可以帮助你安装和使用它。
你还需要了解一些使用终端编辑器编辑和保存文档的知识,例如nano或vi。
注意
如果你刚开始接触命令行,请注意,美元符号表示新的一行代码(也就是说,不要输入美元符号),并且每一行都需要按回车键结束。
我们将使用 Composer 安装 Drush。如果你有 Drupal 7 的先前经验并且已经安装了 Drush,请使用你首选的方法进行升级。
-
首先,我们将全局安装 Composer。在你的终端中运行以下命令:
$ curl -sS https://getcomposer.org/installer | php $ mv composer.phar /usr/local/bin/composer -
现在,使用终端编辑器打开你的
.bash_profile(如果你使用 ZSH,则为.zshrc),并添加以下内容:$ export PATH="$HOME/.composer/vendor/bin:$PATH" -
重新加载文件(或重新加载你的终端):
$ source ~/.bash_profile -
安装 Composer 后,我们可以快速轻松地安装 Drush:
$ composer global require drush/drush:dev-master注意
Composer 使得更新 Drush 或根据需要回滚到特定版本变得非常容易。它在网上有很好的文档。
Drush 有很多有用的功能。请访问www.drush.org/en/master/获取文档。
操作时间 – 将资产添加到您的主题
在 Drupal 7 中,CSS 和 JS 资产通过.info文件添加到主题中。在 Drupal 8 中,资产管理已被分离出来,我们有了资产库的概念。这些库包含我们想要附加的 CSS 和 JS。它们可以应用于全局或特定页面。让我们先创建一个全局样式资产库:
-
在您的主题目录中,创建一个名为
recipes.libraries.yml的文件。 -
将以下内容复制到您的文件中并保存:
global-styling: version: 1.0 css: theme: css/style.css: {}注意
在
.yml文件中,缩进具有意义,因此请确保前面代码中的缩进与您的文件匹配。 -
我们现在需要将我们的库附加到网站上。这可以通过两种方式完成。如果我们想将库附加到特定页面,我们可以在我们的模板中这样做。我们将在本章后面讨论这个问题。但是,当我们想将库附加到使用我们的主题的所有页面上时,我们可以将其添加到我们的 info 文件中。在
recipes.info.yml中,在文件底部复制并保存以下内容:- recipes/global-styling您的 info 文件现在应该看起来像这样:
name: Recipes type: theme base theme: bartik description: A theme for styling up some delicious recipes! core: 8.x libraries: - recipes/global-styling -
我们的库已经就位,并通过 info 文件全局应用,现在让我们创建一些 CSS 并测试它。在您的 recipes 目录中,创建一个名为
css的新目录。 -
在
css目录中,创建一个名为style.css的文件。 -
在
style.css中,复制并保存以下内容:.main-content h2 { font-size: 50px; } -
我们现在需要清除缓存。Drush 使这变得简单。打开您的终端并导航到网站的根目录。我们可以输入以下命令来重建所有缓存:
$ drush cr这将等同于访问
/admin/config/development/performance并点击以下截图所示的“清除所有缓存”按钮:![操作时间 – 将资产添加到您的主题]()
-
作为最后一步,让我们回到主页,看看我们的样式是否生效:
![操作时间 – 将资产添加到您的主题]()
发生了什么?
我们通过创建一个新的库并添加一个 CSS 文件来进行样式调整,以检查其是否工作。
好的,我们的标题变大了,这告诉我们我们的食谱主题正在工作。太棒了!
接下来,在开发过程中,通过禁用 Drupal 的一个默认性能功能,让我们尽可能简化调整我们的资产。
Drupal 使用一个名为聚合的站点性能工具。一个站点的性能可能会因为添加多个 CSS 或 JS 文件而受到影响,每个文件都需要向服务器发送一个 HTTP 请求。Drupal 为了避免这种情况,会将多个文件压缩成一个,同时进行丑化(uglification)和压缩(minification)。这意味着发出的 HTTP 请求更少,请求的文件尽可能小。然而,聚合文件不会在每次更改 CSS 或 JavaScript 时重新创建,因为它们被缓存了。这对性能很好,但在我们开发时并不那么有用。我们可以在每次更改时清除缓存,但更快的方法是暂时禁用聚合。
让我们回到/admin/config/development/performance并取消选择聚合 CSS 文件和聚合 JavaScript 文件选项。然后,点击保存配置按钮,如图所示:

关闭聚合功能后,我们可以进行更改,并且刷新浏览器时它们将立即可见。但请记住,对于生产站点,务必启用聚合功能!
行动时间 - 在特定页面上调用资产
让我们更深入地探讨资产库依赖。我们将添加一小段依赖于 jQuery 的 JavaScript 代码;当用户点击食谱描述字段时,将触发一个警告框告诉他们这是美味的。我们将设置它只在我们的食谱完整视图模式下加载(即不在我们的主页列表中)。这也将是我们对 Twig 的第一个介绍。让我们开始:
-
在 PhpStorm 中,在我们的
recipes主题中,创建一个名为js的目录。 -
在
js目录下,创建一个名为delicious.js的文件。 -
虽然这个 JS 文件的内容只是为了演示概念,但我们将使用良好的实践创建并记录它,这样你就可以将其作为你自己的工作的基础。我们将从定义文件、设置描述,然后创建我们的函数开始:
/** * @file * A delicious notification alert */ var delicious = (function ( ) { })(); -
在函数之后,我们将告诉 jQuery 在页面加载事件上加载这个函数:
jQuery(delicious.onPageLoad); -
现在,我们将开始添加函数的内容。我们将在函数内部调用时创建一个将被返回的对象:
/** * Pubic object. * @type {{}} */ var self = {}; -
在我们的
self对象之后,我们将创建我们的 jQuery 选择器,选择我们想要绑定click函数的内容:/** * The delicious recipe content. * * @type {jQuery} */ var $deliciousContent = jQuery('.delicious-content'); -
接下来,我们将绑定内容点击事件并创建我们的警告函数,我们将使其可翻译:
/** * Bind the recipe content click event. */ var bindRecipeContentEvent = function () { $deliciousContent.on('click', function () { deliciousAlert(); }); }; /** * Alert the user that this is delicious. */ var deliciousAlert = function () { window.alert(Drupal.t("This is delicious!")); }; -
最后,我们将设置
self对象的属性为一个匿名函数,该函数在页面加载时调用bindRecipeContentEvent函数。然后我们将返回该对象:/** * Page load event. */ self.onPageLoad = function () { bindRecipeContentEvent(); }; return self;delicious.js的内容现在应该是:/** * @file * A delicious notification alert */ var delicious = (function ( ) { /** * Pubic object. * @type {{}} */ var self = {}; /** * The delicious recipe content. * * @type {jQuery} */ var $deliciousContent = jQuery('.delicious-content'); /** * Bind the recipe content click event. */ var bindRecipeContentEvent = function () { $deliciousContent.on('click', function () { deliciousAlert(); }); }; /** * Alert the user that this is delicious. */ var deliciousAlert = function () { window.alert(Drupal.t("This is delicious!")); }; /** * Page load event. */ self.onPageLoad = function () { bindRecipeContentEvent(); }; return self; })(); jQuery(delicious.onPageLoad); -
在
delicious.js就位后,我们现在需要将其添加为资产库。在 PhpStorm 中打开recipes.libraries.yml。在全局样式资产库下方,我们将创建另一个,命名为delicious。这个库将包括我们刚刚创建的 JS 文件,并且还将指定我们对 jQuery 的依赖,其中有一个版本位于核心中供我们使用。(在 Drupal 8 中,核心包含许多用于依赖的库,包括 jQuery UI、Modernizr 和 Backbone 等。有关完整列表,请参阅core/core.libraries.yml):delicious: version: 1.0 js: js/delicious.js: {} dependencies: - core/jquery -
最后一个步骤是将资产库添加到网站上。这次,我们不想它全局应用,因为我们只想在需要它们的特定页面上加载
delicious.js和 jQuery。为此,我们将创建一个非常具体的 Twig 模板。现在我们先不深入细节,因为它们将在本章后面的部分进行讲解。在recipes目录下,创建一个名为templates的新目录,然后在其中创建一个名为field的目录。 -
在
recipes|templates|field内部,创建一个名为field--node--field-description--recipe.html.twig的文件。 -
在该文件中,粘贴以下代码。如前所述,我们现在先不深入讲解 Twig,但请注意,我们正在为我们的 JS 应用一个名为
delicious-content的类,并且我们使用attach_library函数附加资产库。这意味着资产库只会在包含特定食谱描述字段的页面上加载:{% set classes = [ 'field', 'field--name-' ~ field_name|clean_class, 'field--type-' ~ field_type|clean_class, 'field--label-' ~ label_display, 'delicious-content', ] %} {% set title_classes = [ 'field__label', label_display == 'visually_hidden' ? 'visually-hidden', ] %} {{ attach_library('recipes/delicious') }} {% if label_hidden %} {% if multiple %} <div{{ attributes.addClass(classes, 'field__items') }}> {% for item in items %} <div{{ item.attributes.addClass('field__item') }}>{{ item.content }}</div> {% endfor %} </div> {% else %} {% for item in items %} <div{{ attributes.addClass(classes, 'field__item') }}>{{ item.content }}</div> {% endfor %} {% endif %} {% else %} <div{{ attributes.addClass(classes) }}> <div{{ title_attributes.addClass(title_classes) }}>{{ label }}</div> {% if multiple %} <div class='field__items'> {% endif %} {% for item in items %} <div{{ item.attributes.addClass('field__item') }}>{{ item.content }}</div> {% endfor %} {% if multiple %} </div> {% endif %} </div> {% endif %} -
使用 Drush 清除我们的缓存:
$ drush cr
好的,让我们测试一下它是否工作!在你的浏览器的主页上,点击列表中的一项食谱的标题,进入完整的节点。当你点击描述部分中的任何文本时,我们的警告应该会出现:

太棒了!现在,让我们测试一下delicious.js是否只在我们期望的页面上加载。如果我们查看当前页面的源代码并搜索它,我们显然会找到它:

在主页上执行相同的搜索,将找不到任何结果。这正是我们希望的。
这究竟发生了什么?
我们创建了一个简单的 JS 代码,将其作为一个带有 jQuery 依赖的资产库添加,并使用 Twig 模板确保资产库只会在包含正确字段的页面上加载。
模板化和 Twig 简介
我们在上一节中跳过了模板和 Twig,所以让我们更深入地讨论它们。
模板将成为你在主题化 Drupal 体验中的主食。它们是我们逻辑过滤数据并创建所需 HTML 结构的方式。Drupal 网站上标准页面的构建是由一组嵌套模板组成的。在嵌套结构的每个级别,都有不同的变量可供你使用,允许你渲染所需的内容。提到的任何模板都可以复制到你的主题中,无论是从 Drupal 核心还是父主题。所有模板的基版本都位于核心中,但你的父主题可能已经有一些模板可以复制,这些模板的结构可以帮助你达到目标的大部分,或者可能已经使用了一个特定命名的模板,你想覆盖它。
这里有一些我们主页上使用的模板示例:

对于从 Drupal 7 迁移到 Drupal 8 的用户,这里有一个快速提示——你可能想知道主题函数——它们已经不再存在了。所有可主题化的输出都通过模板运行。
模板嵌套层次结构从定义 HTML 页面基本结构的模板开始,包括<head>和<body>标签。执行此操作的基模板是html.html.twig,位于core/modules/system/templates/html.html.twig。阅读这些基文件中提供的注释,了解模板的用途以及可用变量的列表是非常有价值的。
之后,是页面模板。这些模板的基模板是page.html.twig,位于core/modules/system/templates/page.html.twig。这是定义诸如<header>、<main>和<footer>等元素的地方。
很常见的是想要创建页面模板的特定实例;例如,一个具有与网站默认页面不同结构的自定义首页。我们通过使用主题钩子建议来实现这一点。
主题钩子建议
主题钩子建议是你可以创建的替代模板,用于覆盖默认模板文件。它们允许你通过使用特定的命名约定在你的主题中实现有针对性的覆盖。
当渲染一个元素时,Drupal 会查找模板的特定性——它会使用它能找到的最具体的模板。例如,当它渲染你的页面时,如果你在你的主题中创建了一个名为page-front.html.twig的模板,它就会使用这个模板来渲染你的首页。如果没有找到这样的文件,它会寻找下一个最具体的模板,并继续这样做,直到达到默认的page.html.twig。
在下一节中,我们将继续介绍 Twig 调试,你将学习如何找出你的网站当前正在使用的模板,以及发现一些模板命名建议以覆盖它们。
对于更多信息示例,请参考以下内容:
目前,让我们继续探讨模板及其层次结构。在html.html.twig模板定义了最高级别的<html>、<head>和<body>标签,以及page.html.twig定义了<header>、<main>和<footer>区域之后,我们嵌套模板树中的下一级是区域模板。这些模板能够渲染网站的各个区域(例如,Bartik 主题中的区域示例有sidebar_first、sidebar_second和footer_first,这些在它的.info.yml文件中定义)。基本区域模板region.html.twig位于core/modules/system/templates/region.html.twig。
在我们的区域中,可以渲染任意数量的节点、块或字段。有很多 Drupal 术语需要学习,如果你对此感到陌生,最好的开始方法是彻底阅读位于www.drupal.org/node/2186401的主题指南中的模板部分。在那里,你可以找到更多关于模板类型、它们的基本位置以及如何覆盖它们的信息。
当我们最初查看核心主题时,你可能已经注意到还有一个名为engines的其他目录。这个目录包含了 Twig 模板引擎,它取代了 Drupal 7 中的默认 PHPTemplate(尽管不推荐,但仍然可以使用 PHPTemplate)。如果你是 Drupal 的新手,模板引擎,有时也称为主题引擎,是结合我们的数据和主题模板的软件组件,并输出 HTML。
PHPTemplate 有什么问题?有很多:
-
模板拥有过多的权限。例如,如果主题开发者忘记对用户提交的文本进行清理,这会使网站面临安全漏洞(跨站脚本)。甚至有可能从模板文件中删除网站的数据库!想要随意删除
users表吗?<?php db_query('DROP TABLE {users}'); ?> -
它很复杂。有很多模板和更多的
theme()函数。它为不同的数据结构提供了不同的语法。总的来说,学习曲线陡峭,这使得许多开发者望而却步。 -
标记语言并不美观,也不便于人类阅读。
-
它是针对 Drupal 特定的。
那为什么选择 Twig?
-
它更安全。PHP 函数,如
db_query,根本不可用,并且默认启用自动转义,因此跨站脚本攻击不再是问题。 -
语法非常易于阅读和使用。
<?php print render($content); ?>变为{{ content }}。更多语法示例将在后续部分中提供。 -
模板是可扩展的,多亏了它的
include功能。 -
Twig 不是 Drupal 特定的,因此我们学习可转移的技能。
-
它有很好的文档记录。请参阅
twig.sensiolabs.org/documentation。
转向 Twig 可能是 Drupal 8 中最激进的前端变化。无论你是 Drupal 新手还是从 Drupal 7 转移过来,你都需要学习一些新内容。但正如你将看到的,语法清晰且易于阅读,你投入学习它的努力很快就会在编写模板时得到回报!在我们使用它之前,让我们先看看它的功能和语法。如果你是 Drupal 新手,以下部分可能一开始会感到困惑,但当你开始使用 Twig 时,它将作为一个很好的参考点。
文件和函数名
-
PHPTemplate 文件名语法:
block--search-form-block.tpl.php -
Twig 文件名语法:
block--search-form-block.html.twig -
PHPTemplate 函数:
theme_node_links() -
Twig 文件:
node-links.html.twig
括号语法
Twig 有三种括号类型:
-
{# #}:这用于注释。这些括号内的文本永远不会被输出。 -
{{ }}:这用于渲染。我们将在后续部分中看到一些示例。 -
{% %}:这用于控制结构,如for循环或条件语句。我们将在后续部分中看到一些示例。
渲染
变量的渲染是通过在打印括号语法中输入变量名来完成的:{{ car }}
让我们用一个 PHP 中的多维car数组示例:
$car = array(
'maxSpeed' => 200,
'gearbox' => 'six-speed',
'optionalUpgrades' => array(
'engine' => array(
'upgrade' => 'turbo-charged engine',
'price' => 500,
),
'windows' => array(
'upgrade' => 'tinted windows',
'price' => 150
),
),
);
要打印变量的属性,使用强大的“点”符号。{{ car.maxSpeed }}返回200。
这个点语法检查了maxSpeed的多种可能情况。以下是其处理过程:
-
检查
car是否是一个数组,maxSpeed是否是一个有效的元素 -
如果不是,并且
foo是一个对象,请检查maxSpeed是否是一个有效的属性 -
如果不是,并且
foo是一个对象,请检查maxSpeed是否是一个有效的函数(即使maxSpeed是构造函数——请使用__construct()代替) -
如果不是,并且
foo是一个对象,请检查getMaxSpeed是否是一个有效的函数; -
如果不是,并且
foo是一个对象,请检查isMaxSpeed是否是一个有效的函数; -
如果不是,则返回一个空值
它是多维的:{{ car.optionalUpgrades.engine.upgrade }}返回turbo-charged engine。
在撰写本文时,渲染数组中的 hashkeys 无法使用点语法进行定位。为此有一个活跃的问题:www.drupal.org/node/2160611,但到目前为止,需要使用语法['#haskey'],即{{ item.property['#markup'] }}.
过滤器
使用 Twig,我们可以在渲染之前通过一个过滤器推送一个变量:{{ car.gearbox |capitalize }} 返回 Six-speed。除了 Twig 的默认过滤器外,Drupal 还包括了一些额外的过滤器,例如翻译:{{ item |t }}。
在 twig.sensiolabs.org/doc/filters/index.html 查看默认 Twig 过滤器的完整列表。
你可以在 core/lib/Drupal/Core/Template/TwigExtension.php 中的 getFilters() 内找到 Drupal 的额外过滤器的列表。
控制结构
以下是不用的控制结构:
-
if语句:{% if car.maxSpeed %} <div class="max-speed">This car will reach {{ car.maxSpeed }} kph!</div> {% endif %} -
for循环:{% for item in optionalUpgrades %} <li class="item-list upgrades"> {{ item.upgrade }} </li> {% endfor %} -
变量创建:
{% set seatFinish = "leather" %} -
数组创建:
{% set colourScheme = [ 'blue', 'white', ] %}
Twig 调试
Twig 默认提供了一款出色的调试工具。在 Drupal 8 中,它得到了进一步的扩展。我们将在下一节中启用它并开始使用它,但现在,让我们看看它为我们提供了哪些能力。
标记中的 HTML 注释
Drupal 8 在这里扩展了默认的 Twig 调试工具,为我们提供了模板建议的信息,以及显示当前标记来自哪个模板:

在前面的屏幕截图中,我们可以看到主页上食谱标题字段的注释。我们可以看到当前使用的模板来自 Classy 主题。如果我们需要创建更具体或更不具体的模板来满足我们的需求,我们还可以看到模板名称建议。
调试变量
要打印模板文件中所有可用的变量,我们可以使用 dump() 函数。
我们可以打印所有可用的变量:
{{ dump() }}
Twig 模板中有几个全局变量:
-
_context:这包含传递给模板文件的所有变量,例如由 preprocess 准备的变量、来自theme()的变量或在模板内设置的变量 -
{{ dump() }}:这相当于{{ dump(_context) }} -
{{ dump(\_context|keys) }}:要打印可用的键 -
_charset:这引用了(当前)字符集
但请注意,{{ dump() }} 可能会导致白屏死机。该过程递归地移动并通过打印一个可能非常长的变量列表,这可能导致内存损失。如果是这种情况,你可以做如下操作:
<ol>
{% for key, value in _context %}
<li>{{ key }}</li>
{% endfor %}
</ol>
要打印特定变量的内容,例如 $car:
{{ dump($car) }}
要打印出 $car 和 $truck 变量的内容:
{{ dump($car, $truck) }}
Kint
{{ dump() }} 是一个出色的工具,但如果它应用到的元素包含大量信息,输出可能难以解释。用于为网站创建虚拟内容的 Devel 模块包含一个名为 Kint 的子模块。此模块可以使用 Drush 启用:
$drush en kint
启用后,所有 {{ dump() }} 命令都可以替换为 {{ kint() }},这将提供更友好的输出。
行动时间 – Twig 实践
好的,让我们将上一节中学到的一些知识应用到实践中。将我们的食谱中的准备阶段与烹饪阶段分开将是个不错的选择。我们将通过将两种类型的字段分别放入单独的包装 div 标签中来实现这一点,然后应用一些样式来帮助我们区分它们:
-
我们将通过启用 Twig 调试来找出当前正在使用的模板。在 PHPStorm 中,导航到 sites / default。如果你有一个名为
services.yml的文件,打开它。如果没有,复制default.services.yml,将其重命名为services.yml,然后打开它。 -
查找名为
twig.config的部分。在这个部分中,你会找到一个当前设置为 debug: false 的选项。将该值更改为 true 并保存。就像 CSS 和 JS 的聚合一样,这将改善你的开发条件,但确保在网站上线前将其关闭。 -
如果在步骤 3 中遇到了权限错误,你将需要在 sites / default 目录中重置权限。在终端中,导航到 sites 目录,然后输入以下命令:
$ chmod 755 default -
我们还将启用 Kint 来帮助我们进行调试:
$ drush en kint启用调试后,让我们查看之前提到的 HTML 输出。在你的浏览器中,点击进入一个完整的食谱页面(我将使用 Awesome Sauce 食谱,并在图片字段中添加了一张图片),然后使用你选择的检查器检查 HTML。(右键点击或 cmd + 点击一些内容,然后根据你的浏览器和工具,你会找到一个 Inspect 选项。)注意:如果你使用 Firebug,确保已开启注释:
![行动时间 – Twig 实践]()
在这个阶段,你将有很多信息可供参考,但很快你会发现你可以相对容易地找到你需要的信息。找到具有
node--type-recipe类的<article>元素。这是所有元素都被包裹在一个包含 div 中的结构点,因此这是我们需要的分割点:![行动时间 – Twig 实践]()
元素的相关模板信息位于其上方。正如我们从绿色注释中可以看到的,当前使用的模板是
core/themes/bartik/templates/node.html.twig。我们还可以看到一些文件名建议,我们可以从中选择以获得额外的具体性。我们将使用node--recipe--full.html.twig建议的文件名,正如其名,它仅适用于完整视图模式。 -
在 PHPStorm 中,在你的主题模板目录内,创建一个名为 content 的目录。请注意,目录名称不会影响 Drupal 查找模板的能力。它们的存在是为了帮助开发者保持工作有序。在 content 目录内,创建一个名为
node--recipe--full.html.twig的文件。 -
现在,我们将把
core/themes/bartik/templates/node.html.twig的内容复制到我们新创建的node--recipe--full.html.twig中,这样我们就可以开始覆盖它。之后,你的文件内容应该如下所示(不包括长度注释):{# {% set classes = [ 'node', 'node--type-' ~ node.bundle|clean_class, node.isPromoted() ? 'node--promoted', node.isSticky() ? 'node--sticky', not node.isPublished() ? 'node--unpublished', view_mode ? 'node--view-mode-' ~ view_mode|clean_class, 'clearfix', ] %} {{ attach_library('classy/node') }} <article{{ attributes.addClass(classes) }}> <header> {{ title_prefix }} {% if not page %} <h2{{ title_attributes.addClass('node__title') }}> <a href="{{ url }}" rel="bookmark">{{ label }}</a> </h2> {% endif %} {{ title_suffix }} {% if display_submitted %} <div class="node__meta"> {{ author_picture }} <span{{ author_attributes }}> {% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %} </span> {{ metadata }} </div> {% endif %} </header> <div{{ content_attributes.addClass('node__content', 'clearfix') }}> {{ content }} </div> </article>在对文件进行任何操作之前,看看你是否能发现我们一直在讨论的一些内容:数组创建、条件逻辑和变量渲染。
-
我们感兴趣的是
{{ content }}变量,因为我们需要深入挖掘这个变量以找到我们想要拆分到两个单独的包装div标签中的不同字段。我们将使用 Kint 来调试其内容。将{{ content }}调整为{{ kint(content) }},然后使用以下命令清除缓存:$ drush cr. -
当你重新加载页面时,内容将消失(因为它不再被渲染),取而代之的是调试信息。打开数组,然后关闭每个默认打开的内部数组项,这样你将剩下以下内容:
![行动时间 – Twig 实践]()
我们可以看到内容数组包含八个项目,其中七个具有我们页面上不同字段的名称作为键。我们将使用这些来打印我们的自定义输出。
-
有些字段既不是准备也不是烹饪相关的,我们希望这些字段保持正常渲染。例如描述和图片。但我们想排除其他所有内容,以便单独打印。我们将使用一个名为
without的 Drupal Twig 过滤器,它可以在不渲染特定内部项目的情况下渲染一个元素。回到模板中,将{{ kint(content) }}替换为{{ content|without('field_cooktime', 'field_preptime', 'field_ingredients', 'field_recipeinstructions') }}。现在我们的页面将只包括我们仍然希望以正常方式渲染的项目:![行动时间 – Twig 实践]()
-
现在我们将在单独的
div标签中渲染字段。直接在最后一个 Twig 语句下面,粘贴以下内容:<div class="preparation recipe-phase"> {{ content.field_preptime }} {{ content.field_ingredients }} </div> <div class="cooking recipe-phase"> {{ content.field_cooktime }} {{ content.field_recipeinstructions }} </div> -
现在,我们将回到主题的
style.css文件,在css目录中添加一些样式。在现有样式下面粘贴并保存以下内容:/* Override default image float on the recipe node */ .node--type-recipe .field--name-field-image { float: none; } .recipe-phase { padding: 20px; margin-bottom: 20px; } .preparation { background-color: #e7e7e7; } .cooking { background-color: #b0fbad; }让我们再次查看页面:
![行动时间 – Twig 实践]()
发生了什么?
我们使用 Kint 和 Twig 调试来确定所需的模板以及我们可用的变量。我们使用一个特定的 Drupal Twig 过滤器来渲染内容并排除一些项目,然后在自定义的结构中单独渲染这些项目。
我们使用这种方法得到了我们想要的结果,但我们需要将数组拆分。对于具有更多字段的大型内容类型,这将变得繁琐。还有一种方法,需要使用贡献模块。让我们看看那个方法。
行动时间 – 理解贡献模块的好处
让我们尝试实现之前章节中得到的相同结果,但这次是通过使用名为field_group的贡献模块:
-
在recipes / templates / content中删除
node--recipe--full.html.twig(或者如果你希望保留工作副本,可以将文件移出项目外)并清除你的缓存。 -
在命令行中,导航到你的 Drupal 项目内部任何位置,并输入以下命令:
$ drush en field_group -y如果你已经下载了
field_group模块,此命令将启用该模块。如果没有,它将为你下载最新的稳定版本并启用它。 -
现在回到浏览器,导航到
/admin/structure/types/manage/recipe/display,这是食谱内容类型的显示设置所在位置。你会在页面底部看到一个新功能:![执行时间 – 理解贡献模块的好处]()
-
在两个文本输入中输入
preparation,从下拉菜单中选择Html 元素,然后点击保存。 -
页面重新加载后,你会看到准备组现在位于禁用标签上方。在准备组项的右侧有一个齿轮图标。点击它。
-
组的设置现在将打开。除了额外 CSS 类文本输入外,其他所有内容保持默认,你可以在这里输入
preparation recipe-phase,然后点击更新按钮。![执行时间 – 理解贡献模块的好处]()
-
再次执行这些步骤,这次将
preparation替换为cooking。完成后,你应该有两个组被启用。 -
现在进行一些拖放操作。将组字段拖到recipeYield字段下方,然后抓住prepTime字段并将其拖到准备组项上方。你会看到图标自动向右移动,好像缩进了。将字段放在那里,然后将ingredients字段拖到准备项区域,并在prepTime下方放下。接着将cookTime和recipeInstructions拖到烹饪组区域。完成后,你的项目应该看起来像这样:
![执行时间 – 理解贡献模块的好处]()
-
点击保存按钮。页面刷新后,你可以看到一个如下通知:
![执行时间 – 理解贡献模块的好处]()
-
点击管理菜单左上角的返回网站按钮。我们已经实现了相同的结果:
![执行时间 – 理解贡献模块的好处]()
发生了什么?
我们移除了使用 Twig 模板所做的模板,然后使用field_group模块复制了该功能。
我们能够如此迅速和轻松地实现对标记的控制,这确实很 neat。更重要的是,这种方法不仅适用于网站构建者,也适用于开发者,如果添加更多字段,它将继续是更新内容类型的一种更快捷的方法。在这种情况下,我们应该如何决定是否在我们的网站上使用贡献模块呢?
贡献模块是什么?
贡献模块,或称为 contrib 模块,是 Drupal 功能的组成部分,由社区贡献并由开发者社区维护,有时由热衷于支持开源的公司赞助。您可以在 Drupal 的模块部分找到所有 contrib 模块,网址为 www.drupal.org/project/project_module 。field_group 模块可以在 www.drupal.org/project/field_group 找到。
我如何知道一个模块是否安全使用?
如果没有事先的知识,随机选择和选择模块使用确实存在风险,但 Drupal (www.drupal.org/) 上关于模块的大量信息可以在使用前为您提供信息。这些包括:
-
维护项目的开发者数量。代码被查看得越多,它就越有可能质量好。
-
提交的数量以及提交的时间。活跃的维护意味着模块更有可能受到任何最近的安全问题的保护。
-
问题与错误队列。这更多关乎对问题的响应程度,而不仅仅是错误或问题的数量。
-
依赖于它的模块数量。如果一个模块很出色,越来越多的开发者会对其作为依赖项感到自信。许多核心模块就是这样开始的。
-
模块是否具有良好的文档?
-
下载量和报告的安装量。同样,查看得越多,越好,用户越多,越有可能在早期发现错误。
所有这些都可以在模块的 Drupal (www.drupal.org/) 页面上探索。
使用贡献模块还是自定义代码更好?
对于这个问题,没有绝对的答案。贡献模块的好处包括以下方面:
-
安全性。如果许多其他开发者正在使用它,这可以给您在代码质量和安全修复方面的信心。
-
您不需要维护(除非您选择将其贡献回去)。
-
易用性。就像
field_group的例子一样,它允许那些开发经验较少的人配置一个功能强大的网站。 -
是一个好的学习机会。深入模块,探索它是如何编码的。
-
通常更具可扩展性。
自定义代码的好处包括:
-
对独特问题的简洁、非臃肿的解决方案。
-
网站性能。具有大量额外功能的大型 contrib 模块会对网站性能产生负面影响,而简洁、编写良好的代码只会解决单个问题。
-
它为你提供了一个提高和测试你的开发技能的机会。
摘要
在本章中,我们探讨了 Drupal 8 的主题化,从什么是主题以及如何创建一个主题,到加载和使用 CSS 和 JS 资产,以及模板化,以及何时以及为什么使用贡献模块。
在下一章中,我们将探索 CKEditor,并介绍 Drupal 8 的 Block API。
第六章. 增强内容作者的用户体验
在本章中,我们将探讨 Drupal 8 核心中内置的 WYSIWYG 编辑器,并探讨 CKEditor 插件、Drupal 插件 API 和块 API 的概念。
在本章中,我们将学习:
-
如何使用 Drupal 内置的 WYSIWYG 编辑器
-
如何进行内联编辑以及如何充分利用它
-
如何通过配置进行更改并保存它们
-
如何通过新的块 API 创建自定义块
-
如何在我们的自定义块中包含默认配置
Drupal 8 中 CKEditor 的快速介绍
Drupal 8 终于发布了默认的 WYSIWYG 编辑器:CKEditor。CKEditor 是一个开源的文本编辑器,旨在将文字处理功能带到网页上。在 Drupal 的早期版本中,几个模块试图填补这一空白,但它们的配置大多数时候有点棘手,因为它们依赖于外部 JavaScript 库。
将 CKEditor 的功能直接集成到核心中,Drupal 8 可以为内容编辑器提供更丰富的创作体验。用户可以获得一个拖放界面来自定义可用功能并将配置导出以与其他系统共享。开发者可以获得一种统一的方式来访问属性、添加插件和扩展编辑器的功能:

配置 CKEditor 配置文件
如果没有根据编辑器的喜好进行自定义的能力,CKEditor 配置文件将不会太多。现在我们将自定义基本 HTML 配置文件。登录到您的 Drupal 8 网站,您应该会看到新的管理工具栏。
执行时间 - 向基本 HTML 配置添加一些按钮
-
点击 配置 然后点击 文本格式和编辑器:
![执行时间 - 向基本 HTML 配置添加一些按钮]()
-
在 文本格式和编辑器 页面上,点击 基本 HTML 配置旁边的 配置 按钮:
![执行时间 - 向基本 HTML 配置添加一些按钮]()
-
滚动到 工具栏配置,将图像图标从工具栏拖到其他元素池中,以从配置中移除它。注意,图像和配置不再是页面的一部分:
![执行时间 - 向基本 HTML 配置添加一些按钮]()
-
在页面底部最下方点击 保存配置:
![执行时间 - 向基本 HTML 配置添加一些按钮]()
-
就这样!如果你访问内容页面表单并选择 基本 HTML 配置,那么图像按钮将不再出现:
![执行时间 - 向基本 HTML 配置添加一些按钮]()
发生了什么事?
按照此程序,我们通过从 CKEditor 配置文件中移除上传图像按钮来自定义现有配置文件。
配置在 D8 中可导出,并可导入到其他环境中。
行动时间 - 导出 CKEditor 配置
-
点击 配置。然后点击 配置同步 并选择 导出 选项卡。点击 单个项目。
-
在 配置类型 中选择 文本编辑器,在 配置名称 中选择 基本 HTML:
![行动时间 - 导出 CKEditor 配置]()
-
通过点击 配置。然后点击 配置同步 并选择 导入 选项卡。点击 单个项目:
![行动时间 - 导出 CKEditor 配置]()
发生了什么?
您已配置了一个现有配置文件,删除了一些字段,并学习了如何导出该配置并将其导入到另一个环境中。
添加新的 CKEditor 配置文件
通常,内置配置文件可以满足网站的需求。然而,默认配置文件不提供匿名用户在输入文本(例如,评论)时获得流畅编辑体验的方法。为了提供该功能,我们需要创建一个新的配置文件并将其分配给匿名用户。
行动时间 - 为匿名用户创建仅文本的控制配置文件
-
从管理员工具栏进入 配置 | 文本格式和编辑器 并选择 添加文本格式:
![行动时间 - 为匿名用户创建仅文本的控制配置文件]()
-
在名称中输入
Text only controls。勾选 匿名用户 并在 文本编辑器 下拉框中选择 CKEditor。 -
添加 下划线 按钮,并保留 粗体、斜体和有序/无序列表。
-
启用以下过滤器:
-
限制允许的 HTML 标签并纠正错误的 HTML
-
将换行符转换为 HTML(即
和)
-
纠正错误的和截断的 HTML
-
-
保存配置:
![行动时间 - 为匿名用户创建仅文本的控制配置文件]()
-
文本格式和编辑器 配置页面应包含新创建的格式。将新格式移动到 受限 HTML 格式之上,这是 Drupal 为匿名用户提供的一个替代方案,即完全跳过编辑器:
![行动时间 - 为匿名用户创建仅文本的控制配置文件]()
-
就这样!如果您访问允许匿名用户发表评论的页面,新创建的格式将用于捕获这些评论:
![行动时间 - 为匿名用户创建仅文本的控制配置文件]()
发生了什么?
您刚刚添加了一个新的 CKEditor 配置文件,针对您网站上的匿名用户,使他们能够输入富文本和列表。
经典编辑器和内联编辑
我们迄今为止看到的示例使用了 CKEditor 作为后端。这是基于在页面中添加 iframe,并且它们不共享前端上显示的 CSS 样式。然而,您可以通过在您的 .info.yml 主题文件中包含以下内容来自定义编辑器的 CSS:
ckeditor_stylesheets[] = css/ckeditor-iframe.css
Drupal 8 中的一个令人兴奋的新功能是使用内联编辑。这用于快速就地编辑,并且不使用 iframe。内联编辑使编辑器能够快速编辑内容的一部分,而无需编辑整个节点。CSS 样式从主题继承,从而提供真正的 WYSIWYG 体验。
操作时间 – 使用内联编辑
-
确保已启用 快速编辑 模块。
-
点击页面左上角的铅笔图标,并选择 快速编辑:
![操作时间 – 使用内联编辑]()
-
新的编辑器实例在内容区域的顶部打开,您可以直接开始编辑文本:
![操作时间 – 使用内联编辑]()
-
就这些了!现在您可以使用这个功能,而不是访问编辑表单来快速编辑内容。
向 CKEditor 添加小部件
CKEditor 默认配置了一系列可以添加到配置文件中的按钮。作为程序员,您可以扩展 CKEditor 并添加自己的按钮。这可以通过添加插件或小部件来实现。两者之间的区别在于,小部件是将多个组件的行为组合在一起的插件。一个小部件的例子是一个图像,其中图像本身、替代文本和标题组成一个项目,并且它们可以作为单个项目在 WYSIWYG 区域中移动。
要使用额外的小部件,您需要做两件事:
-
下载或创建一个 CKEditor 插件。
-
告诉 Drupal 核心应该加载一个新的 CKEditor 插件。
要了解更多关于 CKEditor 方面的信息,请阅读有关添加 CKEditor 插件或 CKEditor 小部件的文档。CKEditor 的插件和小部件可以从 ckeditor.com/addons/plugins/all 下载。
一旦您有了 CKEditor 插件,您需要通过 \Drupal\ckeditor\CKEditorPluginInterface 告诉 Drupal 核心需要加载一个新的 CKEditor 插件。这将创建 CKEditor JavaScript 插件和 Drupal CKEditor 插件插件(尽管名称有些令人困惑)之间的 1:1 关系。通过 Drupal\ckeditor\CKEditorPluginBase 提供了一个默认实现,因此并非每个方法都需要由 CKEditor 插件实现。
此外,还可以在 Drupal 端实现以下接口以扩展插件功能:
-
\Drupal\ckeditor\CKEditorPluginButtonsInterface允许 CKEditor 插件定义它提供的按钮,以便用户可以通过基于拖放的用户界面配置 CKEditor 工具栏实例。 -
\Drupal\ckeditor\CKEditorPluginContextualInterface允许 CKEditor 插件根据上下文自动启用自己:如果某个其他 CKEditor 插件的按钮被启用,如果某个过滤器被启用,如果某个 CKEditor 插件的设置具有某个值,或者这些条件的组合。 -
\Drupal\ckeditor\CKEditorPluginConfigurableInterface允许 CKEditor 插件定义一个设置表单来配置此 CKEditor 插件可能具有的任何设置。 -
\Drupal\ckeditor\CKEditorPluginCssInterface允许 CKEditor 插件定义要加载到 iframe 实例中的 CKEditor 的额外 CSS。
要做到这一点,您需要创建一个新的模块,利用 Drupal Plugin API。尽管 CKEditor 和 Drupal 共享插件的符号,但请注意,这些是非常不同的东西。根据 Drupal Plugin API,插件实现必须使用@CKEditorPlugin注解进行注解,以便它们可以被发现。
在创建 CKEditor 插件时,请记住,您正在创建旨在为内容编辑器提供功能,因此用户界面和用户体验应该非常出色。查看ckeditor.drupalimage.admin.js和ckeditor.stylescombo.admin.js以获取良好实现的示例(另请参阅www.drupal.org/node/2567801)。
在本章的下一节中,我们将广泛使用 Drupal Plugin API 来添加一个新块,但 Drupal 插件本质上是可以互换的功能组件。
尝试英雄 - 创建 CKEditor 插件并允许 Drupal 发现它
要声明一个 CKEditor 插件的 Drupal 实例,您需要遵循以下步骤:
-
按照以下结构创建您的模块结构。在
js目录中放置 CKEditor 插件,而 Drupal 插件则放置在lib目录中:![尝试英雄 - 创建 CKEditor 插件并允许 Drupal 发现它]()
-
确保 CKEditor 插件(在
js目录中命名为plugin.js)按照 Drupal 的预期进行命名空间:(function ($, Drupal, drupalSettings, CKEDITOR) { /* existing plugin code */ })(jQuery, Drupal, drupalSettings, CKEDITOR) -
创建包含类的文件:
DRUPAL_ROOT/modules/MODULE_NAME/lib/Drupal/PLUGIN_NAME/Plugin/CKEditorPlugin/PLUGIN_NAME.php -
在该文件中扩展
CKEditorPluginBase类:<?php /** * @file * Contains \Drupal\PLUGIN_NAME\Plugin\CKEditorPlugin\PLUGIN_NAME. */ namespace Drupal\PLUGIN_NAME\Plugin\CKEditorPlugin; use Drupal\ckeditor\CKEditorPluginBase; use Drupal\Core\Plugin\PluginBase; use Drupal\ckeditor\CKEditorPluginInterface; use Drupal\ckeditor\CKEditorPluginButtonsInterface; use Drupal\ckeditor\CKEditorPluginConfigurableInterface; use Drupal\ckeditor\CKEditorPluginContextualInterface; use Drupal\editor\Entity\Editor; use Drupal\ckeditor\Annotation\CKEditorPlugin; use Drupal\Core\Annotation\Translation; /** * Defines the "PLUGIN_NAME" plugin. * @see MetaContextual * @see MetaButton * @see MetaContextualAndButton * * @CKEditorPlugin( * id = "PLUGIN_NAME", * label = @Translation("PLUGIN_NAME") * ) */ class PLUGIN_NAME extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface, CKEditorPluginContextualInterface { // your code here }
更多信息,您可以访问Drupal\ckeditor命名空间的文档,网址为api.drupal.org/api/drupal/namespace/Drupal%21ckeditor/8.2.x。
Drupal 8 的 Block API 简介
在 Drupal 7 中,添加一个块只是实现一个钩子,如hook_block_info(),以及更多在自定义模块中的钩子。然后,该块就可以在用户界面中供您放置在任何您想要的位置。
在 Drupal 8 中,由模块提供的自定义块实现了 Block Plugin API,它是更通用的 Plugin API 的一个子集。过去用于返回数组以进行块发现的 info 钩子现在由注解和 PSR-0 的使用组成,因此 Drupal 可以找到并理解您的块。返回您块内容的回调函数现在是我们可以在自定义块代码中按需覆盖的Drupal\block\BlockPluginInterface上的方法。
在 Drupal 8 中创建一个块需要根据 Plugin API 和基于注解的插件发现来创建一个插件。在整个书中,我们在创建字段小部件或 CKEditor 配置文件时都看到了这两个方面的应用。
创建块的流程可以可视化如下:
-
使用注解创建一个块插件。
-
实现
Drupal\Core\Block\BlockBase类。 -
根据用例实现
Drupal\Core\Block\BlockPluginInterface类方法。
对于名为author_tool的自定义块,PSR-4 兼容的结构是author_tool/src/Plugin/Block:

操作时间 – 创建一个辅助作者体验的块
让我们创建一个自定义块,该块将可供认证用户在访问食谱页面时创建新食谱。我们的模块将命名为author_tool:
-
创建一个类似于我们上次看到的截图的结构。
-
创建一个
author_tool.info.yml文件,并使用块系统创建您模块的依赖项:name: Author tool type: module description: A custom block to allow content editors to quickly add a new recipe. core: 8.x dependencies: - block -
使用您自己的实现
AuthorToolBlock扩展BlockBase类,并将以下代码放入其中:<?php /** * @file * Contains \Drupal\author_tool\Plugin\Block\AuthorToolBlock.php. */ namespace Drupal\author_tool\Plugin\Block; use Drupal\Core\Block\BlockBase; /** * Provides a custom block. * * Drupal\Core\Block\BlockBase gives us a very useful set of basic functionality * for this configurable block. We can just fill in a few of the blanks with * defaultConfiguration(), blockForm(), blockSubmit(), and build(). * * @Block( * id = "author_tool_block", * admin_label = @Translation("Author tool block") * ) */ classAuthorToolBlock extends BlockBase { // our code goes here. } -
在您的类中,像这样实现
BlockPluginInterface::build方法:/** * {@inheritdoc} */ public function build() { } -
构建方法返回一个可渲染的数组或内容,就像 Drupal 7 中的
hook_block_view所做的那样。渲染数组是 Drupal 8 中首选的方式。完整的实现如下所示:<?php /** * @file * Contains \Drupal\author_tool\Plugin\Block\AuthorToolBlock.php. */ namespace Drupal\author_tool\Plugin\Block; use Drupal\Core\Block\BlockBase; /** * Provides a custom block. * * Drupal\Core\Block\BlockBase gives us a very useful set of basic functionality * for this configurable block. We can just fill in a few of the blanks with * defaultConfiguration(), blockForm(), blockSubmit(), and build(). * * @Block( * id = "author_tool_block", * admin_label = @Translation("Author tool block") * ) */ classAuthorToolBlock extends BlockBase { /** * {@inheritdoc} */ public function build() { $link = array( '#type' => 'markup', '#title' => 'Author tools', '#markup' => $this->t('<a href=":url">Add another recipe</a>', array(':url' => 'add/recipe')), ); return $link; } } -
您的模块已准备就绪。转到扩展,定位它,并启用它。
-
现在,是时候将您的块放置在食谱页面上,以便认证用户可以看到。从主菜单中选择结构 | 块布局,滚动到侧边栏第一,然后点击放置块按钮:
![操作时间 – 创建一个辅助作者体验的块]()
-
定位您的块并点击放置块:
![操作时间 – 创建一个辅助作者体验的块]()
-
配置您的块以仅显示食谱内容类型:
![操作时间 – 创建一个辅助作者体验的块]()
-
配置您的块以仅对认证用户显示:
![操作时间 – 创建一个辅助作者体验的块]()
-
保存图片,就这样!现在访问一个食谱页面以验证它是否在那里:
![操作时间 – 创建一个辅助作者体验的块]()
发生了什么?
虽然这是一个通过代码创建块的非常简单的示例,但通过 Drupal 8 新出现的 Block API 创建块简单性是显而易见的。您创建了一个块来辅助作者 UI,并学习了扩展 Plugin API 以向系统添加自定义功能的基础知识。
要进一步探索,请查看api.drupal.org/api/drupal中的 BlockBase 类,以了解所有可以添加的方法。
操作时间 – 在您的模块中包含默认配置
一旦你在主题中放置了你的块,你可能希望在模块安装时包含该配置。通过在安装时包含默认配置,你可以提供一个合理的默认值来放置该块。此外,在你的部署机制中,你只需担心安装模块,其余的配置将在该步骤中应用:
-
通过访问结构 | 配置 | 配置同步 | 导出 | 单个项目来导出配置,并选择块和作者工具块。
-
确保复制粘贴所有内容,除了
uuid行(如下截图所示):![执行时间 – 在你的模块中包含默认配置]()
-
在你的模块中创建以下结构,并将内容放置在名为
block.block.authortoolblock.yml的文件中:![执行时间 – 在你的模块中包含默认配置]()
-
又是这样做!尝试再次卸载并安装模块。块应该会自动放置在完全相同的位置,并遵循相同的可见性规则。
摘要
在本章中,你学习了如何通过安装、配置和重用内置 WYSIWYG 编辑器的配置来增强作者 UI,现在它是 Drupal 8 核心的一部分。你探索了后端编辑实例以及内联编辑功能,并尝试添加自己的 CKEditor 插件。你还学习了如何使用新的块 API,通过自定义模块向系统中添加新块,扩展默认方法,并在安装模块时提供默认配置。
在下一章中,我们将看到如何处理媒体并将它们集成到我们的新 Drupal 8 站点中。
第七章。向我们的网站添加媒体
仅文本的网站无法吸引访客;网站需要一些活力和特色!为您的网站增添活力的一种方法是通过添加多媒体内容,例如图片、视频、音频等。但,我们不仅仅想在这里和那里添加几张图片;实际上,我们希望提供一个沉浸式且引人入胜的多媒体体验,它易于管理、配置和扩展。Drupal 8 的文件实体(drupal.org/project/file_entity)模块将使我们能够非常容易地管理文件。在本章中,我们将了解如何将文件实体模块集成到我们的 d8dev 网站中,并探索向用户展示图片的引人入胜的方法。这包括查看用于显示由文件实体模块管理的图片的灯箱类型 UI 元素的集成,以及学习我们如何通过 UI 和代码创建自定义图片样式。
本章将涵盖以下主题:
-
Drupal 8 的文件实体模块
-
将食谱图片字段添加到你的内容类型中
-
代码示例——Drupal 8 的图片样式
-
在灯箱弹出窗口中显示食谱图片
-
与 Drupal 问题队列协作
文件实体模块简介
根据模块页面www.drupal.org/project/file_entity:
文件实体提供了管理文件的接口。它还扩展了核心文件实体,允许文件可字段化、按类型分组、查看(使用显示模式)以及使用字段格式器格式化。文件实体与多个模块集成,将文件暴露给视图、实体 API、标记等。
在我们的案例中,我们需要这个模块来轻松编辑图片属性,如标题文本和 Alt 文本。因此,这些属性将在 colorbox 弹出窗口中使用,作为标题显示。
与模块的开发版本协作
有时候,你会遇到一个引入了一些重大新功能且相对稳定的模块,但还不适合在实时/生产网站上使用,因此只能作为开发版本提供。这是一个为 Drupal 社区提供宝贵贡献的绝佳机会。仅通过安装和使用模块的开发版本(当然是在你的本地开发环境中),你就为模块维护者提供了宝贵的测试。当然,如果你发现任何错误或希望请求任何额外功能,你应该在项目的问题队列中提交一个问题。此外,使用模块的开发版本为你提供了承担一些自定义 Drupal 开发的机会。然而,重要的是要记住,一个模块作为开发版本发布是有原因的,它可能还不够稳定,不能部署在面向公众的网站上。
本章中我们对文件实体模块的使用是使用模块开发版本的很好例子。有一点需要注意:Drush 会下载官方和开发版本的模块。但到目前为止,Drupal 中还没有文件实体模块的官方端口,所以我们将使用非官方的,它位于 GitHub 上(github.com/drupal-media/file_entity)。在下一步中,我们将从 GitHub 下载开发版本。
安装文件实体模块的开发版本的时间
在 Drupal 中,我们使用 Drush 下载和启用任何模块/主题,但文件实体模块在 Drupal 中还没有官方端口,所以我们可以使用位于 GitHub 上的非官方版本github.com/drupal-media/file_entity:
-
打开终端(Mac OS X)或命令提示符(Windows)应用程序,并转到您的 d8dev 站点的根目录。
-
进入
modules文件夹,从 GitHub 下载文件实体模块。我们使用git命令下载此模块:$ git clone https://github.com/drupal-media/file_entity。另一种方法是下载来自github.com/drupal-media/file_entity的.zip文件,并在模块文件夹中提取它:![安装文件实体模块的开发版本的时间]()
-
接下来,在扩展页面(
admin/modules)上启用文件实体模块。
刚才发生了什么?
我们启用了文件实体模块,并学习了如何使用 GitHub 下载和安装。
我们网站上的新食谱
在本章中,我们将创建一个新的食谱:泰国罗勒鸡肉。如果您想使用更多真实内容作为示例,并自由尝试这个食谱!

-
名称:泰国罗勒鸡肉
-
描述:我最喜欢的泰国菜肴之一的一个辛辣、美味的版本
-
产量:四份
-
准备时间:25 分钟
-
烹饪时间:20 分钟
-
配料:
-
一磅去骨鸡胸肉
-
两大汤匙橄榄油
-
四瓣大蒜,切碎
-
三大汤匙酱油
-
两大汤匙鱼露
-
两个大甜洋葱,切片
-
五瓣大蒜
-
一个黄色的甜椒
-
一个绿色的甜椒
-
四到八个泰国辣椒(根据您想要的辣度而定)
-
三分之一杯深棕色糖溶解在一杯热水中
-
一杯新鲜罗勒叶
-
两杯茉莉香米
-
-
说明:
-
按照说明准备茉莉香米。
-
在一个大煎锅中用中热加热橄榄油两分钟。
-
将鸡肉加入锅中,然后倒入酱油。
-
煮鸡肉直到没有可见的粉红色——大约 8 到 10 分钟。
-
将热量降低到中低。
-
加入大蒜和鱼露,煮沸 3 分钟。
-
接下来,加入泰国辣椒、洋葱和甜椒,搅拌均匀。
-
煮沸 2 分钟。
-
添加红糖和水混合物。搅拌混合,然后盖上盖子。
-
炖煮 5 分钟。
-
揭盖,加入罗勒,搅拌以混合。
-
搭配米饭食用。
-
操作时间 – 向我们的食谱内容类型添加食谱图片字段
我们将使用管理字段管理页面将媒体字段添加到我们的 d8dev 食谱内容类型中:
-
在您最喜欢的浏览器中打开 d8dev 网站,点击管理工具栏中的结构链接,然后点击内容类型链接。
-
接下来,在内容类型管理页面上,点击您食谱内容类型的管理字段链接:
![操作时间 – 向我们的食谱内容类型添加食谱图片字段]()
-
现在,在管理字段管理页面上,点击添加字段链接。在下一屏幕上,从添加新字段下拉菜单中选择图片,并将标签设置为食谱图片。点击保存字段设置按钮。
-
接下来,在字段设置页面,将允许的值数设置为无限。点击保存字段设置按钮。在下一屏幕上,保留所有设置不变,并点击保存设置按钮。
-
接下来,在管理表单显示页面,为食谱图片字段选择小部件可编辑文件并点击保存按钮。
-
现在,在管理显示页面,对于食谱图片字段,将标签选择为隐藏。点击设置图标。然后选择中等(220*220)作为图片样式,并点击更新按钮。在底部,点击保存按钮:
![操作时间 – 向我们的食谱内容类型添加食谱图片字段]()
-
让我们向一个食谱添加一些食谱图片。在菜单栏中点击内容链接,然后点击添加内容和食谱。在下一屏幕上,填写标题为
泰国罗勒鸡肉,并分别填写其他字段,如前述食谱详情中所述。 -
现在,向下滚动到您刚刚添加的新食谱图片字段。点击添加新文件按钮或拖放您想要上传的图片。然后点击保存并发布按钮:
![操作时间 – 向我们的食谱内容类型添加食谱图片字段]()
-
重新加载您的泰国罗勒鸡肉食谱页面,您应该会看到以下类似的内容:
![操作时间 – 向我们的食谱内容类型添加食谱图片字段]()
-
所有图片都堆叠在一起。因此,我们将在
/modules/d8dev/styles/d8dev.css文件中field--name-field-recipe-images和field--type-recipe-images样式下方添加以下 CSS,以使食谱图片更像是网格布局:.node .field--type-recipe-images { float: none !important; } .field--name-field-recipe-images .field__item { display: inline-flex; padding: 6px; } -
现在我们将加载这个
d8dev.css文件以影响这个网格样式。在 Drupal 8 中,加载 CSS 文件有一个过程:-
将 CSS 保存到文件中。
-
定义一个库,其中可以包含 CSS 文件。
-
在钩子中将库附加到渲染数组。
-
-
因此,我们已经在
styles文件夹下保存了一个名为d8dev.css的 CSS 文件;现在我们将定义一个库。要定义一个或多个(资产)库,请将一个*.libraries.yml文件添加到您的theme文件夹中。我们的模块名为d8dev,然后文件名应该是d8dev.libraries.yml。文件中的每个库都是一个条目,详细说明了 CSS,如下所示:d8dev: version: 1.x css: theme: styles/d8dev.css: {} -
现在,我们定义
hook_page_attachments()函数来加载 CSS 文件。在d8dev.module文件中添加以下代码。当您想有条件地向页面添加附件时使用此钩子:/** * Implements hook_page_attachments(). */ function d8dev_page_attachments(array &$attachments) { $attachments['#attached']['library'][] = 'd8dev/d8dev'; } -
现在,我们需要通过访问配置,点击性能链接,然后点击清除所有缓存按钮来清除我们的 d8dev 网站的缓存。重新加载您的泰国罗勒鸡肉食谱页面,您应该会看到以下类似的内容:
![通过食谱内容类型添加食谱图片字段]()
发生了什么?
我们为我们的食谱内容类型添加并配置了一个基于媒体的字段。我们通过自定义 CSS 代码更新了 d8dev 模块,以便以更网格的形式布局食谱图片。同时,我们还研究了如何通过模块附加 CSS 文件。
创建自定义图片样式
在我们配置 colorbox 功能之前,我们将创建一个自定义图片样式,以便在添加到 colorbox 内容预览设置时使用。Drupal 8 的图片样式是核心图片模块的一部分。核心图片模块提供了三个默认图片样式——缩略图、中图和大图,如以下图片样式配置页面所示:

现在,我们将添加第五个自定义图片样式,一个介于 100 x 75 缩略图样式和 220 x 165 中图样式之间的图片样式。我们将通过图片样式管理页面演示创建图片样式的过程,并演示通过编程创建图片样式的过程。
通过图片样式管理页面添加自定义图片样式
首先,我们将使用图片样式管理页面(admin/config/media/image-styles)来创建一个自定义图片样式:
-
在您最喜欢的浏览器中打开 d8dev 网站,点击管理工具栏中的配置链接,然后点击媒体部分下的图片样式链接。
-
一旦图片样式管理页面加载完成,点击添加样式链接。
-
接下来,将自定义图片样式的图片样式名称输入为
small,然后点击创建新样式按钮:![通过图片样式管理页面添加自定义图片样式]()
现在,我们将通过从效果选项中选择缩放并点击添加按钮来为我们的自定义图片样式添加唯一的效果。
-
在添加缩放效果页面上,输入宽度为
160,高度为120。不要勾选允许放大复选框,然后点击添加效果按钮:![行动时间 – 通过图像样式管理页面添加自定义图像样式]()
-
最后,只需在编辑小样式管理页面上的更新样式按钮上点击,我们就完成了。我们现在有一个新的自定义小图像样式,我们可以用它来调整我们网站的图像大小:
![行动时间 – 通过图像样式管理页面添加自定义图像样式]()
发生了什么?
我们学习了如何通过管理 UI 轻松添加自定义图像样式。现在,我们将看到如何通过编写一些代码来添加自定义图像样式。基于代码的自定义图像样式的优点是,它将允许我们利用源代码仓库,例如 Git,来管理和在不同环境之间部署我们的自定义图像样式。例如,它将允许我们使用 Git 将图像样式从开发环境提升到实时生产网站。否则,我们刚才所做的手动配置将不得不为每个环境重复进行。
行动时间 – 创建一个程序化的自定义图像样式
现在,我们将看到如何通过代码添加自定义图像样式:
-
我们需要做的第一件事是删除我们刚刚创建的小图像样式。因此,打开您最喜欢的浏览器中的 d8dev 网站,点击管理工具栏中的配置链接,然后在媒体部分下点击图像样式链接。
-
一旦图像样式管理页面加载完成,点击我们刚刚添加的小图像样式的删除链接。
-
接下来,在在删除小样式之前可选选择一个样式页面上,将替换样式选择列表的默认值保留为无替换,仅删除,然后点击删除按钮:
![行动时间 – 创建程序化的自定义图像样式]()
-
在 Drupal 8 中,图像样式已从数组转换为扩展 ConfigEntity 的对象。所有由模块提供的图像样式都需要在每个模块的
config/install文件夹中定义为 YAML 配置文件。 -
假设我们的模块位于
modules/d8dev。创建一个名为modules/d8dev/config/install/image.style.small.yml的文件,内容如下:uuid: b97a0bd7-4833-4d4a-ae05-5d4da0503041 langcode: en status: true dependencies: { } name: small label: small effects: c76016aa-3c8b-495a-9e31-4923f1e4be54: uuid: c76016aa-3c8b-495a-9e31-4923f1e4be54 id: image_scale weight: 1 data: width: 160 height: 120 upscale: false注意
我们需要使用 UUID 生成器为图像样式效果分配唯一的 ID。不要从其他代码片段或其他图像样式中复制/粘贴 UUID!
-
我们自定义样式的名称是"small"。在这里,名称和标签是相同的。对于我们要添加到图像样式的每个效果,我们将在键的后面指定效果本身,键后的值作为效果的设置。在我们这里使用的image_scale效果的情况下,我们传递了宽度、高度和upscale设置。最后,weight键的值允许我们指定效果应该处理的顺序,尽管当只有一个效果时它不太有用,但当有多个效果时,它就变得很重要了。
-
现在,我们需要通过访问扩展页面来卸载并安装我们的 d8dev 模块。在下一屏幕上点击卸载选项卡,勾选d8dev复选框,然后点击卸载按钮。现在,点击列表选项卡,勾选d8dev,然后点击安装按钮。然后,返回到图像样式管理页面,您将看到我们通过编程创建的小图像样式。
发生了什么?
我们创建了一个带有一些自定义代码的自定义图像样式。然后,我们配置了我们的食谱内容类型,使其为添加到食谱图片字段的图片使用我们的自定义图像样式。
集成 Colorbox 和文件实体模块
文件实体模块提供了管理文件的用户界面。对于图片,我们将能够轻松编辑标题文本、替代文本和文件名。然而,图片占据了相当大的空间。让我们创建一个弹出式相册并显示图片。当有人点击图片时,一个灯箱会弹出,并允许用户浏览所有相关图片的较大版本。
安装 Colorbox 模块的行动时间
在我们能够在 Colorbox 中显示食谱图片之前,我们需要下载并启用该模块:
-
打开 Mac OS X 终端或 Windows 命令提示符,切换到
d8dev目录。 -
接下来,使用 Drush 下载并启用 Colorbox 模块的当前开发版本(
drupal.org/project/colorbox):$ drush dl colorbox-8.x-1.x-dev Project colorbox (8.x-1.x-dev) downloaded to /var/www/html/d8dev/modules/colorbox. [success] $ drush en colorbox The following extensions will be enabled: colorbox Do you really want to continue? (y/n): y colorbox was enabled successfully. [ok] -
Colorbox 模块依赖于位于
github.com/jackmoore/colorbox的 Colorbox jQuery 插件。Colorbox 模块包含一个 Drush 任务,该任务将在/libraries目录下下载所需的 jQuery 插件:$ drush colorbox-plugin Colorbox plugin has been installed in libraries [success] -
接下来,我们将查看 Colorbox 显示格式化器。点击管理工具栏中的结构链接,然后点击内容类型链接,最后点击操作下拉菜单下的管理显示链接,以您的食谱内容类型为例:
![安装 Colorbox 模块的行动时间 – 安装 Colorbox 模块]()
-
接下来,点击格式选择列表中的食谱图片字段,您将看到一个 Colorbox 选项,选择Colorbox后,您将看到设置发生变化。然后,点击设置图标:
![安装 Colorbox 模块的行动时间 – 安装 Colorbox 模块]()
-
现在,您将看到 Colorbox 的设置。在下拉菜单中选择内容图像样式为小尺寸,并将第一张图片的内容图像样式也设置为小尺寸,并使用其他选项的默认设置。点击底部的更新按钮,然后点击底部的保存按钮:
![行动时间 – 安装 Colorbox 模块]()
-
重新加载我们的泰式罗勒鸡肉食谱页面,你应该会看到以下类似的内容(带有新的图像样式,小尺寸):
![行动时间 – 安装 Colorbox 模块]()
-
现在,点击任何图片,然后您将看到图片在 colorbox 弹出窗口中加载:
![行动时间 – 安装 Colorbox 模块]()
-
我们对 colorbox 的图片有了更多的了解,但 colorbox 也支持视频。为我们的网站添加一些活力的一种方式是添加视频。因此,有多个模块可用于与 colorbox 一起工作以处理视频。Video Embed Field 模块创建了一个简单的字段类型,允许您通过输入视频的 URL 将 YouTube 和 Vimeo 中的视频嵌入,并简单地显示它们的缩略图预览。因此,您可以尝试此模块为您的网站添加一些亮点!
发生了什么?
我们已安装 Colorbox 模块,并已将其启用以用于我们的自定义食谱内容类型的“食谱图片字段”。现在,我们可以轻松地使用 Colorbox 弹出功能将图片添加到我们的 d8dev 内容中。
与 Drupal 问题队列协作
Drupal 拥有自己的一套问题队列,用于与全球的开发者团队协作。如果你需要特定项目、核心、模块或主题的帮助,你应该去问题队列,那里的维护者、用户和主题的追随者会进行沟通。
问题页面提供了一个筛选选项,您可以根据项目、分配、提交者、追随者、状态、优先级、类别等搜索特定的问题。
我们可以在www.drupal.org/project/issues/colorbox找到问题。在这里,将 colorbox 替换为特定的模块名称。更多信息请参阅www.drupal.org/issue-queue。
在我们的案例中,我们有一个关于 colorbox 模块的问题。对于自动和内容标题属性,标题是正常工作的,但对于Alt 文本和标题文本属性则不工作。要检查这个问题,请转到结构 | 内容类型并点击管理显示。在下一屏幕上,点击“食谱图片字段”的设置图标。现在选择标题文本或Alt 文本作为标题选项,并点击更新按钮。最后,点击保存按钮。重新加载泰式罗勒鸡肉食谱页面,并点击任何图片。然后它将在弹出窗口中打开,但我们看不到这个图片的标题。
注意
确保您已更新泰式罗勒鸡肉食谱的“食谱图片字段”的标题文本和 Alt 文本属性。
行动时间 – 为 Colorbox 模块创建一个问题
现在,在我们尝试解决 Colorbox 模块的功能问题之前,让我们先创建一个问题:
-
在
www.drupal.org/project/issues/colorbox上,点击 创建新问题 链接:![创建 Colorbox 模块问题的时机]()
-
在下一屏,我们将看到一个表单。我们将填写所有必填字段:标题,类别选择为“错误报告”,版本选择为
8.x-1.x-dev,组件选择为“代码”,以及问题摘要字段。一旦我提交了表单,就在www.drupal.org/node/2645160创建了一个问题。你应该在 Drupal (www.drupal.org/) 上看到一个类似的问题:![创建 Colorbox 模块问题的时机]()
-
接下来,colorbox 模块的维护者将调查这个问题并相应地回复。实际上,
@frjo回复说:“我从未使用过那个模块,但如果有人发送补丁,我会看看。”他是这个模块的贡献者,所以我们将等待一段时间,看看是否有人能通过提供补丁或发表有用的评论来解决这个问题。如果有人提供补丁,那么我们必须将其应用到 colorbox 模块中。这些信息可以在 Drupal 上找到www.drupal.org/patch/apply。
发生了什么?
我们理解并创建了一个 Colorbox 模块问题队列列表中的问题。我们还查看了所需字段以及如何填写它们以在 Drupal 模块队列列表表单中创建问题。
摘要
在本章中,我们探讨了一种使用我们的 d8dev 网站与多媒体结合的方法,通过一些自定义代码创建图像样式,并学习了一些与 Drupal 开发者社区互动的新方法。我们还与 Colorbox 模块合作,使用 Colorbox 弹出功能将图像添加到我们的 d8dev 内容中。最后,我们研究了自定义模块以处理自定义 CSS 文件。
在下一章中,我们将对 Colorbox 模块进行一些改进,并添加一些功能到我们的 d8dev 网站上,这将使访问者能够为我们提供反馈并与我们网站的内容互动。
第八章.味道如何? – 获取反馈
本章将逐步介绍在联系模块中添加 HTML 表单元素占位符的代码。我们还将回顾上一章中安装的 Colorbox 模块。我们将对代码进行一些增强(一个高级的实战示例),并讲解使用补丁的过程。我们还将使用评论类型和视图配置实现带有食谱内容类型的评论和点赞系统。
Drupal 联系表单简介
Drupal 核心包含一个非常简单的联系表单,默认启用。核心联系表单为我们网站提供了一个良好的起点,可以引入一些交互式功能。
操作时间 – 启用和配置核心联系表单
我们将配置核心联系表单,以便我们的网站匿名访客能够提供关于网站的反馈:
-
在 Drupal 8 中,联系模块默认启用。我们只需设置匿名和认证角色的权限即可使用反馈表单。
-
现在,在您最喜欢的浏览器中打开 d8dev 网站,并点击管理工具栏中的扩展链接。滚动到核心组下的名为联系的模块;您会注意到该模块已启用。然后,点击权限链接:
![操作时间 – 启用和配置核心联系表单]()
-
在权限页面上,您会注意到使用全局联系表单权限默认为管理员、匿名和认证角色启用。因此,匿名用户可以使用联系表单。
-
接下来,注销并导航到
http://localhost/contact/feedback。您将看到一个简单的联系表单,如下面的截图所示:![操作时间 – 启用和配置核心联系表单]()
刚才发生了什么?
我们启用了一个简单的联系表单,以便从我们的 d8dev 网站访客那里获取反馈。
向我们的联系表单添加占位文本
核心联系模块为我们网站提供了一个不错的默认联系表单。但如果我们想对其进行一些定制呢?比如说,我们想添加一些占位文本来解释用户为什么应该填写表单。正如我们在前面的章节中看到的,通常有多种方式使用 Drupal 完成此类定制。
使用配置向联系表单添加占位文本
我们将配置核心联系表单以启用占位符,向用户解释他们为什么应该填写表单。
操作时间 – 向我们的网站联系表单添加占位文本
核心联系模块为我们网站提供了一个不错的默认联系表单。核心还提供了可配置的字段占位符。因此,我们为表单字段添加占位符,以显示占位符并解释用户为什么应该填写表单:
-
在您最喜欢的浏览器中打开 d8dev 网站,并点击 管理 工具栏中的 结构 链接。在下一页,您可以看到列出的 网站反馈。
-
现在,点击 操作 列中的 管理表单显示,针对 网站反馈 表单:
![操作时间 – 向我们的网站联系表单添加占位文本]()
-
在下一屏,您可以看到主题和消息字段是可以配置的:
![操作时间 – 向我们的网站联系表单添加占位文本]()
-
现在,点击 主题 字段的设置图标,填写 占位符 为
请在此处输入您的主题!,然后点击 更新 按钮:![操作时间 – 向我们的网站联系表单添加占位文本]()
-
接下来,点击 消息 字段的设置图标,填写 占位符 为
请在此处输入您的消息!,将 行数 值从12更改为8,然后点击 更新 按钮:![操作时间 – 向我们的网站联系表单添加占位文本]()
-
接下来,以匿名用户身份注销并导航到
http://localhost/contact/feedback。您将看到一个简单的联系表单,如下面的截图所示:![操作时间 – 向我们的网站联系表单添加占位文本]()
注意
您还可以尝试 Contact Storage 模块,该模块为联系消息提供存储;这些是 Drupal 8 中的完整实体。此模块提供消息存储、编辑消息和删除消息等功能。有关更多详细信息,请参阅 www.drupal.org/project/contact_storage。
发生了什么?
我们在联系表单的 管理表单显示 页面上配置了 主题 和 消息 字段的 占位符 文本。
使用自定义代码向姓名和电子邮件字段添加占位文本
Drupal 为主题和消息字段提供了可配置的占位符,但不是为姓名和电子邮件字段,因此我们使用自定义代码在我们的 d8dev 模块中为它们添加占位符。
操作时间 – 向姓名和电子邮件字段添加占位文本
我们将利用核心 hook_form_alter 钩子来修改核心联系表单,以显示姓名和电子邮件字段的占位符。我们将添加以下代码到 d8dev.module 文件中:
-
在 PhpStorm 中,打开位于
/modules下的自定义模块中的d8dev.module文件。 -
接下来,切换到已加载核心联系表单的浏览器,并找到表单元素的
form_id。在页面上右键单击并选择 查看源代码。在下一页,您可以看到 HTML 代码。现在搜索form_id,您将找到所需的表单 ID 为contact_message_feedback_form。 -
另一种方法是使用
drupal_set_message()核心函数来查找form_id。为此,我们需要在d8dev.module文件顶部添加以下代码:use Drupal\Core\Form\FormStateInterface; /** * Implements hook_form_alter(). */ function d8dev_form_alter(&$form, FormStateInterface $form_state, $form_id) { drupal_set_message($form_id); } -
现在,以登录用户重新加载
http://localhost/contact/feedback页面,你会看到一个显示form_id的消息,如下所示:![行动时间 - 向名称和电子邮件字段添加占位文本]()
-
接下来,我们需要向
name和mail表单元素添加一个占位符 HTML 属性,如下所示:$form['name']['#attributes'] = array('placeholder' => array('Please enter your first and last name.')); $form['mail']['#attributes'] = array('placeholder' => array('Please enter your e-mail address.')); -
只为联系人表单添加前面的占位符代码。现在我们的
d8dev_form_alter()函数看起来是这样的:/** * Implements hook_form_alter(). */ function d8dev_form_alter(&$form, FormStateInterface $form_state, $form_id) { if ($form_id == 'contact_message_feedback_form') { $form['name']['#attributes'] = array('placeholder' => array('Please enter your first and last name.')); $form['mail']['#attributes'] = array('placeholder' => array('Please enter your e-mail address.')); } } -
接下来,通过访问管理 | 配置 | 开发 | 性能来清除缓存。现在以匿名用户重新加载联系人页面。然后你会看到以下表单:
![行动时间 - 向名称和电子邮件字段添加占位文本]()
发生了什么?
我们使用了hook_form_alter钩子向核心联系人表单的名称和电子邮件字段添加占位文本。
是时候再做一个菜谱了!
就算你开始饿了,我们也会添加一个新的菜谱。现在就加进来,这样我们就可以在完成这个具有挑战性的章节的剩余部分时享受一些美味的食物!这一章的菜谱是印度风蒜香鸡。享受吧!

-
名称:印度风蒜香鸡
-
描述:印度风蒜香鸡菜谱,学习如何在 20 分钟内制作蒜香鸡,包括餐厅风格和家庭风格的开胃菜、咖喱、比拉尼等。这道蒜香鸡可以在 20 分钟内准备完成(不包括腌制时间)。它是一个完美的开胃菜或配菜,可以让你的餐点更加异国风味。
-
recipeYield:四份
-
prepTime:20 分钟
-
cookTime:20 分钟
-
配料:
-
300 克鸡肉条或去骨块
-
4 汤匙酸奶/达希/凝乳(不要使用酸酸奶)
-
盐——非常少
-
适量胡椒
-
两个汤匙油
-
一枝咖喱叶
-
¼茶匙孜然
-
一个切碎的洋葱
-
五个红辣椒或¾茶匙红辣椒粉
-
五瓣捣碎的大蒜
-
¾汤匙醋
-
¼茶匙糖(可选)
-
根据口味加盐
-
水——15 至 30 毫升(越少越好)以制作糊状物
-
几片香菜叶
-
洋葱楔或圈
-
柠檬
-
-
说明:
-
将酸奶、盐和胡椒与半杯至¾杯水混合。充分搅拌后放置一旁。
-
洗净鸡肉,将其浸泡在这准备好的黄油牛奶中,放入冰箱冷藏 2 小时至过夜。
-
建议至少浸泡 6 小时以获得柔软的鸡肉。
-
将用于辣椒蒜香酱的配料搅拌并制成糊状。
-
倒掉酸奶油,加入准备好的酱汁。
-
搅拌均匀,静置 5 分钟。如果你想稍后准备,可以将其放回冰箱。
-
用油加热平底锅。加入孜然和咖喱叶。炒至孜然开始噼啪作响。
-
加入洋葱,炒至金黄色。
-
将鸡肉加入,用大火煎 2 到 3 分钟。当你看到鸡肉变白时,盖上盖子,降低火力,煮至鸡肉变嫩。如果你使用的是条状鸡肉,这几乎只需要 5 分钟。
-
取下盖子,煎至水分蒸发,酱汁开始粘附在鸡肉上。煎得越久,酱汁越香,越美味,但鸡肉往往会失去其嫩滑。
-
所以,在你喜欢的时候,从炎热中解脱出来。小心不要烧焦;大蒜烧得更快。
-
Colorbox 文件增强
在上一章中,我们将 Colorbox 模块添加到我们的 d8dev 网站中,用于显示叠加中的图片。目前,Colorbox 模块将菜谱的标题作为所有在 Colorbox 叠加中显示的菜谱图片的标题,如图下所示。但是,它与标题和 alt 值等图片属性不兼容。

因此,我们在 第七章,向我们的网站添加媒体中,在 Colorbox 问题队列中提出了一个问题 (www.drupal.org/node/2645160)。Colorbox 维护者 @frjo 如下回复:
"文件实体模块是一个贡献模块,所以你真正需要的是 Colorbox 支持它。更改标题以便清晰。我从未使用过该模块,但如果有人提交补丁,我会查看。"
在我们开始创建补丁之前,我们需要清楚地了解为什么在 title 或 alt 属性的情况下没有显示。我查看了 Colorbox 模块,并对所有渲染图片字段属性的预处理函数进行了调试。我发现 template_preprocess_colorbox_formatter() 函数是正确加载图片字段所有属性的函数。但是,当文件实体模块启用时,它不会加载,因为文件已经变成了实体,Colorbox 不知道如何从图片文件中渲染。因此,我们将为这个问题创建一个补丁,并在上一章中创建的 Colorbox 问题中提交它。
行动时间 - 增强 Colorbox 模块以包含图片标题和 alt 标题
那么,我们应该从哪里开始实施这个建议的方法并完成相关的功能请求呢?我们将从调试 template_preprocess_colorbox_formatter() 函数中可用的处理过的 $variable 开始,以便我们可以修改或添加到图片字段中所需属性:
-
在 PhpStorm 中,打开
/modules文件夹中的贡献模块的colorbox.inc文件。 -
在我们开始修改或添加代码之前,让我们先了解
colorbox.inc文件中的template_preprocess_colorbox_formatter()函数中的代码。首先,我们需要知道标题设置在哪里保存。有一个$settings变量已经被声明;在$settings变量旁边添加以下代码(第 35 行):var_dump($settings); die; -
从性能页面清除缓存并重新加载菜谱内容;你将看到以下内容:
![执行时间 – 使用图像标题和 alt 标题增强 Colorbox 模块]()
-
我们注意到自动是此内容类型的 colorbox 标题。为了确认这一点,转到结构 | 内容类型,然后点击菜谱内容类型的管理显示。接下来,点击菜谱图像字段的设置图标,你会注意到标题设置为自动:
![执行时间 – 使用图像标题和 alt 标题增强 Colorbox 模块]()
-
你的设置可能不同,所以最后一步中你可以相对地看到。
-
在接下来的几行中,我们可以看到一个
switch语句的声明,如下设置$caption变量,它将作为 Colorbox 覆盖层的标题显示:switch ($settings['colorbox_caption']) { case 'auto': // If the title is empty use alt or the entity title in that order. if (!empty($item->title)) { $caption = $item->title; } elseif (!empty($item->alt)) { $caption = $item->alt; } elseif (!empty($entity_title)) { $caption = $entity_title; } else { $caption = ''; } break; case 'title': $caption = $item->title; break; case 'alt': $caption = $item->alt; break; case 'entity_title': $caption = $entity_title; break; case 'custom': $token_service = \Drupal::token(); $caption = $token_service->replace($settings['colorbox_caption_custom'], array($entity_type => $entity, 'file' => $item), array('clear' => TRUE)); break; default: $caption = ''; } -
因此,我们可以理解
$caption变量需要修改为设置 alt 或标题文本作为标题。为了确认这一点,当$caption在 colorbox 覆盖层中加载时;我们只需向其中设置一些占位符文本,我们就会在菜谱内容中看到效果。在添加占位符文本之前,删除你在上一步添加的var_dump($settings); die;行。 -
接下来,在
switch语句旁边添加以下行:$caption = 'This is test caption!'; -
从性能页面清除缓存。重新加载菜谱内容并点击任何图像以检查标题;你将看到以下截图:
![执行时间 – 使用图像标题和 alt 标题增强 Colorbox 模块]()
-
在进行下一步之前,删除你在上一步添加的占位符标题代码:
$caption = 'This is test caption!';。 -
接下来,预处理
attributes变量的标题,该变量需要从图像文件实体中加载。在template_preprocess_colorbox_formatter()函数末尾的$variables['attributes']['title'] = $caption;行之前添加以下代码:// if File Entity module is enabled, load attribute values from file entity. if(\Drupal::moduleHandler()->moduleExists('file_entity')) { // file id of the save file. $fid = $item->target_id; // load file object $file_obj = file_load($fid); $file_array = $file_obj->toArray(); // populate the image title if (Unicode::strlen($file_array['field_image_title_text'][0]['value']) != 0 && empty($item->title) && $settings['colorbox_caption'] == 'title') { $caption = $file_array['field_image_title_text'][0]['value']; } // populate the image alt text. if (!empty($file_array['field_image_alt_text'][0]['value']) && empty($item->alt) && $settings['colorbox_caption'] == 'alt') { $caption = $file_array['field_image_alt_text'][0]['value']; } } -
首先,我们检查了
file_entity模块是否启用,因为这个模块不依赖于 colorbox 模块。file_entity模块的目的是提供管理文件以及将文件作为实体字段化的接口,因此图像文件的属性可以渲染为对象。接下来,我们添加了加载文件对象的代码,并通过验证来自$file对象的标题和 alt 文本属性来设置$caption变量。 -
为了在菜谱内容中看到这个效果,在确保你的图像已更新了 alt 和标题文本之前,从性能页面清除缓存并重新加载菜谱内容。
-
接下来,转到“食谱内容类型管理显示页面”,点击“食谱图像”字段的设置图标,并将标题更新为标题文本。在“管理显示”页面的底部点击保存按钮,并重新加载食谱内容页面以查看更改。点击任何图像。哇!现在它正在显示标题文本作为字幕(见下一张截图)。或者,在设置中将字幕更新为Alt 文本并检查食谱内容;它将工作:
![行动时间 – 使用图像标题和 alt 字幕增强 Colorbox 模块]()
发生了什么?
我们使用自定义代码更新了 colorbox 模块,使其能够与图像的标题和 alt 文本属性一起工作。
将我们的代码贡献给 Drupal
现在我们有了将在 colorbox 模块中通用的代码。但是,我们如何将此代码传递给其他 Drupal 用户,并且最好是将它作为 colorbox 模块的一部分进行维护?为了共享此代码,我们将创建一个补丁。
行动时间 – 创建补丁并将其上传到 Drupal 问题队列
在我们能够使用 Drupal 8/Git 方式创建补丁之前,我们需要使用 Git 将 colorbox 模块项目检出到一个新目录:
-
前往
www.drupal.org/project/colorbox/git-instructions。在顶部,你可以看到 从哪个版本开始工作 选择框。选择 8.x-1.x 并点击 显示 按钮:![行动时间 – 创建补丁并将其上传到 Drupal 问题队列]()
-
现在打开终端并转到你希望克隆 colorbox 模块的任何位置(使用
cd <path/to/go>命令),然后运行以下命令来克隆 colorbox Git 仓库:$ git clone --branch 8.x-1.x https://git.drupal.org/project/colorbox.git Cloning into 'colorbox'... remote: Counting objects: 2082, done. remote: Compressing objects: 100% (1545/1545), done. remote: Total 2082 (delta 1253), reused 661 (delta 410) Receiving objects: 100% (2082/2082), 1.84 MiB | 27.00 KiB/s, done. Resolving deltas: 100% (1253/1253), done. Checking connectivity... done. -
接下来,我们需要将我们刚刚为 d8dev 网站所做的更改应用到
colorbox.inc文件中,以及我们刚刚使用 Git 克隆的代码中。 -
现在我们已经更新了代码,我们准备创建我们的补丁。按照 Drupal 指南
drupal.org/node/707484在 colorbox 根目录下运行此命令:$ git diff > 2645160-6.patch -
接下来,我们将在
drupal.org/node/2645160的问题上进行评论,并上传我们的补丁以及我们的评论,确保问题状态设置为需要审查:![行动时间 – 创建补丁并将其上传到 Drupal 问题队列]()
如果我们想应用刚刚显示的补丁,我们使用以下
git命令:git apply -v [patchname.patch]如果想撤销最后应用的补丁,那么我们使用以下 git 命令:
git checkout [filename] git reset --hard
发生了什么?
我们不仅更新了代码,使其能够在 Colorbox 模块和任何数量的 Drupal 网站上工作,我们还帮助了 Drupal 社区。我们学习了如何创建补丁。我们将回到 drupal.org/node/2645160 的 Colorbox 问题,看看 Drupal 社区如何对我们的补丁做出回应。
带有评论的食谱审查
现在我们已经增强了 Colorbox 模块的可配置标题,我们将把注意力转回到增强网站的用户交互。让访客能够评审和点赞/不喜欢内容是吸引他们与网站互动的一个很好的方法,在这种情况下,是菜谱。
操作时间 – 配置评论为菜谱评审
我们现在将设置评论,以便我们的 d8dev 网站的访客能够评审菜谱:
-
我们将配置我们的菜谱内容类型以使用评论。在您喜欢的浏览器中打开 d8dev 网站,点击管理工具栏中的结构链接,然后点击评论类型链接,最后点击为您的菜谱内容类型添加的添加内容类型链接。
-
在下一屏,将
菜谱评审作为标签字段的值,将内容作为目标实体类型字段的值。点击保存按钮:![操作时间 – 配置评论为菜谱评审]()
-
我们添加了一个新的评论类型,并且它可以在任何内容类型中使用。点击管理工具栏中的结构链接,然后点击内容类型链接,最后点击您的菜谱内容类型的管理字段链接。
-
接下来,点击添加字段链接。在下一屏,选择评论作为添加新字段,输入
菜谱评审作为标签,然后点击保存并继续按钮:![操作时间 – 配置评论为菜谱评审]()
-
在下一屏,选择菜谱评审作为评论类型,然后点击保存字段设置按钮。接下来,保留所有字段为默认设置,并点击保存设置按钮:
![操作时间 – 配置评论为菜谱评审]()
-
我们已经为菜谱内容类型启用了评论。现在我们以匿名用户重新加载菜谱内容,以查看默认的评论表单。但由于权限问题,我们看不到表单。要设置权限,请转到人员 | 权限,为匿名用户检查发表评论权限,滚动到页面底部,然后点击保存权限按钮。再次以匿名用户重新加载菜谱内容,您将看到如下截图:
![操作时间 – 配置评论为菜谱评审]()
-
我们不需要默认提供的名称、主题和评论字段。我们将删除它们并添加电子邮件和味道如何?字段。现在点击管理工具栏中的结构,然后点击评论类型链接。
-
现在,点击菜谱评审评论类型的操作中的管理字段。您可以看到只有评论字段。点击操作中的评论字段的删除链接。在下一屏,点击删除按钮来删除此字段:
![操作时间 – 配置评论为菜谱评审]()
-
接下来,点击添加字段,选择添加新字段为电子邮件,输入标签为
E-mail,然后点击保存并继续按钮:![操作时间 – 配置评论为食谱评论]()
-
在下一屏幕上,保留默认值限制、1,然后点击保存字段设置按钮。接下来,勾选必填字段并点击保存设置按钮。
-
现在,点击添加字段链接。选择文本(纯文本,长文本)作为添加新字段,并将标签设置为口感如何?。点击保存并继续按钮。
-
在下一屏幕上,保留默认设置1,然后点击保存字段设置按钮。再次在下一屏幕上,保留所有字段为默认值,然后点击保存设置按钮。
-
接下来,以匿名用户重新加载食谱内容。我们可以看到以下截图所示的形式:
![操作时间 – 配置评论为食谱评论]()
-
但是我们仍然可以看到您的姓名和主题字段,这些字段不是必需的。因此,我们将从管理表单显示页面禁用它们。现在,点击管理表单显示以访问食谱评论类型。在下一屏幕上,将作者和主题字段的值设置为隐藏,然后点击保存按钮。请参阅以下截图:
![操作时间 – 配置评论为食谱评论]()
-
现在,我们重新加载食谱内容,我们可以看到以下评论表单:
![操作时间 – 配置评论为食谱评论]()
-
由于我们需要显示食谱内容的点赞数,我们需要限制匿名和认证用户角色的评论查看权限。要设置权限,请转到人员 | 权限。取消勾选匿名和认证用户的查看评论权限,滚动到页面底部,然后点击保存权限按钮。
发生了什么?
我们创建了一个新的评论类型,食谱评论,并在食谱内容类型中启用了它。此外,我们在食谱评论评论类型中配置了新的字段。
操作时间 – 使用评论和视图增强点赞系统
现在,我们将设置一个点赞/不喜欢按钮,以便我们的 d8dev 网站的访客能够点赞/不喜欢食谱。此外,我们将展示有多少人喜欢了每个食谱内容:
-
首先,我们将在食谱评论类型中添加你喜欢它吗?按钮,以便我们的 d8dev 网站的访客能够点赞/不喜欢食谱。在你的浏览器中打开 d8dev 网站,点击管理工具栏中的结构链接。然后点击评论类型链接,最后在操作下拉菜单中点击为你的食谱评论管理字段链接。
-
点击添加字段链接以添加新字段。在下一屏幕上,选择列表(整数)作为添加新字段,并将你喜欢它吗?作为标签。点击保存并继续按钮:
![操作时间 – 使用评论和视图增强喜欢系统]()
-
现在,在允许的值列表字段中输入以下文本,保留其他设置默认,然后点击保存字段设置按钮:
1|Yes 0|No -
对于这一步,请检查必填字段按钮,选择默认值为是,然后点击保存设置按钮。
-
现在,点击管理表单显示链接。将你喜欢它吗?字段小部件更改为复选框/单选按钮小部件,然后点击保存按钮:
![操作时间 – 使用评论和视图增强喜欢系统]()
-
接下来,重新加载食谱内容,您可以看到以下评论表单:
![操作时间 – 使用评论和视图增强喜欢系统]()
-
我们已经启用了喜欢按钮。接下来,我们需要展示有多少人喜欢了食谱内容。我们将创建一个视图块来显示每个食谱内容的点赞数。在您喜欢的浏览器中打开 d8dev 网站。点击管理工具栏中的结构链接,然后点击视图链接,最后点击添加新视图链接。
-
在添加新视图页面,输入视图名称为
Recipe likes count。在视图设置部分,选择显示字段为评论,类型字段为食谱评论。在块设置部分,勾选创建块并点击保存并编辑按钮。 -
接下来,在格式部分,点击显示:评论链接。然后,将出现一个弹出窗口。将行字段值评论更改为字段,并点击应用(所有显示)按钮:
![操作时间 – 使用评论和视图增强喜欢系统]()
-
接下来,在高级部分和其他组下,点击使用聚合:否链接。然后,将出现一个弹出窗口。勾选聚合并点击应用(所有显示)按钮:
![操作时间 – 使用评论和视图增强喜欢系统]()
-
现在,在字段部分,点击添加链接。在弹出的窗口中,搜索关键词
do you like。然后勾选你喜欢它吗?(field_do_you_like_it_)复选框,并点击应用(所有显示)按钮:![操作时间 – 使用评论和视图增强喜欢系统]()
-
在下一屏幕上,选择求和作为聚合类型字段,并点击应用并继续按钮:
![操作时间 – 使用评论和视图增强喜欢系统]()
-
在下一屏幕上,向下滚动到重写结果部分并点击它。勾选用自定义文本覆盖此字段的输出复选框,并在文本字段中输入以下文本:
{{ field_do_you_like_it_ }} likes -
接下来,在样式设置部分,勾选自定义字段 HTML复选框。选择HTML 元素为STRONG,并点击应用(所有显示)按钮。
-
现在,在高级部分,点击添加链接以添加上下文过滤器。在弹出的窗口中,搜索关键词
node id。然后勾选节点 ID复选框,并点击应用(所有显示)按钮。在下一屏幕上,保持设置默认,并点击应用并继续按钮。 -
在下一屏幕的当过滤器值不可用部分,选择提供默认值单选按钮。选择类型为从 URL 获取内容 ID,并点击底部的应用(所有显示)按钮:
![操作时间 – 使用评论和查看增强点赞系统]()
-
接下来,在字段部分,点击评论:标题链接。在弹出的窗口中,点击删除链接。在相同的字段部分,点击添加链接。在弹出的窗口中,搜索
title,并将类型选择为内容。勾选标题复选框,并在底部点击应用(所有显示)按钮。 -
在下一屏幕上,保持默认设置,并点击应用并继续按钮。
-
在下一屏幕上,勾选排除显示复选框,并在底部点击应用(所有显示)按钮。然后,向下滚动并点击保存按钮以保存视图设置。
-
我们已经完成了块视图的创建。现在我们需要将此块放置在食谱内容页面上。点击管理工具栏中的结构链接,然后点击块布局链接。
-
向下滚动到内容部分,并点击放置块按钮。接下来,在弹出的窗口中,搜索
Recipe likes count,并点击此放置块按钮:![操作时间 – 使用评论和查看增强点赞系统]()
-
在下一屏幕上,取消勾选显示标题。在内容类型部分,勾选食谱,并保持其他设置默认。点击保存块按钮:
![操作时间 – 使用评论和查看增强点赞系统]()
-
接下来,在块布局页面,将食谱点赞数块移动到主页内容块上方,并向下滚动到页面底部。点击保存块按钮:
![操作时间 – 使用评论和查看增强点赞系统]()
-
现在我们已经完成了所有配置。现在我们需要检查菜谱内容页面,看看点赞数是如何显示的。我们将使用 Devel 模块生成示例内容和评论。使用 Drush 下载并启用 Devel 模块。在 d8dev 根目录下运行以下命令:
$ drush en devel -y devel was not found. [warning] The following projects provide some or all of the extensions not found: [ok] devel Would you like to download them? (y/n): y Project devel (8.x-1.x-dev) downloaded to /usr/share/nginx/html/drupal-8.0.2//modules/devel. [success] Project devel contains 5 modules: webprofiler, kint, devel_node_access, devel_generate, devel. The following extensions will be enabled: devel Do you really want to continue? (y/n): y devel was enabled successfully. [ok] devel defines the following permissions: access devel information, execute php code, switch users $ drush en devel_generate -y devel_generate is already enabled. [ok] There were no extensions that could be enabled. [ok] -
要生成示例内容,请打开您喜欢的浏览器中的 d8dev 网站,点击管理工具栏中的配置链接,然后点击开发部分下的生成内容链接。
-
在下一屏上,检查菜谱内容类型,在每个节点最大评论数字段中输入
10,然后点击生成按钮。这将生成 50 个带有 10 条评论的菜谱内容:![操作时间 – 使用评论和视图增强点赞系统]()
-
打开由 devel 模块在管理内容页面创建的任何菜谱内容。我们可以看到下一张截图所示的点赞数。如果我们检查这个菜谱的所有评论,我们可以从你喜欢它吗?字段中找到只有4(或多少个是值)的点赞:
![操作时间 – 使用评论和视图增强点赞系统]()
-
只有经过批准的评论的点赞会被计算,所以如果我们收到任何评论,管理员必须将它们更新为已发布评论。为此,我们必须转到
http://localhost/admin/content/comment/approval:![操作时间 – 使用评论和视图增强点赞系统]()
发生了什么?
我们创建了一个视图块,并配置了评论设置,用于在 d8dev 网站上对评论和点赞/踩赞菜谱进行设置。整个过程简单直接,表明有时给 Drupal 网站添加独特的亮点并不需要太多的开发工作。
摘要
在本章中,我们添加了一些新功能,为访客提供了与我们的 d8dev 网站互动的方式,我们还通过评论和视图增强了其中一些互动功能。我们还回顾了上一章中引入的 Colorbox 文件模块,并对它进行了一些修改,以便更好地控制如何在 Colorbox 覆盖层中显示标题。
在下一章中,我们将更深入地探讨视图模块。我们将探索高级配置和开发一个自定义视图插件,该插件利用自定义 jQuery 插件以语义化和渐进增强的方式显示视图输出。
第九章。高级 Views 开发
本章将深入介绍 Views 模块,并介绍一些 Views 插件架构。我们将深入了解 Views UI 中的一些高级功能,包括基于分类的 Views 介绍。我们将开发一个 Views 风格插件,以显示我们的新 Recipes 视图作为语义标签。我们还将把 Views 语义标签模块作为沙盒项目贡献给 Drupal。
在本章中,我们将涵盖以下主题:
-
高级 Views 配置
-
Views 插件开发
-
创建 Drupal 沙盒项目
重访 Views – 高级配置
在最后几章,自定义内容类型和模块开发简介中,我们快速介绍了 Views,并看到了使用新的 Views 向导的用户界面创建视图是多么简单。基于向导的新视图创建使得开始使用 Views 变得容易,但它不包括许多更高级的 Views 配置选项。即使在标准的 Views 编辑页面上,高级配置选项也被隐藏起来,以免让初学者感到不知所措。本章的开始将探讨许多 Views 提供的高级配置选项。Views 配置可能会很快变得复杂。所以,从某种意义上说,高级 Views 配置并不比我们编写的某些 PHP 代码简单。
随机高评价食谱块
首页仍然有点单调乏味。我们将使用 Views 创建一个块,随机展示网站上的一篇高评价食谱。这将涉及使用 Views 过滤器和排序设置。
动手实践时间 – 使用 Views 构建随机高评价食谱块
我们将超越基本的 Views 向导视图创建用户界面,并学习一些更多高级功能和 Views 的配置。
-
在浏览器中打开我们的 d8dev 网站,点击 管理 工具栏中的 结构 链接,然后点击 Views 链接。
-
点击 Views 页面顶部的 添加新视图 链接。
-
将
Random Top Rated Recipe作为 视图名称 输入。 -
为 类型 选项选择 Recipe。
-
打勾 创建一个块 复选框。
![动手实践时间 – 使用 Views 构建随机高评价食谱块]()
-
接下来,点击 保存并编辑 按钮,因为我们想配置一些基本块创建向导中不可用的更多高级选项。
现在,我们将添加我们想要在此块中显示的 Recipe 内容字段。记住,这个块将显示在我们的 d8dev 网站的前页面上,所以我们要让它看起来视觉上吸引人。注意,标题 字段默认已经包含在内。
-
点击字段的添加按钮,搜索
image,并选择内容:图像的复选框,然后点击应用(所有显示)按钮。注意,视图会显示字段关联的节点或内容类型。![使用视图构建随机高评分菜谱块的时机]()
-
对于图像字段的配置字段设置,选择中等(220*220)作为图像样式,然后点击应用(所有显示)按钮。
-
点击字段的添加按钮,搜索
Comment count,并选择评论统计:评论计数的复选框。然后点击应用(所有显示)按钮。 -
对于评论计数的配置字段设置,选择总计数作为前缀,评论作为后缀值。在样式设置部分,勾选自定义字段 HTML复选框,选择STRONG作为HTML 元素字段,然后点击应用(所有显示)按钮。
![使用视图构建随机高评分菜谱块的时机]()
-
接下来,在排序标准部分,点击添加按钮,搜索
Comment count,并选择评论统计:评论计数的复选框。然后点击应用(所有显示)按钮。 -
对于评论计数的配置排序标准设置,选择降序作为顺序字段,然后点击应用(所有显示)按钮。
![使用视图构建随机高评分菜谱块的时机]()
-
接下来,在排序标准部分,点击添加按钮,搜索
Random,并选择随机的复选框。然后点击应用(所有显示)按钮。 -
在下一屏幕上,点击应用(所有显示)按钮。然后保存视图,我们可以看到此块的预览如下截图所示:
![使用视图构建随机高评分菜谱块的时机]()
-
现在您的视图配置应该类似于以下截图所示:
![使用视图构建随机高评分菜谱块的时机]()
-
现在我们需要配置这个基于视图的新块,使其显示在首页上。点击管理工具栏中的结构,然后点击块布局链接。
-
向下滚动到侧边栏第一个区域,点击放置块链接。在弹出窗口中,搜索
Random top,然后点击放置块按钮,为随机高评分菜谱块放置。 -
在下一屏幕上,勾选覆盖标题复选框,输入
Top Recipe作为标题,然后点击保存块按钮。![使用视图构建随机高评分菜谱块的时机]()
发生了什么?
我们使用了视图并利用了一些高级配置选项来显示最新食谱的图片和标题,并学习了如何通过添加基于视图的动态块使我们的 d8dev 网站更加有趣。
基于分类的视图带标签
在本节中,我们将向我们的首页添加另一个基于视图的块。然而,这将是一个基于分类的视图,而不是我们迄今为止创建的内容视图。分类是指信息的组织。正如我们在上一章中学到的,分类是一个可字段的实体。分类模块是一个核心模块,它允许您创建术语词汇,以关联到其他实体类型,以便它们可以组织。因此,在此之前,我们需要创建一个包含分类词汇和术语的视图,并将这些术语关联到我们的食谱内容。我们将添加一个用于按菜系类型组织食谱的词汇。
操作时间 – 创建用于组织食谱的菜系词汇
在我们能够创建基于分类的视图之前,我们需要创建一个 Drupal 分类词汇:
-
在您的浏览器中打开我们的 d8dev 网站,点击管理工具栏中的结构链接,然后点击分类链接。
-
在分类配置页面,点击添加词汇链接。
-
将
菜系类型作为名称输入并点击保存按钮。现在我们将为我们新的词汇添加一些术语。 -
点击我们新的菜系类型词汇的添加术语链接(确保您在
admin/structure/taxonomy/manage/type_of_cuisine/overview页面):![操作时间 – 创建用于组织食谱的菜系词汇]()
-
将
美国作为我们第一个术语的名称并点击保存按钮。 -
重复此过程并添加术语
亚洲和泰国。现在我们将为我们的食谱内容类型添加一个分类字段。 -
点击管理工具栏中的结构链接,点击内容类型链接,然后点击我们食谱内容类型的管理字段链接。
-
点击添加字段链接。在下一屏幕中,选择分类术语作为添加新字段,并将
recipeCuisine作为标签。点击保存并继续按钮。![操作时间 – 创建用于组织食谱的菜系词汇]()
-
在下一屏幕中,选择无限制作为允许的值数,并点击保存字段设置按钮。在下一屏幕中,检查参考类型下的词汇中的菜系类型,并点击保存设置按钮。
![操作时间 – 创建用于组织食谱的菜系词汇]()
-
点击 管理 栏中的 内容 链接,然后点击其中一个菜谱的编辑链接,向下滚动到我们新的 recipeCuisine 字段,选择美国,然后点击 保存 按钮。为腰果青豆鸡和泰式及亚洲菜谱重复此过程。
发生了什么?
我们快速了解了 Drupal 分类法,并创建了一个词汇表来组织 d8dev 菜谱按菜系类型。
现在我们已经添加了一个新的词汇表来关联菜谱内容到菜系类型,我们准备在一个基于视图的块中使用它。我们将创建一个基于视图的块,显示 d8dev 网站按菜系类型的新菜谱条目。除此之外,我们还将按关联菜谱数量最少的菜系类型对菜谱列表进行排序。这将帮助我们推广菜谱数量较少的菜系类型。最后,我们希望有一个基于标签的用户界面,每个菜系类型都有一个标签,该标签的内容是该菜系类型最新的五个菜谱。让我们一步一步来。
行动时间 – 创建按菜系类型查看的菜谱块
我们创建了一个新的词汇表并将其关联到我们的菜谱内容类型。现在我们将学习如何使用自定义词汇与视图一起使用。
-
点击 管理 工具栏中的 结构 链接,点击 视图 链接,然后点击 添加新视图 按钮。
-
在下一页上,将 视图 名称输入为
按菜系菜谱,从 显示 选择列表中选择 分类术语,从 类型 选择列表中选择 菜系类型。 -
在 块设置 部分下勾选 创建一个块 复选框。
-
保留剩余的默认设置,然后点击 保存并编辑 按钮。
-
视图自动添加了分类术语 名称 字段,但我们还希望显示与每个菜系术语关联的最新菜谱。然而,如果您点击 字段 的 添加 按钮,您会注意到没有可用的 内容 字段。我们将使用视图 关系 配置来在菜谱内容和我们要显示的分类术语之间添加一个关系。
-
点击 添加 按钮选择 关系,勾选 使用 field_recipecuisine 字段的菜谱内容 关系,然后点击 应用(所有显示) 按钮。
-
在下一屏幕上,保留剩余的默认设置,然后点击 应用(所有显示) 按钮。
-
现在点击 字段添加 按钮;有可用的内容字段。在 搜索 输入框中输入
title,选择它,然后点击 应用(所有显示) 按钮。注意,字段配置中有一个 关系 选择列表。基于分类术语视图的所有内容字段都需要一个关系。因此,这将是默认列出的第一个关系。
![行动时间 – 创建按菜系类型查看的菜谱块]()
-
保持创建标签复选框未勾选,因为我们只想显示标题本身。我们将保持链接到此内容…复选框勾选,以便用户能够导航到完整的食谱。点击应用(所有显示)按钮。
现在如果您滚动到视图配置页面的底部,您将看到此视图输出的预览,您会看到我们正在显示菜系类型术语名称和食谱标题,但我们希望按术语名称分组食谱标题。
-
接下来,在格式部分下,点击格式设置链接,然后为分组字段编号 1选择Taxonomy term: Name,并点击应用按钮
![执行时间 – 创建按菜系类型显示的食谱视图块]()
-
您将看到此视图输出的预览,您会看到我们正在显示菜系类型术语名称和食谱标题,因为我们按术语名称分组了食谱标题,并且术语名称显示了两遍。因此,我们将在字段设置中隐藏它们。
-
现在点击分类术语:名称链接,并勾选排除显示复选框,因为我们只想显示内容标题。然后点击应用(所有显示)按钮。
-
现在我们将添加一个排序标准,以便首先显示按最新食谱分组的术语。点击添加按钮为排序标准,在搜索字段中输入
Authored on,选择Authored on字段,然后点击应用(所有显示)按钮。 -
在下一屏幕上,选择降序为顺序字段,并点击应用(所有显示)按钮。此视图的预览现在应该类似于以下截图。注意:以下内容是 Devel 模块生成的内容。
![执行时间 – 创建按菜系类型显示的食谱视图块]()
-
接下来,点击此视图的保存按钮。
发生了什么事?
我们创建了一个基于视图的食谱块,通过菜系名称显示食谱。尽管表面上看起来一切正常,但我们的新视图在分组和限制方面存在问题。我们希望显示三种不同的菜系和每种菜系类型的五道食谱,但我们创建的视图只限制了返回的总行数。如果我们再添加一道食谱,那么这道食谱就会显示出来。然而,第六古老的食谱将会被移除,如果新添加的食谱恰好是泰国或亚洲菜系,那么美国分组将会消失。因此,我们只剩下两种菜系类型的分组。实际上,这是一个相当复杂的 SQL 问题,但有一个贡献模块可以让我们得到我们想要的确切结果。
视图字段视图模块(drupal.org/project/views_field_view)启用了一个全局视图字段,允许你将另一个视图嵌入为父视图的字段,有点像一套俄罗斯套娃。然而,对于实际生产使用,请注意,使用这种方法会有一些相当严重的性能影响,因为将会有四个 SQL 查询而不是一个。所以你绝对需要在使用类似这种方法的生产站点之前确保你理解视图缓存和 Drupal 缓存。
操作时间 – 为我们的“按菜系类型查看食谱”安装和使用视图字段视图模块
通过安装和使用视图字段视图模块,我们将学习到有许多与视图相关的扩展模块,它们扩展了视图模块的功能和能力。
-
首先,我们需要安装视图字段视图模块。打开终端(Mac OS X)或命令提示符(Windows)应用程序,并转到我们的 d8dev 站点根目录。
-
使用 Drush 下载并启用视图字段视图模块:
$ drush dl views_field_vies Project views_field_view (8.x-1.0-beta2) downloaded to /var/www/html/d8dev/modules/views_field_view. [success] $ drush en views_field_view The following extensions will be enabled: views_field_view Do you really want to continue? (y/n): y views_field_view was enabled successfully. [ok] -
现在,在我们修改我们的“按菜系查看食谱”视图之前,你需要了解视图字段视图功能是如何工作的。我们将移除食谱标题字段并添加一个全局:视图字段。全局:视图字段允许我们指定另一个视图作为字段的内联内容,而不是我们的食谱内容类型中的字段。它允许我们将任何其他可用的视图字段作为参数传递给其他视图,作为上下文过滤器传递给用作字段内容的视图。我知道这听起来相当复杂,这就是为什么我们将一起慢慢走过这个过程。首先,我们需要创建一个视图,用作视图字段视图,其中将按发布日期降序列出食谱,因此我们将使用视图字段视图字段来显示我们的食谱列表视图中的内容。
-
在管理工具栏中点击结构链接,然后点击视图链接,并点击我们随机最高评分食谱视图的编辑按钮。
-
在下一页的顶部,点击添加按钮,然后点击块链接。
-
点击添加字段链接,在输入搜索框中搜索 node id。勾选排除显示复选框,然后点击应用(此显示)按钮。
-
再次点击添加字段链接,在输入搜索框中搜索“视图”。在下一屏幕上,在视图设置部分,选择食谱点赞数作为视图,块作为显示,以及
{{ fields.nid }}作为上下文过滤器。点击应用(此显示)按钮。![操作时间 – 为我们的“按菜系类型查看食谱”安装和使用视图字段视图模块]()
-
现在,在 ADVANCED(高级)部分,点击 Contextual filters(上下文过滤器)的 Add(添加)链接。在弹出的窗口中,搜索节点 ID 关键词。现在勾选 Node ID(节点 ID)复选框,然后点击 Apply (this displays)(应用(显示))按钮。在下一屏幕上,保持设置默认,然后点击 Apply and continue(应用并继续)按钮。注意:确保在顶部选择了 This block (override)(此块(覆盖))。
-
在下一屏幕上,在 WHEN THE FILTER VALUE IS NOT AVAILABLE(当过滤器值不可用)部分,选择 Provide default value(提供默认值)单选按钮。选择 Type 为 Content ID from URL,然后点击底部的 Apply(this displays)(应用(显示))按钮。
![执行时间 – 为我们的“按菜系类型查看食谱”安装和使用 Views Field View 模块]()
-
然后保存视图,我们可以看到如下截图所示的预览:
![执行时间 – 为我们的“按菜系类型查看食谱”安装和使用 Views Field View 模块]()
-
接下来,点击 Admin 工具栏中的 Structure(结构)链接,然后点击 Views(视图)链接,并点击我们食谱按菜系视图的编辑按钮。
-
在下一页的顶部,点击 Add(添加)按钮,然后点击 Block(块)链接。
-
点击 Add Field(添加字段)链接,并在输入搜索框中搜索节点 ID。勾选 Exclude from display(从显示中排除)复选框,然后点击 Apply (this displays)(应用(显示))按钮。
-
再次点击 Add Field(添加字段)链接,并在输入搜索框中搜索视图。在下一屏幕上,在 VIEW SETTINGS(视图设置)部分,选择 Random Top Rated Recipe(随机最高评分食谱)作为 View(视图),Block 2(块 2)作为 Display(显示),以及
{{ raw_fields.nid }}(原始字段.nid)作为 Contextual filters(上下文过滤器)。点击 Apply (this displays)(应用(显示))按钮。![执行时间 – 为我们的“按菜系类型查看食谱”安装和使用 Views Field View 模块]()
-
然后保存视图,我们可以看到如下截图所示的预览:
![执行时间 – 为我们的“按菜系类型查看食谱”安装和使用 Views Field View 模块]()
-
现在,我们需要配置这个基于 Views 的新块,使其显示在首页上。点击 Admin 工具栏中的 Structure(结构),然后点击 Block Layout(块布局)链接。
-
向下滚动到 Sidebar first Region(侧边栏第一个区域),然后点击 Place block(放置块)链接。在弹出的窗口中,搜索
Recipes by,然后点击 Recipes by Cuisine 块的 Place block(放置块)按钮。
发生了什么?
在 Views Field View 模块的协助下,我们创建了一个显示我们食谱内容的视图。
标签视图显示
为了使“按菜系查看食谱”更加视觉上吸引人,并在我们 d8dev 前页的可视区域内看起来更有组织,我们将显示每个菜系类型作为一个选项卡,并为活动选项卡提供食谱。我们将使用基于 JavaScript 的方法在我们的选项卡界面中显示按菜系分组的食谱。请查看 jQuery UI 选项卡页面(jqueryui.com/tabs/),你将看到我们如何显示“按菜系查看食谱”的示例。

我之所以指出基于 jQuery 的 UI 选项卡,是因为 Drupal 8 包含了 JavaScript 库。因此,使用一个作为 Drupal 8 核心安装的一部分已经可用的 JavaScript 小部件来处理选项卡是非常有意义的。然而,目前为我们的“按菜系查看食谱”生成的标记将很难与 jQuery UI 选项卡集成,因为 jQuery UI 选项卡旨在在单独的 HTML 容器中处理选项卡及其内容。请查看上一张截图中的 jQuery UI 选项卡页面上的示例标记,以了解我的意思。
<div id="tabs">
<ul>
<li><a href="#tabs-1">Tab1 title</a></li>
<li><a href="#tabs-2">Tab2 title</a></li>
<li><a href="#tabs-3">Tab3 title</a></li>
</ul>
<div id="tabs-1">
<p>Tab1 content</p>
</div>
<div id="tabs-2">
<p>Tab2 content</p>
</div>
<div id="tabs-3">
<p>Tab3 content</p>
</div>
</div>
Views 为我们的“按菜系查看食谱”生成的标记更加语义化。它保留了与相关内容关联的组标题(实际上,Views 生成的标记比这要多得多,所以请查看浏览器中“按菜系查看食谱”视图的源输出)。基本上,一个简化版本的 Views 为默认 HTML 列表格式生成的标记更接近以下内容:
<div>
<ul>
<li>content 1</li>
<li>content 2</li>
</ul>
</div>
因此,如果我们想使用 jQuery UI 选项卡,那么我们必须修改 Views 为我们的“按菜系查看食谱”视图生成的标记。一个 Views 风格插件将允许我们生成与 jQuery UI 选项卡通常使用的标记类型。然而,由于我们无论如何都要为 Views 编写一个自定义插件,为什么还要编写一个创建非语义且在渐进增强方面有限的选项卡插件呢?
行动时间 - 开发一个用于语义选项卡的 Views 风格插件
我们将创建一个新的模块来介绍 Views 插件。我们将在下一主题中将它贡献给 Drupal。
-
打开 PhpStorm 并导航到我们 d8dev 项目的
/modules/custom文件夹。 -
创建一个名为
views_semantic_tabs的新文件夹——这是我们新模块的名称。 -
创建一个名为
views_semantic_tabs.info.yml的新文件,并输入以下信息:name: Views semantic tabs type: module description: Provides a views style plugin to display views results in jQuery UI Tabs. core: 8.x package: Views dependencies: - views在 Drupal 7 中,我们使用
hook_views_plugins()钩子函数来注册新插件。另一方面,Drupal 8 依赖于注解和自动加载来发现任何插件,如块和视图样式。自动加载的概念允许我们将插件文件放在预定义的目录中,Views/Blocks 在需要时找到它。在插件文件内部使用注释块指定的任何插件元数据称为注解。 -
要通过视图找到我们的样式插件,我们必须将其放置在我们自定义模块
modules/views_semantic_tabs目录内的src/Plugin/views/style文件夹中。因此,我们希望构建一个语义标签样式插件,该插件显示基于 jQuery UI 标签的样式。 -
在
/views_semantic_tabs/src/Plugin/views/style目录内创建一个新的文件ViewsSemanticTabs.php。语义标签样式插件的骨架如下所示:<?php /** * @file * Contains \Drupal\views\Plugin\views\style\HtmlList. */ namespace Drupal\views_semantic_tabs\Plugin\views\style; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\style\StylePluginBase; /** * Style plugin to render each item in an ordered or unordered list. * * @ViewsStyle( * id = "views_semantic_tabs", * title = @Translation("Semantic tabs"), * help = @Translation("Configurable semantic tabs for views fields."), * theme = "views_semantic_tabs_format", * display_types = {"normal"} * ) */ class ViewsSemanticTabs extends StylePluginBase { /** * Does the style plugin allows to use style plugins. * * @var bool */ protected $usesRowPlugin = TRUE; /** * Does the style plugin support custom css class for the rows. * * @var bool */ protected $usesRowClass = TRUE; /** * Does the style plugin support grouping of rows. * * @var bool */ protected $usesGrouping = FALSE; /** * Set default options */ protected function defineOptions() { $options = parent::defineOptions(); $options['group'] = array('default' => array()); return $options;} /** * Render the given style. */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { parent::buildOptionsForm($form, $form_state); $options = array('' => $this->t('- None -')); $field_labels = $this->displayHandler->getFieldLabels(TRUE); $options += $field_labels; $grouping = $this->options['group']; $form['group'] = array( '#type' => 'select', '#title' => $this->t('Grouping field'), '#options' => $options, '#default_value' => $grouping, '#description' => $this->t('You should specify a field by which to group the records.'), '#required' => TRUE, ); } }我们的
ViewsSemanticTabs类需要继承自StylePluginBase类,因此我们使用了一个关键字。此外,我们使用FormStateInterface在我们的类中使用表单。此文件位于正确的位置,并在类定义上方有一个注解。这个注解提供了样式、主题和显示类型的 ID。因此,视图将找到这个插件,它将在视图格式样式设置中可用。受保护的$usesRowPlugin属性对这个插件是必要的,它让我们选择是否希望在视图显示中显示字段或渲染的内容。受保护的$usesRowClass属性也是我们案例中必需的,它决定了样式插件是否支持为行提供自定义 CSS 类。受保护的defineOptions()方法用于定义默认选项,这些选项将在设置表单中显示。受保护的buildOptionsForm()方法用于定义任何将在设置表单中显示的任何自定义表单值。parent::buildOptionsForm($form, $form_state);允许我们访问我们扩展的类的输出并操作该输出。我们使$form['group']字段成为必填项,因为视图 HTML 渲染输出需要分组字段名称。 -
视图的输出将保存在模板文件中,并且模板文件需要放置在我们模块的
templates目录内,文件名为views-semantic-tabs-format.html.twig。我们不需要实现hook_theme()钩子函数,因为它将根据在views_semantic_tabs_format注解中指定的主题名称自动注册。模板文件名是通过将主题名称中的下划线替换为连字符,并添加html.twig扩展名来生成的。<div class="views-semantic-tabs"> <ul> {% set i = 1 %} {% for row in rows.group %} <li><a href="#tabs-{{ i }}">{{ row }}</a></li> {% set i = i + 1 %} {% endfor %} </ul> {% set j = 1 %} {% for row in rows %} <div id="tabs-{{ j }}">{{ row.content }}</div> {% set j = j + 1 %} {% endfor %} </div>首先,我们需要理解这个模板将用于每个标签页。我们不得不将分组字段值作为一组单独的集合,这些集合是
<ul>中的<li>标签的一部分。我们正在包装行数据,这是标签页的内容,并用带有唯一tabs-id属性的div标签包装。 -
模板中的变量由视图提供。为
twig文件准备变量可以通过.module文件中的template_preprocess_views_semantic_tabs_format()函数来完成。这个函数名称是基于注释views_semantic_tabs_format中指定的名称定义的。在/modules/views_semantic_tabs目录内创建一个名为views_semantic_tabs.module的新文件。语义标签样式插件的骨架如下所示:<?php /** * @file * Contains views_semantic_tabs.module.. */ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Template\Attribute; /** * Implements hook_help(). */ function views_semantic_tabs_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { // Main module help for the views_semantic_tabs module. case 'help.page.views_semantic_tabs': $output = ''; $output .= '<h3>' . t('About') . '</h3>'; $output .= '<p>' . t('Provides a views style plugin to display views results in jQuery UI Tabs.') . '</p>'; return $output; default: } } /** * Prepares variables for Views HTML list templates. * * @param array $variables * An associative array containing: * - view: A View object. */ function template_preprocess_views_semantic_tabs_format(&$variables) { $handler = $variables['view']->style_plugin; $view = $variables['view']; $rows = $variables['rows']; $style = $view->style_plugin; $fields = &$view->field; $options = $style->options; $variables['fields'] = $fields; // add jquery & jquery ui $variables['view']->element['#attached']['library'][] = 'core/jquery.ui.tabs'; $variables['view']->element['#attached']['library'][] = 'core/jquery'; $variables['view']->element['#attached']['library'][] = 'views_semantic_tabs/views-semantic-tabs'; // Get first group field selected if($options){ $first_group_field = $options['group']; } // Add tabs id to main div $variables['attributes'] = new Attribute(array('id' => 'tabs')); $fields = &$view->field; $variables['default_row_class'] = !empty($options['default_row_class']); foreach ($rows as $id => $row) { // todo : group field value should be plain text $field_output = $handler->getField($id, $first_group_field); $variables['rows']['group'][] = $field_output; $variables['rows'][$id] = array(); $variables['rows'][$id]['content'] = $row; $variables['rows'][$id]['attributes'] = new Attribute(); if ($row_class = $view->style_plugin->getRowClass($id)) { $variables['rows'][$id]['attributes']->addClass($row_class); } } }有两个函数,
template_preprocess_views_semantic_tabs_format()和hook_help。在我们的template_preprocess_views_semantic_tabs_format()函数的顶部,我们正在从$variables变量中获取所需的处理器、行、字段和样式属性。然后我们将jquery.ui.tabs和 jQuery 核心 jQuery 库附加到$variable上,因为它需要这两个 jQuery 库。我们还附加了自定义的views-semantic-tabs库,它有一个自定义 JS 文件,我们将定义 jQuery 代码来工作标签。在接下来的几行中,我们正在构建行数据,它是标签内容的一部分。我们还声明了帮助函数来定义关于此模块的简单文档。有关hook_help()函数的更多信息,请访问api.drupal.org/api/drupal/core!modules!help!help.api.php/function/hook_help/8。 -
接下来,在新的主题文件夹上右键点击,创建一个名为
views_semantic_tabs.libraries.yml的新文件,并将以下代码添加到该文件中:views-semantic-tabs: version: VERSION js: js/views-semantic-tabs.js: {} dependencies: - core/jquery - core/drupal这是我们在上一步的
template_preprocess_views_semantic_tabs_format()函数中附加的库文件。它定义了views-semantic-tabs.js文件,该文件包含使 jQuery 标签按 twig 文件中定义的渲染 HTML 结构工作的 jQuery 代码。 -
接下来,在模块文件夹上右键点击,创建一个名为
js的新文件夹。再次右键点击js文件夹,创建一个名为views-semantic-tabs.js的文件。将以下代码添加到该文件中:(function ($) { $(function() { $( ".views-semantic-tabs" ).tabs(); }); })(jQuery);注意整个 JavaScript 代码块是如何被
(function ($) { ... })(jQuery);包裹的。这是 Drupal 7 和 Drupal 8 的新 JavaScript 命名空间特性,它允许其他 JavaScript 库与 Drupal 一起使用,减少了冲突的可能性。$( ".views-semantic-tabs" ).tabs();是使我们的 twig 渲染的 HTML 代码作为标签工作的代码。 -
现在我们已经完成了所有代码,我们的
views_semantic_tabs文件夹应该看起来类似于以下截图:![Time for action – developing a Views style plugin for semantic tabs]()
-
现在我们已经准备好通过将其应用于我们的“按菜系查看食谱”视图来测试我们新的视图样式插件,但首先我们需要启用我们的新模块。我们可以使用 Drush 来做这件事,但我希望能在浏览器中启用自定义模块,这样我就能看到我的新模块。
-
在您的浏览器中打开我们的 d8dev 网站,点击管理工具栏中的扩展链接,然后滚动到模块的视图部分或搜索输入框中搜索
views semantic。 -
您应该看到我们的新视图语义标签模块与其他已安装的视图模块一起列出。
![进行操作 - 开发用于语义标签的视图样式插件]()
-
打勾以启用我们的新模块,然后点击安装按钮。
-
接下来,点击管理工具栏中的主页链接,然后点击我们的食谱按菜系视图的上下文链接按钮,并点击编辑视图链接。
![进行操作 - 开发用于语义标签的视图样式插件]()
-
在格式下,点击未格式化列表链接。现在,视图样式设置表单包括我们新的视图样式插件:语义标签。
![进行操作 - 开发用于语义标签的视图样式插件]()
-
选择我们的语义标签样式,然后点击应用(此显示)按钮。
-
接下来,在样式选项屏幕上,选择分类术语:名称作为分组字段,然后点击应用按钮。请注意,分组字段是必需的,因为我们已在我们的插件类中指定。
![进行操作 - 开发用于语义标签的视图样式插件]()
-
现在,点击保存按钮以保存我们对视图的更改。您现在应该有一个类似于以下截图的食谱按菜系视图块:
![进行操作 - 开发用于语义标签的视图样式插件]()
刚才发生了什么?
虽然这个例子相当复杂,但一旦你掌握了视图插件的一些开发概念,实际的代码就相当简单了。我们能够创建一个定制的视图样式插件,这将增强我们 d8dev 网站上内容的显示。
又是时候准备另一道菜谱
这是一点点辣味美式风情——库尔特的经典辣椒。将它添加到 d8dev 网站上,并查看上一节中的食谱按菜系视图(秘密成分是香叶)。

-
名称: 库尔特的经典辣椒
-
描述: 在寒冷的冬日里,没有什么比一碗热腾腾的辣椒更让人感到舒适了。自制的辣椒粉真的给这道菜增添了独特的美味风味。
-
食谱份量: 八份
-
准备时间: 30 分钟
-
烹饪时间: 60 分钟
-
配料:
-
一磅碎牛肉
-
两汤匙橄榄油
-
一个大型的甜洋葱,切碎
-
六瓣大蒜,压碎
-
八个安乔辣椒,干燥
-
八个瓜希洛辣椒,干燥
-
两汤匙糖浆
-
一汤匙可可粉
-
六盎司拉格啤酒
-
三汤匙孜然
-
半杯牛肉汤
-
两杯番茄酱
-
一个大型的黄甜椒,切丁
-
一个大型的哈瓦那辣椒,切丁
-
一杯淡色肾豆
-
一杯深色肾豆
-
三片香叶
-
-
步骤:
-
将干辣椒放入食品加工机中,搅拌两分钟。
-
加入捣碎的大蒜、糖浆和可可粉,搅拌两分钟。
-
在中等低温下向一个大荷兰炖锅中加入油,加热三到四分钟。
-
将火力调至中等,加入洋葱并烹饪,频繁搅拌,直到开始焦糖化,大约四到八分钟。
-
将牛肉末加入洋葱中,频繁搅拌,直到肉变棕色,大约八分钟。
-
将干辣椒混合物与牛肉末和洋葱混合,炒三到四分钟。
-
向荷兰炖锅中加入啤酒并搅拌,以松动底部烧焦的碎片,然后在中等火候下煮沸五分钟。
-
加入番茄酱和孜然,搅拌至混合均匀。煮沸五分钟。
-
加入切碎的辣椒、红肾豆和月桂叶。将火力调至低,偶尔搅拌,煮沸 30 分钟。
-
将 Views 语义标签模块贡献给 Drupal
在本章中,我们在语义标签模块上投入了大量精力。似乎让这些增强功能对整个 Drupal 社区都很有意义。但在我们这样做之前,我们需要做一些事情来确保该模块对 Drupal 社区尽可能有用。
Drupal 有严格的编码标准,在首次推广任何代码时必须严格执行。有关 Drupal 编码标准的良好概述可在 drupal.org/coding-standards 找到。在将任何代码贡献给 Drupal 之前,应检查以确保其符合 Drupal 的编码标准。幸运的是,这相当简单,因为正如之前提到的页面所指出的,有一个 Coder 模块可以提供检查代码标准合规性的自动化流程。
然而,我们不需要安装此模块,因为我们有在线工具可以完成这项工作。pareview.sh 是一个服务,它使用 PHP CodeSniffer 对 Drupal 项目进行自动审查。此在线服务提供了最新的 pareview 脚本,无需安装本地测试环境。现在,是时候在 Drupal 中创建一个沙盒模块项目并推送我们的 views 语义标签模块代码了。
创建 views 语义标签模块的沙盒 - 行动时间
Drupal 允许我们创建新的项目类型,如模块、主题或发行版。在我们的案例中,我们想要贡献一个模块。
-
前往
www.drupal.org/node/add并点击 模块项目 链接。![创建 views 语义标签模块的沙盒 - 行动时间]()
-
我正在填写表格。我们的新项目最初将是一个沙盒项目。我已经有权限将项目从沙盒提升到完整项目。我可以看到一个复选框,允许您选择完整项目,但通常最好是先从沙盒开始。
![创建用于视图语义标签模块的沙盒操作时间 – 创建沙盒]()
-
点击保存按钮,Drupal 将为您的全新项目创建和加载一个页面:
www.drupal.org/sandbox/krishnakanth17/2665888。![创建用于视图语义标签模块的沙盒操作时间 – 创建沙盒]()
-
点击新项目页面顶部附近版本控制标签,以获取如何开始向沙盒仓库提交代码的说明。
![创建用于视图语义标签模块的沙盒操作时间 – 创建沙盒]()
-
首次设置此仓库时,我们需要遵循一些 Git 步骤来推送我们的模块代码。注意:在最后一步后,您将被提示输入您的 Drupal 密码。
$ mkdir views_semantic_tabs $ cd views_semantic_tabs $ git init $ git checkout -b 8.x-1.x $ echo "name = Views semantic tabs" > views_semantic_tabs.info.yml $ git add views_semantic_tabs.info $ git commit -m "Initial commit." $ git remote add origin krishnakanth17@git.drupal.org:sandbox/krishnakanth17/2665888.git $ git push origin 8.x-1.x -
接下来,我们需要将所有模块文件推送到沙盒中。因此,复制并粘贴此目录内的所有文件,并按照
www.drupal.org/project/2665888/git-instructions页面上的这些git命令进行操作。$ git add -A $ git commit -m "Pushing all files of module" $ git push -u origin 8.x-1.x -
我们已经完成了创建沙盒项目和将我们的模块代码推送到它的操作。接下来,我们需要将这个模块提升为完整项目。我有权限将项目从沙盒提升到完整项目,一旦我们的沙盒处于我们认为可以提升为完整项目状态时,可以申请这个权限。如果我们没有这个权限,那么我们必须通过一次性的审批流程。查看
www.drupal.org/node/1011698获取更多信息。 -
但我们正在继续提升到完整项目,我们的代码已贡献给 drupal.org,应该检查它是否符合 Drupal 的编码标准。因此,我们将使用在线工具来检查 Drupal 标准。正如我们之前讨论的,我们将使用
pareview.sh。在 URL 输入框中,输入 URLhttp://git.drupal.org/sandbox/krishnakanth17/2665888.git,然后点击提交分支按钮。![创建用于视图语义标签模块的沙盒操作时间 – 创建沙盒]()
-
提交后,它会把我带到
pareview.sh/pareview/httpgitdrupalorgsandboxkrishnakanth172665888.git页面,并列出需要修复的事项。![创建用于视图语义标签模块的沙盒操作时间 – 创建沙盒]()
-
修复所有这些错误需要一些时间。
在下一章中,我们将把这个沙盒模块提升为一个完整的项目。
摘要
在本章中,我们学习了很多关于视图的知识,并看到了视图如何通过基于网络的用户界面让你向网站添加有趣的功能组件。我们还了解到,视图提供了一个强大的开发平台,用于自定义扩展。我们为视图语义标签模块创建了一个沙盒项目。我们还调查了一些在线工具,以审查我们的模块并检查 Drupal 编码标准。
在下一章中,我们将添加一些视觉上引人注目的横幅组件,这些组件将利用本章中提到的视图开发,并展示 d8dev 网站上所有美丽的食谱照片。最后,我们将推广视图语义标签模块作为完整项目。
第十章。Drupal 项目管理与合作
在本章中,我们将学习将沙盒模块提升为完整项目状态的过程。在这个过程中,你将了解更多关于 Drupal 开发者社区、开发者协作以及如何更深入地参与其中。我们还将探讨如何使用新引入的配置管理和功能来管理生产网站上的增量开发。最后,我们将为 Drupal 8 创建一个旋转横幅模块。
本章将涵盖以下主题:
-
Drupal 中的发布管理
-
更高级的模块开发
-
如何使用功能模块
使用视图幻灯片模块创建旋转横幅
我们将探讨一种基于视图插件的方法,其主要由视图配置组成。视图插件输出的自定义将使用自定义 CSS 来处理。
视图幻灯片模块是一个视图样式插件的优秀示例,它提供了比仅旋转横幅更多的功能。基本上,视图幻灯片模块将 jQuery Cycle 插件作为视图样式插件进行包装,但它这样做是通过一个子模块,即views_slideshow_cycle模块。views_slideshow模块不仅仅是一个视图样式插件。它是一个插件框架,它将不同的 jQuery 幻灯片插件与视图集成,并提供基于 jQuery cycle 插件的默认实现。
行动时间 - 安装视图幻灯片模块
在我们使用视图创建旋转横幅之前,我们需要安装视图幻灯片模块:
-
打开终端(Mac OS X)或命令提示符(Windows)应用程序,并切换到 d8dev 站点的根目录。
-
使用 Drush 下载并启用视图幻灯片模块:
$ drush dl views_slideshow Project views_slideshow (8.x-4.0) downloaded to /var/www/d8dev/sites/all/modules/views_slideshow. [success] Project views_slideshow contains 2 modules: views_slideshow_cycle, views_slideshow. root@vb:/var/www/html/drupal8_3_new# drush en views_slideshow views_slideshow_cycle, jQuery Cycle appears to be already installed. [ok] Directory libraries/json2 was created [success] The latest version of JSON2 has been downloaded to libraries/json2 [success] Directory libraries/jquery.hoverIntent was created [success] The latest version of jQuery HoverIntent has been downloaded to libraries/jquery.hoverIntent [success] Directory libraries/jquery.pause was created [success] The latest version of jQuery Pause has been downloaded to libraries/jquery.pause [success] The following extensions will be enabled: views_slideshow, views_slideshow_cycle Do you really want to continue? (y/n): y views_slideshow was enabled successfully. [ok] views_slideshow_cycle was enabled successfully. [ok]
发生了什么?
视图幻灯片模块由两个模块组成:基础views_slideshow模块和views_slideshow_cycle模块。我们需要告诉 Drush 安装views_slideshow_cycle模块,Drush 将自动安装属于同一父模块的任何依赖项;在这种情况下,views_slideshow模块将由 Drush 自动启用。同时请注意,Drush 提示我们下载views_slideshow_cycle的其他未满足的依赖项;在这种情况下,外部 js 库jquery.cycle、jquery.hoverIntent、jquery.pause和json2被下载到libraries文件夹中。
使用视图幻灯片创建旋转横幅
视图幻灯片模块是一个视图样式插件。我们将创建一个基于块的视图,该视图将使用此样式插件将我们的食谱图片列表转换为旋转横幅。之后,我们就能在我们的 d8dev 网站首页上显示它。
行动时间 - 使用视图幻灯片模块创建横幅
现在我们已经安装并设置了视图幻灯片模块,是时候我们构建一个基于视图的旋转横幅了:
-
在浏览器中打开 d8dev 网站,点击管理工具栏中的结构链接,然后点击视图链接。
-
我们正在创建一个新的视图。点击视图列表页面顶部的添加新视图链接。
-
将视图名称输入为
Front Banner,并选择食谱作为类型。我们将创建我们的旋转横幅作为一个块,所以勾选创建一个块复选框。 -
接下来,选择字段的幻灯片用于块显示设置。验证添加新视图表单看起来与以下截图相似,然后点击保存并编辑按钮:
![使用视图幻灯片模块创建横幅的行动时间]()
-
现在,我们需要决定我们想在横幅中显示哪些字段。内容:标题字段已默认添加。但我们显然想在旋转横幅中显示一个图像。
-
点击字段的添加按钮,在搜索输入中搜索
Images,并选择图像。点击应用(所有显示)按钮。 -
接下来,在配置字段表单中,选择大作为图像样式,然后点击应用(所有显示)按钮。
-
现在,如果你滚动到视图页面的预览区域,你会看到一个类似以下截图的工作幻灯片:
![使用视图幻灯片模块创建横幅的行动时间]()
-
点击过滤器条件的添加按钮。在搜索输入框中搜索
images,并选择任何图像(field_images:title)过滤器。点击应用(所有显示)按钮。 -
在下一屏幕上,选择不是空的(NOT NULL)运算符,然后点击应用(所有显示)按钮。
-
点击字段添加按钮的下拉菜单,并选择重新排列:
![使用视图幻灯片模块创建横幅的行动时间]()
-
接下来,只需将内容:标题字段拖到内容:图像字段下方,然后点击应用(所有显示)按钮。
现在,我们准备好查看我们的新视图幻灯片横幅在首页上的样子。
-
点击我们新视图的保存按钮。然后点击管理工具栏中的结构链接,最后点击块布局链接。
-
滚动到内容区域,并点击放置块链接。在弹出窗口中,搜索
Front banner,然后点击放置块按钮以放置Front Banner块。 -
在下一屏幕上,取消选中覆盖标题复选框。接下来,选择内容作为显示我们 D8Dev 块的区域。在可见性 | 页面部分,选择显示在列出的页面,并输入
<front>作为唯一要显示的页面:![使用视图幻灯片模块创建横幅的行动时间]()
-
点击保存块按钮。在块配置页面,将前横幅块拖到内容区域中的主页内容块上方,然后在屏幕底部点击保存块按钮。
![执行时间 – 使用视图幻灯片模块创建横幅]()
-
当你完成时,前横幅应该看起来类似于以下截图:
![执行时间 – 使用视图幻灯片模块创建横幅]()
发生了什么?
我们使用视图幻灯片插件创建了一个前横幅块,并将其分配到我们的 d8dev 网站的前页。
我们创建了一个幻灯片放映,但它正在显示我们与食谱一起上传的图片大小。这些大小可能各不相同。为了使图片大小统一,我们不得不创建一个可以应用于此幻灯片放映以及任何其他我们想要显示相同大小图片的位置的图像样式。
执行时间 – 为我们的旋转食谱横幅中的图片创建新的图像样式
在上一章中,我们学习了如何为 Drupal 8 添加图像样式。让我们添加一个名为front_banner的新图像样式,该样式将我们的食谱图片缩放到宽度不超过 680 像素,高度裁剪到 410 像素。我们将将其应用于我们的前横幅视图的图像字段。这将使我们的旋转横幅看起来更一致,因为它不会从幻灯片到幻灯片改变大小:
-
在浏览器中打开 d8dev 网站,点击管理工具栏中的配置链接,然后点击媒体链接下的图像样式。
-
我们正在创建一个新的图像样式。因此,点击图像样式页面顶部的+添加图像样式链接。
-
将图像样式名称输入为前横幅,然后点击创建新样式按钮。
-
选择裁剪作为效果,然后点击页面底部的添加按钮:
![执行时间 – 为我们的旋转食谱横幅中的图片创建新的图像样式]()
-
接下来,将
680输入为宽度,将410输入为高度,然后点击添加效果按钮:![执行时间 – 为我们的旋转食谱横幅中的图片创建新的图像样式]()
-
现在我们需要更新这个图像样式以用于前横幅视图。点击管理工具栏中的配置链接,然后点击视图链接。
-
接下来,点击前横幅视图的编辑链接。在下一个视图编辑页面,点击字段下的内容:图片链接,并将图像样式字段选择为前横幅。点击应用(所有显示)按钮。然后点击屏幕底部的保存按钮保存视图。
-
当你完成时,前横幅应该看起来类似于以下截图:
![执行时间 – 为我们的旋转食谱横幅中的图片创建新的图像样式]()
发生了什么?
我们刚刚为旋转食谱横幅中的图片创建了一个新的图像样式。
使用翻页器和 CSS 增强前横幅的外观
我们的新前横幅工作得很好,但我们可以通过添加更多配置和 CSS 轻松改善其外观和用户体验。我们打算向我们的 d8dev 模块添加自定义 CSS 来调整旋转横幅的外观,但首先我们要添加一个翻页器,它会显示有多少幻灯片以及当前幻灯片。
执行时间 – 更新前横幅视图以包含幻灯片翻页器
我们将增强我们的视图旋转横幅,添加一个翻页器:
-
在浏览器中打开 d8dev 网站,将鼠标悬停在新的前横幅上,点击上下文链接小部件,然后点击编辑视图链接:
![执行时间 – 更新前横幅视图以包含幻灯片翻页器]()
-
接下来,我们需要向我们的视图添加一个翻页器。点击幻灯片格式化器的设置链接:
![执行时间 – 更新前横幅视图以包含幻灯片翻页器]()
-
在块:样式选项表单中,滚动到底部小部件部分,并勾选翻页器复选框。勾选此复选框后,我们可以看到其他字段出现。在翻页器字段部分下勾选内容:标题,然后点击应用按钮。请看以下截图:
![执行时间 – 更新前横幅视图以包含幻灯片翻页器]()
-
现在,点击视图的保存按钮,查看更新的前横幅。请看以下截图:
![执行时间 – 更新前横幅视图以包含幻灯片翻页器]()
-
虽然这并不是我们想要的视觉效果鲜明的翻页器,但如果你点击任何标题,你会注意到幻灯片会切换到相应的页面项。所以,尽管翻页器工作正常,但外观并不那么出色。让我们看看我们能为它的外观做些什么,通过向我们的 d8dev 模块添加一些自定义 CSS。
-
在 Chrome/Firefox 中打开 d8dev 网站的前页,右键点击你的旋转横幅,并从弹出的上下文菜单中选择Inspect Element:
![执行时间 – 更新前横幅视图以包含幻灯片翻页器]()
-
在元素检查器中,找到带有
views-slideshow- controls-bottom类的div标签,并展开它:![执行时间 – 更新前横幅视图以包含幻灯片翻页器]()
-
选择带有
views-slideshow-pager-field类的div标签,使其高亮。然后在样式检查器中的element.style花括号内点击,并输入float: right;。我们可以看到页面字段标题向右浮动时的变化。 -
接下来,在 PhpStorm IDE 中,打开位于
d8dev/modules/d8dev/styles/的d8dev.css文件。 -
滚动到文件底部,添加以下样式:
div.views_slideshow_pager_field { bottom: 30px; float: left; position: relative; text-align: right; z-index: 113; } -
现在,回到浏览器中,展开
views-slideshow-pager-fielddiv,找到带有div.views_slideshow_pager_field_item选择器的div标签,并在d8dev.css文件中为div.views_slideshow_pager_field_item选择器添加以下样式:div.views_slideshow_pager_field_item{ display: inline-block; background-color: #999; width: 10px; height: 10px; text-indent: -9999px; border: 2px solid #CCC; -moz-border-radius: 4px; border-radius: 8px; margin-left: 4px; cursor: pointer; } div.views_slideshow_pager_field_item:hover, div.views_slideshow_pager_field_item.active{ background-color: #BF0000; } -
清除缓存并在浏览器中刷新前页。请看以下截图:
![行动时间 – 更新前页横幅视图以包含幻灯片翻页器]()
-
现在将 CSS 添加到隐藏
views-content-titlediv。在 PhpStorm IDE 中打开style.css文件,在文件末尾添加以下 CSS:div.views-content-title { display: none; }注意
注意:通过从视图字段设置中排除内容标题,也会隐藏标题,但请确保其他 CSS 也会受到影响。
-
清除缓存并在浏览器中刷新前页:
![行动时间 – 更新前页横幅视图以包含幻灯片翻页器]()
-
现在,我们需要对菜谱标题进行一些样式设计。我们将增加字体大小,并将其定位在翻页器上方。但将文本对齐在图像中心,添加背景色番茄红,文字颜色白色。同时,我们将更新为 672 像素,因为我们取了 4 像素的填充。
-
在 PhpStorm IDE 中将以下 CSS 添加到
div.views-field-title选择器:#views_slideshow_cycle_main_front_banner-block_1 .views-field-title { background: tomato none repeat scroll 0 0; bottom: 40px; color: white; display: block; font-weight: bold; padding: 4px; position: absolute; text-align: center; width: 672px; } -
清除缓存并在浏览器中刷新前页;你会看到菜谱标题的阅读更加容易。
![行动时间 – 更新前页横幅视图以包含幻灯片翻页器]()
发生了什么?
我们在我们的前页横幅中添加了一个翻页器和标题,尽管我们没有编写很多自定义 PHP 代码,但我们看到了如何通过适当的视图配置模块(视图幻灯片)和一点创意 CSS,将一点点的视图配置结合起来产生很好的效果。
是时候准备另一道菜谱了
这是一道适合寒冷冬日的美味又营养的汤。几乎任何有饮食限制的人都能享受这道健康美味的汤。

-
名称:土豆韭菜汤(纯素)
-
菜系类型:欧洲
-
描述:这道健康且仍然浓稠的汤会真正地温暖你的胃,在寒冷的日子里让你感到温暖。
-
菜谱产量:十份
-
准备时间:30 分钟
-
烹饪时间:45 分钟
-
原料:
-
五到六个大土豆,去皮并切成四份
-
四根韭菜,洗净并切成薄片
-
一个大甜洋葱,切碎
-
四汤匙纯素黄油
-
一汤匙橄榄油
-
六杯蔬菜高汤
-
一杯普通大豆奶
-
海盐
-
新磨的黑胡椒
-
一汤匙米醋
-
两茶匙磨碎的红辣椒
-
1/4 杯切碎的香菜
-
-
说明:
-
在中火上用大荷兰锅融化纯素黄油。
-
当黄油融化后,加入切碎的洋葱,炒至开始变焦糖色。
-
在中火上加入细切的韭菜,炒至 10 分钟,大约每分钟搅拌一次。
-
加入切碎的土豆,与韭菜和洋葱一起炒至 10 分钟,大约每分钟搅拌一次。
-
加入橄榄油并搅拌以混合。
-
加入蔬菜高汤和平常的豆浆。搅拌均匀。用中高火煮沸,然后降低火力至低。
-
文火煮 15 分钟。
-
使用浸入式手持搅拌器将汤搅拌成光滑的泥状。
-
加入醋和磨碎的红辣椒。
-
根据口味加入海盐和新鲜磨碎的黑胡椒。
-
加入新鲜的香菜并享用。
-
将沙盒项目提升为完整项目
虽然我们将我们的视图语义标签模块的更改提交到了沙盒 Git 仓库——这样做使得代码可供任何想要使用它的人使用——但对于许多不是开发者只想下载模块、配置并使用的人来说,使用 Git 是一个障碍。沙盒模块也可能阻止人们尝试你的模块,因为他们可能不会信任不是一个完整项目的模块(Drupal 在所有沙盒模块页面的顶部都有一个很大的警告)。我将创建一个可以轻松下载的版本,无需 Git。
在这个阶段,我将只创建一个 alpha 版本,直到社区有机会对其进行测试。一旦收到一些反馈,我将创建一个完整版本。在我们开始之前,我们将遵循模块文档指南 www.drupal.org/node/161085。阅读完这个页面后,我决定将 README.txt 文件添加到我们的视图语义标签模块中。
将 Views 语义模块提升为完整项目的时间 - 创建 README.txt 并推送到沙盒
-
在
views_semantic_tabs文件夹下创建README.txt文件,如下所示:Introduction ------------ This module provides a views style plugin to display views results in jQuery UI Tabs. Requirements ------------ This module requires the following modules: * Views (https://drupal.org/project/views) INSTALLATION ------------ * Install as you would normally install a contributed Drupal module. See: https://drupal.org/documentation/install/modules-themes/modules-8 for further information. CONFIGURATION ------------- * Update your view with semantic tabs style under format style in the view. MAINTAINERS ----------- Current maintainers: * Krishna kanth (krknth) - https://drupal.org/u/krknth * Neeraj kumar (neerajskydiver) - https://drupal.org/u/neerajskydiver -
接下来,我们需要将其推送到 Git 沙盒。打开终端,转到
modules/views_semantic_tabs文件夹,并运行以下命令:$ git add README.txt $ git commit -m "Added README.txt file."$ git push origin 8.x.2.x
发生了什么?
我们在模块文档指南中了解了一些关于 Drupal 的 README.txt 文件的内容。我们将其添加到模块中,并将其推送到 Drupal 项目的 Git 仓库。
将 Views 语义模块提升为完整项目的时间 - 提升视图语义模块到完整项目
-
访问我们的沙盒项目页面
www.drupal.org/sandbox/krishnakanth17/2665888。然后点击编辑标签:![将 Views 语义模块提升为 Drupal.org 上的完整项目的时间 - 提升视图语义模块到完整项目]()
-
然后是提升子标签:
![将 Views 语义模块提升为 Drupal.org 上的完整项目的时间 - 提升视图语义模块到完整项目]()
-
填写表格,确保输入一个简短的项目名称:
![将 Views 语义模块提升为完整项目的时间 - 提升视图语义模块到完整项目]()
-
下一页询问我是否确定要提升模块,我确定,所以我将点击提升按钮:
![将 Views 语义模块提升为完整项目的时间 - 提升视图语义模块到完整项目]()
-
现在它不再是沙盒项目,Drupal 为项目提供了关于远程仓库的一些重要说明。以下截图中的 Git 命令需要在将任何新更改推送到远程仓库之前执行,因为 Drupal 已经将其移动到新的位置,以适应完整项目和新的简短项目名称:
![操作时间 – 将 Views 语义模块提升为 Drupal.org 上的完整项目]()
-
经过所有这些步骤,我们的模块已提升为完整项目,并且我们可以通过 URL
www.drupal.org/project/views_semantic_tabs访问它。 -
现在我们需要为 Views 语义标签模块创建一个开发版本,以便更容易下载和安装。Drupal 在完整项目页面的版本控制页面上提供了有关使用 Git 创建开发分支的说明(这些说明仅对模块维护者显示)。
-
在我们的本地 Views 语义标签仓库上执行以下 Git 命令将创建一个新的开发分支:
$ git checkout -b 8.x-2.x $ git push -u origin 8.x-2.x -
现在我们已经在 Views 语义标签仓库中有一个新的开发分支,我们将能够向项目中添加一个开发版本。在 Views 语义标签模块的 视图 页面上,有一个位于 项目信息 下的 添加新版本 链接。点击该链接将带我们到 创建项目版本 页面。请看这个截图:
![操作时间 – 将 Views 语义模块提升为 Drupal.org 上的完整项目]()
-
创建版本 页面列出了两个 Git 发布分支以供选择,即我们刚刚创建的
8.x.1.x和8.x.2.x分支。因此,我们只需选择 8.x-2.x (8.x-2.x-dev) 并点击 下一步 按钮:![操作时间 – 将 Views 语义模块提升为 Drupal.org 上的完整项目]()
-
在下一页,我们将输入一些 发布说明 并点击 保存 按钮;其他字段已经填写,因为这是第一个开发版本。
![操作时间 – 将 Views 语义模块提升为 Drupal.org 上的完整项目]()
-
我们已成功将 Views 语义标签模块提升为完整项目状态,并创建了一个初始开发版本。
![操作时间 – 将 Views 语义模块提升为 Drupal.org 上的完整项目]()
-
Drupal 将自动生成
tar.gz和.zip文件并将它们附加到项目中,但开发版本可能需要 1-2 个小时(官方非开发版本在 5 分钟内发布)。在此期间,在 Views 语义标签的发布页面只会出现一个未发布的版本节点。此外,我们可以使用 Drush 通过以下命令下载并启用模块:$ drush dl views_semantic_tabs
发生了什么?
我们从模块文档指南中了解了一些关于 Drupal 的 README.txt 文件的内容。我们还学习了如何将一个模块从沙盒项目提升为完整项目,以及如何发布一个开发版本到项目中。
介绍 Features 模块
Drupal 8 的 Features 模块使我们能够捕捉和管理一组 Drupal 实体,即功能。Features 模块将通过提供可导出和捆绑的 UI 和 API,从模块中获取不同的网站构建组件,并将它们捆绑成一个单独的模块。在常规术语中,一个功能可能是一个博客、一个页面或一个图片库。因此,在下一个主题中,我们将学习如何使用 Features 模块。我们将导出我们的 Recipe 内容类型并在另一个环境中使用它。
行动时间——安装 Features 模块
我们将使用 Drush 下载并安装 Features 模块。同时,我们还将安装 features_ui 模块作为其包的一部分:
-
打开终端(Mac OS X)或命令提示符(Windows)应用程序,并切换到我们的 d8dev 网站的根目录。
-
使用 Drush 下载并启用 Features 模块:
$ drush dl features Project features (8.x-3.0-alpha6) downloaded to /var/www/html/d8dev/modules/features. [success] Project features contains 2 modules: features_ui, features. $ drush en features The following projects have unmet dependencies: [ok] features requires config_update Would you like to download them? (y/n): y Project config_update (8.x-1.0) downloaded to /var/www/html/drupal8_3_new//modules/config_update. [success] Project config_update contains 2 modules: config_update_ui, config_update. The following extensions will be enabled: features, config_update Do you really want to continue? (y/n): y config_update was enabled successfully. [ok] features was enabled successfully. [ok] $ drush en features_ui The following extensions will be enabled: features_ui Do you really want to continue? (y/n): y features_ui was enabled successfully.
发生了什么?
我们已启用 Features 模块。它由两个模块组成:基础 Features 模块和 features_ui 模块。Features 模块依赖于 config_update 模块,该模块在启用 Features 模块后自动下载。我们还启用了 features_ui 模块,它提供了一个易于创建功能的用户界面。
Features 模块中的 Recipe 功能
我们将通过导出我们的 Recipe 内容类型和相关配置(如——Recipe 内容类型的字段、表单显示、表单视图显示、视图——前导横幅、随机最高评分的食谱、最高评分的食谱,以及块——顶级食谱、前导横幅)来学习如何使用 Features 模块。然后我们将探讨如何在其他环境中使用它。
行动时间——使用 Features 模块导出 Recipe 内容类型和相关配置
我们将导出我们的 Recipe 内容类型作为一个功能模块,并将其相关配置作为导出模块的一部分导出:
-
在浏览器中打开 d8dev 网站,点击 Admin 工具栏中的 Configuration 链接,然后在 DEVELOPMENT 部分下点击 Features 链接。我们可以看到如下截图所示的下一页:
![使用 Features 模块导出 Recipe 内容类型和相关配置的行动时间]()
-
接下来,点击 DESCRIPTION 列下的 Included configuration 链接,以查看 Recipe 功能。
![使用 Features 模块导出 Recipe 内容类型和相关配置的行动时间]()
-
在这里,我们可以了解此功能包含哪些配置。它有两个块(顶部食谱和前导横幅),三个视图以及其他我们寻找的字段相关配置。但我注意到食谱按菜系块缺失,因此我们将在下一步添加这个缺失的组件。
-
现在点击食谱链接功能。在下一页,我们可以看到该功能的组件列表和一般信息:
![使用功能模块导出食谱内容类型和相关配置的时刻]()
-
在组件部分下面,搜索
recipes。我们注意到在块部分下有食谱按菜系:块 2(views_block__recipes_by_cuisine_block_2)可见,并勾选该复选框:![使用功能模块导出食谱内容类型和相关配置的时刻]()
-
此外,在视图部分下,勾选食谱按菜系(recipes_by_cuisine)复选框。
-
在一般信息部分,保留所有值默认,并将
8.x-1.0输入为版本字段。点击下载存档按钮:![使用功能模块导出食谱内容类型和相关配置的时刻]()
-
我们可以看到
Recipe.tar.gz已经下载。现在我们将这个文件提取到我们的d8dev/module目录下。打开 PhpStorm,查看以下截图所示的.yml文件和几个目录:![使用功能模块导出食谱内容类型和相关配置的时刻]()
-
我们可以看到有许多 YML 文件。所有这些文件都是视图、字段和食谱内容类型的配置文件。还有一个
info.yml文件,因此我们可以将其用作模块。我们可以在任何已安装 Drupal 8 并安装了views_semantic和其他模块的其他环境中使用此模块。按照第一章,设置 Drupal 开发环境,安装另一个 d8dev 站点。
发生了什么?
我们启用了功能模块,并了解了如何导出食谱内容类型及其字段。我们还了解了如何导出与食谱功能一起导出的视图和块。
与功能相比何时使用核心配置管理
在 Drupal 7 核心中,没有管理系统配置的系统,功能模块可以将配置数据作为代码打包导出和导入。功能模块用于配置管理和部署。由于以下问题,该模块并不真正适合:
-
导出配置的结构没有一致性
-
它覆盖并撤销内容管理器的修改
Drupal 8 引入了配置管理系统,可以处理此类问题,但这并不意味着我们不再需要 Features 模块。配置管理系统不适合将捆绑功能导出到其他环境、网站、客户或项目中。这就是我们仍然需要 Features 模块的原因。Drupal 8 的 Features 将返回其捆绑功能(如博客或图片库),而不仅仅是管理配置。此外,Features 允许我们以简化的方式选择和添加我们想要捆绑到包中的配置数据。此外,在开发过程中更新模块中存储的配置要容易得多。配置管理也存在一些问题:
-
将配置添加到模块是一个手动过程,意味着复制/粘贴 YML 数据。
-
模块仅提供初始配置文件。如果我们想更改配置文件,我们需要通过编写更新钩子来完成。
-
如果我们卸载一个模块,它不会删除所有配置。并且 Drupal 8 核心无法启用已存在配置的模块。
我的结论是,Features 模块在 Drupal 8 中仍然非常重要。该模块可以被标记为一个开发者模块,旨在帮助 Drupal 开发者在日常工作中打包功能,并在开发过程中轻松导入更改。
摘要
在本章中,我们更深入地了解了 Views 模块,并看到了一个优秀的 Views 插件以及一些自定义 CSS 如何使我们能够为 d8dev 网站创建一个非常吸引人的旋转横幅组件。
我们还了解了一些关于 Drupal README.txt 文件的信息,以及一个模块是如何从沙盒项目提升为完整项目的。
最后,我们学习了如何使用 Features 模块导出 Recipe 内容类型及其字段、块和视图作为模块,以便在其他环境中使用。我们还通过比较研究和确定何时使用这两个模块之一,来比较了配置管理系统与 Features 模块。
在下一章中,我们将介绍一些高级搜索概念,并指导您完成基于 Java 的 Apache Solr 搜索引擎的安装和 Drupal 集成。然后,我们将使用基于 Search API 模块的自定义分面搜索来增强我们的网站。
第十一章. 使用 Search API 模块在您的网站上搜索
在本章中,我们将探讨一个更强大、更灵活的搜索模块替代品,该模块包含在 Drupal 核心中。我们将介绍 Apache Solr 搜索服务器的安装,并探讨增强用户搜索体验的方法。
在本章中,我们将学习:
-
如何根据您的需求选择合适的搜索配置
-
安装和配置 Search API 模块以替换 Drupal 核心搜索
-
使用两种不同的方法安装和配置 Apache Solr 搜索服务器
-
使用 Search API 模块设置搜索服务器和搜索索引
-
创建自定义的细分搜索块以增强用户的搜索体验
Drupal 核心搜索
与 Drupal 7 类似,Drupal 8 核心自带内置的搜索模块。通过直接查询 Drupal 数据库,它提供了可用的搜索功能。Drupal 核心搜索允许您搜索用户资料和节点内容。它定期查询数据库以维护内容索引,无需任何先前的配置。
如果您只需要基本搜索功能且网站流量不大,Drupal 搜索可能适合您的需求。然而,它有几个功能限制,包括以下内容:
-
它使用 Drupal 数据库来执行其查询,这可能会给数据库增加额外的负载,而数据库可能已经是您网站上性能瓶颈的一部分
-
无法控制哪些节点被索引以供搜索
-
内容始终根据每个节点的默认显示模式进行索引
-
搜索只匹配节点的完整关键词
如果您介意这些限制中的任何一项,您应该切换到使用 Search API 模块。
除了允许对内容搜索和索引方式有更多的控制外,Search API 模块还提供了通过集成第三方搜索引擎来减轻 Drupal 数据库负载的选项。
如果您网站或其流量增加的复杂性已经达到数据库性能成为瓶颈的程度,您需要使用 Search API 模块与第三方搜索引擎结合使用,以便让 Drupal 数据库专注于其他事情。Apache Solr 是我们将在本章中使用的第三方搜索引擎的一个例子。
Search API 模块
Search API 模块(www.drupal.org/project/search_api)用可以与不同类型的底层搜索引擎一起使用的框架替换了 Drupal 核心搜索。搜索引擎作为独立于 Drupal 网站及其数据库的第三方存在。
托马斯·赛德尔(也被称为“醉酒的猴子”)是 Search API 模块的主要贡献者之一。这是他对其起源的解释:
"搜索 API 是在 2010 年创建的。我参与了有关如何改进 Drupal 核心搜索模块并将其转变为 Drupal 8 框架的讨论。问题是 Drupal 中没有搜索框架,所有与搜索相关的模块都必须反复包含相同的样板代码。我取了那次讨论的最好部分,并将其转化为 Drupal 7 的贡献模块和搜索框架。"
搜索 API 模块可以通过其他贡献的模块进行扩展,以提供非常强大的搜索功能。搜索 API 还与视图模块集成,这使得您可以根据用户输入的搜索词创建视图列表页面。
我们稍后将要使用的 Apache Solr 搜索引擎是开源的、快速的,并且具有许多功能。然而,在不使用搜索引擎的情况下,我们也有继续使用搜索 API 模块查询 Drupal 数据库的选项。这有一个优点,即我们可以根据搜索 API 设置任何所需的复杂配置,而无需在我们的开发环境中安装搜索引擎。然后,当我们将网站部署到生产环境时,我们可以切换到使用搜索引擎,并且最小化重新配置。
我们将首先设置搜索 API 模块以使用 Drupal 数据库进行搜索。这将使我们能够探索搜索 API 模块提供的一些功能和概念。稍后,我们将切换到使用 Apache Solr 搜索引擎与搜索 API,并探索一些更强大的搜索功能。
行动时间 - 搜索 API 模块的基本安装和配置
在以下步骤中,我们将下载、安装和配置搜索 API 模块:
-
首先,使用您习惯的方法下载
search_api模块。如果您已安装 Drush,如第五章中“移动优先,响应式设计”部分所述,您将能够在命令行中键入以下内容:$ drush dl search_api $ drush en search_api -
在我们继续之前,我们应该遵循 Drupal 的建议卸载被搜索 API 模块替换的核心搜索模块。
![行动时间 - 搜索 API 模块的基本安装和配置]()
如果您通过 Drush 安装模块,您将通过命令行消息得到通知,或者该消息将出现在报告 | 状态报告的状态报告页面上。禁用核心搜索模块将删除搜索块以及内容和用户的搜索页面。不要慌张,我们将替换它们。要使用 drush 安装核心搜索模块,请键入:
$ drush pm-uninstall search -
搜索 API 本身并不做任何事情——我们需要给它一个搜索引擎来交互。为此,我们将使用一个子模块,您将作为
search_api的一部分下载,称为search_api_db。现在使用您习惯的方法启用它。search_api_db模块允许搜索 API 直接与 Drupal 数据库交互。这具有设置简单、易于配置的优点,在小型网站上,如果数据库本身没有显著的负载,您可以在生产服务器上使用这种方法进行搜索。 -
search_api_db模块包含一个非常实用的子模块,该子模块设置了一些合理的默认配置。然后您可以使用这个配置作为您搜索设置的起点。为了利用这一点,启用search_api_db_defaults,然后再次禁用它。所有配置都是在模块最初启用时发生的,并在您禁用模块时保持不变。 -
如果您现在导航到配置 | 搜索和元数据 | 搜索 API,您应该会看到一个类似于以下屏幕的界面:
![执行时间 – 搜索 API 模块的基本安装和配置]()
刚才发生了什么?
您下载并安装了搜索 API 模块,并使用其子模块之一安装了一些默认配置。现在我们可以看到这个配置包括一个搜索服务器和一个搜索索引,我们将探讨这意味着什么。
在接下来的几节中,我们将回顾由search_api_db_defaults模块设置的默认设置的一些方面。请注意,我们使用的一些设置和屏幕布局可能在后续版本的模块中发生变化。我们不会涵盖所有设置,但希望足以让您开始根据自己的需求定制设置。
搜索服务器和搜索索引的解释
在 Drupal 术语中,搜索服务器定义了可搜索数据的索引方式。这包括在您的 Drupal 网站上如何查询它以及搜索索引是如何存储的。这些事情是由您为搜索服务器选择的后端决定的。在本章中,我们将探讨使用 Drupal 数据库作为后端和 Apache Solr 搜索引擎作为后端的搜索服务器。然而,您还可以使用其他后端,这将定义您的数据如何被查询和索引的其他方式。
搜索索引定义了索引过程。搜索索引与一个之前配置好的用于提供动力的搜索服务器相关联。搜索索引设置决定了诸如在您的网站上哪些内容被索引、哪些字段被索引以及数据在搜索前后处理的各种方式。
通过将配置拆分为搜索服务器和搜索索引,可以在保持数据查询和索引方式的同时更换执行工作的搜索引擎。这有点类似于能够在保持车辆外观和驾驶感受不变的情况下,将您的汽车汽油发动机更换为柴油发动机。
还有可能有多个搜索服务器,并选择在网站开发中的哪个阶段或你正在工作的哪个环境中使用最合适的搜索服务器。
为了进一步扩展搜索配置,也可以有多个搜索索引。想象一下,未来的某个时刻,你的食谱网站如此受欢迎,以至于你开始在网站上销售你的食谱书籍。你可能希望有一个单独的搜索索引,只包含书籍的详细信息,这样当用户在单独的搜索框中输入印度时,他们只会在搜索结果中看到与印度烹饪相关的烹饪书籍。你的两个搜索索引仍然可以基于具有 Apache Solr 后端的搜索服务器。
搜索服务器
在前一步配置了搜索 API 模块后,你将在搜索 API 配置表中看到第一个列出的是名为数据库服务器的搜索服务器。正如其名所示,这是一个基于 Drupal 数据库作为后端的服务器。
在操作列中点击编辑,你将被带到数据库服务器配置:

服务器配置相当直观。服务器有一个名称,并且应该通过复选框启用。它还有一个后端,在这个例子中是数据库,由我们的search_api_db模块提供。你还可以设置用于搜索的单词的最小字母数。
接下来,我们将查看默认内容索引的配置。
搜索索引
我们现在需要我们的搜索索引设置。搜索索引的配置也在搜索 API 页面的配置 | 搜索和元数据 | 搜索 API。点击默认内容索引旁边的编辑,以查看配置页面:

你会看到有多个数据源可供索引。我们不仅仅限于用户和节点内容,就像我们在 Drupal 核心搜索中做的那样。目前我们只索引内容,因为默认配置中已经选择了它。
你还会注意到搜索索引是通过单选按钮选择与搜索服务器链接的。在这种情况下,服务器字段设置为数据库服务器,因为我们目前只设置了这一个。
字段
然后,转到页面顶部的字段标签页。这是我们设置每个数据源上哪些字段实际上会被索引的地方:

在常规表中,你会看到项目语言和渲染项列出。这些是所有可搜索数据源共有的属性。渲染项字段允许你根据实体在 Drupal 中渲染的样子进行全文搜索。
在页面底部的内容表中,你会看到节点共有的字段。对于字段配置页面上的每个字段,你可以设置提升值,这控制了在排序搜索结果时,该字段中的搜索匹配项的相对重要性。
处理器
处理器在搜索和索引过程中的不同点对数据或查询进行操作,并做出更改。进程有自己的标签页,可以在其中进行配置。

在页面中间,你可以看到处理器顺序表,它允许你控制处理器运行的顺序:

例如,渲染项处理器,它将整个要搜索的实体组装起来,必须在数据被索引之前运行。像高亮显示这样的处理器,它将显示在搜索结果上以显示你搜索的术语,是在查询执行之后发生的,所以你会注意到这一点位于后处理查询部分。
如果你正在修改处理器,请特别注意页面底部的垂直标签页中的处理器设置。这是配置每个处理器确切行为的地方。如果启用的处理器没有按照你预期的行为,那么这些设置应该是你的首要考虑。

填充你的搜索索引
现在我们已经设置了我们的搜索后端和搜索索引,我们需要使用它们来索引我们网站上的内容。
可以通过点击默认内容索引视图标签页底部的立即索引按钮来执行内容的批量索引。当你内容被索引时,你会看到一个蓝色进度条来显示进度,然后你会返回到内容索引视图标签页。
在cron运行期间也可以索引内容。触发cron运行的一种方法是通过drush命令:
$ drush cron
每次运行cron时索引的内容节点数默认为 50。如果你有 50 个或更少的内容项,蓝色进度条应该在一次cron运行后显示 100%。如果你有更多内容,你可能需要运行几次cron才能全部索引。你可以在搜索设置的编辑标签页上调整每次cron运行要索引的节点数(即cron批量大小)。滚动到页面底部并展开索引选项,你将能够调整此值:

无论你是运行了cron还是点击了立即索引按钮,你的内容现在应该已经被索引。
你会注意到在内容索引视图标签页的底部有一些链接,可以提供一些额外的控制来管理你的内容索引:
-
将所有项目排队以重新索引将当前索引的内容项标记为准备好在下次
cron运行或您点击立即索引时再次索引。在内容重新索引被触发之前,当前搜索索引将继续被使用。这有助于确保用户在内容必须重新索引时也能不间断地进行搜索。 -
清除所有索引数据将当前搜索索引立即变为不可用,并且搜索将停止产生结果,直到内容被重新索引。
向用户展示搜索
因此,我们现在有一个包含内容的搜索索引,但如何实际上允许最终用户在我们的 Drupal 网站上执行搜索呢?
搜索 API 的一个特性是它与视图模块很好地集成。这意味着您可以使用您设置的搜索服务器搜索内容,并在视图中显示它。您之前使用的search_api_db_defaults模块提供了一些视图配置,实现了配置的搜索索引和搜索服务器。
要查看此视图,请访问您网站上的/search/content。尝试搜索特定单词的内容,并检查您是否得到合理的结果。您应该会发现,如果您有两个内容项,其中一个标题中有您的搜索关键字,另一个在正文文本中有,搜索结果将根据您在之前搜索索引设置部分的字段选项卡中设置的增强值进行排序。默认情况下,标题字段的增强值为八,而渲染项的增强值为一。因此,标题中包含您的关键字的 内容应该出现在正文或其它地方包含您的关键字的 内容之前。
为了更好地复制我们之前为了使用搜索 API 而禁用的 Drupal 核心搜索功能,有一个可以显示在全站范围内的搜索字段会很好,这样用户就不需要导航到/search/content来执行搜索。
为了实现这一点,我们将以块的形式显示搜索表单。导航到结构 | 视图,然后点击搜索内容视图旁边的编辑按钮。展开高级部分,将块中显示表单从否切换到是。点击保存按钮:

出现在我们新搜索结果页面顶部的暴露搜索字段现在可以在一个我们可以配置以出现在我们网站页面上的块中。转到结构 | 块布局,然后点击您想要放置该块的区域的放置块旁边的按钮。在出现的列表中,您将看到块暴露表单:search_content-page_1。以通常的方式配置块设置:

您现在应该有一个全站搜索块,它替换了我们之前移除的核心搜索块。
修改搜索显示
搜索 API 的一个优点是,你可以通过内容类型显示设置来控制搜索结果中每个项目的显示方式。你会注意到搜索内容视图使用的是搜索结果高亮输入的节点显示模式。

这是一个由search_api_db_defaults提供的节点显示模式。你可以从每个内容类型的设置中更改此显示模式的设置。
前往结构 | 内容类型,选择你想要更改显示设置的特定内容类型,然后转到标签管理显示 | 搜索结果高亮输入:

尝试更改在搜索结果中显示的内容类型的字段。例如,尝试隐藏图片并将正文格式设置为截断,截断限制为 400 个字符。
你现在应该得到类似以下截图的搜索结果:

排除实体不被索引
到目前为止,我们已设置的配置将使你网站上的所有节点都被索引并可搜索。如果我们想选择是否将我们在网站上创建的个别菜谱显示在搜索结果中,会怎样呢?也许我们想创建指向特殊菜谱的促销链接,这些菜谱无法通过网站搜索获得。
嗯,有一个名为 Search API Exclude Entity 的模块可以做到这一点(www.drupal.org/project/search_api_exclude_entity)。当你下载并安装它时,你会发现有额外的字段类型可以添加到你的节点和其他实体中。如果你将此字段添加到你的菜谱内容类型中,那么当你下次创建或编辑菜谱时,你会在菜谱编辑屏幕上注意到一些额外的元数据选项:

接下来,前往你的搜索索引的处理器配置:配置 | 搜索和元数据 | 搜索 API。点击默认内容索引旁边的编辑,查看配置页面,然后选择处理器标签。你会看到有一个新的搜索 API 排除实体可以启用。一旦启用了此处理器,滚动到屏幕底部的处理器设置部分,并检查你刚刚添加到内容类型中的字段名称旁边的复选框。
尝试创建一个新的菜谱并将其排除在搜索之外。检查所有内容是否已被索引,然后搜索你的新菜谱。你应该会发现它没有出现在搜索结果中。
安装 Apache Solr 作为搜索后端
使用搜索 API 而不是核心搜索模块的主要原因是我们可以插入不同的搜索引擎并使用它们来索引你的内容。当我们开始处理包含大量内容的网站时,这确实成为一种优势。
Apache Solr 是一个开源搜索引擎,被许多知名企业级网站使用。它不是 Drupal 或 Drupal 模块的一部分,并且通常独立于 Drupal 使用。然而,它与 Drupal 搜索 API 模块很好地集成,正如我们将看到的。
与使用数据库的搜索 API 相比,Solr 具有以下优势:
-
它更快
-
它不依赖于你的 Drupal 数据库,从而为 Drupal 释放数据库资源以完成其余工作
-
它可以托管在独立于你的数据库和代码库的服务器上,这在需要扩展你的基础设施时非常有用。
-
你可以搜索短语并使用通配符
我们将在本章后面检查这些相同的默认设置。同时,如果你想了解更多关于 Apache Solr 项目的信息,请访问其网站 lucene.apache.org/solr/。
安装 Solr 有几种不同的方法。在这里,我们将解释两种方法,一种使用虚拟机和 puppet 脚本,另一种更手动的方法使用 Ubuntu 14.04 服务器。
注意
如果你遵循这两种方法中的任何一种,Apache Solr 默认不会阻止未经授权的用户访问你的 Solr 服务器。如果你在本地机器之外设置 Solr 服务器,或者可以通过互联网连接,那么使用某种方法来保护 Apache Solr 非常重要。否则,未经授权的用户将能够进行搜索查询,甚至可能以其他方式损害你的网站。请参阅本章的 使用 Uncomplicated Firewall 保护 Apache Solr 部分,了解如何保护 Solr。
在使用 Vagrant 和 Puppet 的虚拟机上安装 Solr 4.x
这是一个快速简单的方法来设置一个可丢弃的虚拟机,你可以用于本地开发目的。它将表现得与真实服务器上的 Solr 实例完全相同,你可以稍后设置它。
在第一章中介绍了用于生成 Puppet 部署配置的有用在线服务,设置 Drupal 开发环境。这个工具叫做 PuPHPet——中间的 PHP 字母大写以表明它主要用于 PHP 网络开发。在撰写本文时,PuPHPet 上可用的 Apache Solr 最新版本是 4.10.*。因此,以下说明将假设你正在使用 Solr 的 4.x 版本。
如果你真的需要 Solr 的最新和最佳版本,你可以跳到下一节,我们将解释如何在 Ubuntu 14.04 服务器上手动安装 Solr。
动手时间 - 创建和配置你的虚拟机
以下步骤将使用在线服务创建一个你可以使用 Vagrant 在本地启动虚拟机的 Puppet 脚本。Puppet、Vagrant 和 PuPHPet 网站在本书的第一章中介绍,设置 Drupal 开发环境。如果你还没有在本地设置 Puppet 和 Vagrant,请按照那里的说明来设置它们。
-
在 PuPHPet 上创建你的虚拟机。将你的浏览器指向
puphpet.com/并点击那个大绿色的立即开始按钮:![操作时间 – 创建和配置你的虚拟机]()
在你将被引导的向导过程中,你通常可以接受默认选项。只需确保当你到达搜索服务器部分时,你选择安装最新的 4.*版本的 Apache Solr。我们还将快速查看这里的一些其他选项,以创建一个干净、可重复使用的虚拟机。
在点击开始按钮后的向导的第一页,确保你已经勾选了部署到本地主机,并且选择 VirtualBox 作为提供者。我们还将选择 Ubuntu Trusty 14.04 LTS x64 作为发行版:
![操作时间 – 创建和配置你的虚拟机]()
如果你继续向下滚动这个页面,你会找到设置内部标识符、主机名和IP 地址的选项。如果你已经运行了其他虚拟机并且需要区分它们,你可能想要更改这些设置。
![操作时间 – 创建和配置你的虚拟机]()
同样重要的是,你有一个文件夹,它将在你的物理主机机器和你创建的虚拟机之间共享。默认情况下,这是为你配置好的。
使用屏幕底部的绿色大按钮遵循向导,并接受系统部分的其余默认设置。
当你到达Web 服务器部分时,你可以取消选择 Apache 和 Nginx(Apache Solr 不需要它们中的任何一个)。在语言和数据库部分的所有选项也可以取消选择——Solr 不需要这些中的任何一个。
继续通过向导前进,继续接受进一步的默认设置,直到你到达搜索服务器。选择Apache Solr并从下拉框中选择最新版本:
![操作时间 – 创建和配置你的虚拟机]()
-
下载并设置你的虚拟机。现在你可以点击创建存档按钮下载一个包含你刚刚设置的 Puppet 配置的 ZIP 文件。
如前所述,在 第一章 “设置 Drupal 开发环境”中,您现在可以通过解压 ZIP 存档,在命令行中导航到解压的文件夹(其中将包含一个名为
Vagrantfile的文件),并键入以下命令来运行您的虚拟机:$ vagrant up第一次启动虚拟机时需要一些时间,因为它需要下载包含操作系统的大型磁盘镜像。在此过程中,您应该在命令行中收到一些详细的输出,告知您正在发生的事情。
如果一切运行正确,您现在应该能够打开浏览器并导航到
http://localhost:8983/solr。如果不起作用,请通过滚动回您的命令行输出并检查哪个本地端口已转发到8983(Apache Solr 端口)来检查端口号是否正确。您现在应该在浏览器中看到以下内容:
![行动时间 – 创建和配置您的虚拟机]()
-
安装 Drupal 架构。Apache Solr 现已安装到您的虚拟机中,您可以通过浏览器连接到它。恭喜!
但除非我们给它一些要索引和搜索的内容,否则它将没有太大用处。我们需要将其连接到我们的 Drupal 网站。完成此操作的第一步是安装正确的配置文件,这些文件告诉 Solr 如何索引 Drupal 内容。
下载并解压 Drupal 8 的
search_api_solr模块。将其放入您网站的/modules文件夹中,但暂时不要启用它。Vagrant Solr 实例所需的配置文件可以在 Drupal 的search_api_solr模块中的search_api_solr/solr-conf/4.x找到。要将这些文件复制到您的虚拟机中,请将
4.x文件夹复制到之前提取的 Vagrantfile 所在的puphpet文件夹中。接下来,通过导航到包含 Vagrantfile 的文件夹并在其中键入以下命令来 SSH 进入您的虚拟机:
$ vagrant ssh您将通过 SSH 登录到您的虚拟机,就像它是一个远程服务器一样。
您复制的配置文件所在的
puphpet文件夹在物理主机机器和虚拟机之间是共享的。您可以在虚拟机上的/var/www/puphpet/4.x找到4.x文件夹。首先,备份原始 Solr 配置,然后将新配置移动到您的 Solr 实例中的/opt/solr/solr-4.10.2/example/solr/collection1/conf:$ sudo cp -r /opt/solr/solr-4.10.2/example/solr/collection1/conf /opt/solr/solr-4.10.2/example/solr/collection1/conf-orig $ mv /var/www/puphpet/4.x/* /opt/solr/solr-4.10.2/example/solr/collection1/conf/现在,我们将重新启动 Solr。一个简单的方法是直接重新启动整个虚拟机。
接下来,退出 SSH 终端:
$ exit然后停止虚拟机:
$ vagrant halt然后再次启动它:
$ vagrant up通过在浏览器中再次访问 Solr 管理页面来检查一切是否运行正确。
![行动时间 – 创建和配置您的虚拟机]()
从侧边栏的下拉菜单中选择 collection1,然后点击侧边栏底部的 Schema Browser。在随后出现在页面主要内容区域的下拉菜单中,您应该会看到一些与 Drupal 相关的 字段、bundle_name、entity_id、entity_type 等等。
接下来,您需要在 Drupal 中启用和配置
seach_api_solr模块。要执行此操作,请跳转到“搜索 API Solr 模块”部分。
刚才发生了什么?
您使用 Vagrant 和 Puppet 来设置运行 Apache Solr 的虚拟机。然后,您通过安装随 Drupal 搜索 API Solr 模块提供的配置文件来配置 Solr 服务器以与 Drupal 一起工作。当您在 Apache Solr 上进行实验和开发时,此虚拟机配置可以反复使用。
在 Ubuntu 14.04 上手动安装 Solr 5.x
如果以下任何一项适用,请使用此方法:
-
您希望拥有比 PuPHPet 上可用的 Apache Solr 更新的版本
-
您需要在本地机器之外的服务器上运行 Solr
-
您希望在安装过程中有更多的控制权
这些说明将假设您正在 Ubuntu 4.x 箱子上安装 Solr 5.x。这可能就是您的网站正在运行的服务器,或者是一个专门用于 Solr 的服务器。
Ubuntu 软件仓库中可用的 Solr 版本比当前版本低一个主要版本。我们不会简单地使用 Ubuntu 的 apt 软件包管理工具来安装早期版本,而是会遵循一种允许我们安装最新 5.x 版本 Solr 的方法。
是时候采取行动——在 Ubuntu 上安装和配置 Solr
首先,我们将安装一些必需的软件包,然后从 Apache Solr 网站手动安装最新版本的 Solr:
-
安装必需的软件包。首先,使用
apt-get确保您有python-software-properties软件包可用。这将允许您添加一个新的软件包仓库:$ sudo apt-get install python-software-properties现在,添加非官方的 Java 安装程序仓库:
$ sudo add-apt-repository ppa:webupd8team/java当提示时,确认您想要添加此仓库,然后更新源软件包列表:
$ sudo apt-get update我们现在可以安装 Oracle Java JDK 版本 8:
$ sudo apt-get install oracle-java8-installer这是一个 Oracle 软件包,您必须同意许可条款才能继续。当提示时,只需按 Enter 键。
-
运行 Apache Solr 安装程序。现在我们已经安装了 Java JDK,我们可以安装 Apache Solr 的最新版本。转到可从
www.apache.org/dyn/closer.lua/lucene/solr/下载 Solr 的镜像列表。选择一个镜像,然后选择以
5.*开头的最新版本号目录。接下来,右键单击最新版本的
.tgz文件(文件名中不包含src),并复制其链接。在撰写本文时,这是solr-5.5.0.tgz,下面的命令中的版本号将替换为您下载的版本。回到您正在安装 Solr 的服务器的命令行,将文件下载到您的家目录,并解压它:
$ cd ~ $ wget http://<download_path>/solr-5.5.0.tgz $ tar -xvzf solr-5.5.0.tgzSolr 5.x 附带自己的安装脚本,这使得它比以前的版本更容易设置。我们将按以下方式运行脚本:
$ sudo bash solr-5.5.0/bin/install_solr_service.sh solr-5.5.0.tgz当它启动时,Solr 将输出它正在运行的端口号,通常是
8983。打开浏览器窗口,导航到服务器的 IP 地址后跟端口号。例如:http://<服务器 IP 地址>:8983。您应该会看到以下 Solr 管理界面:
![是时候采取行动——在 Ubuntu 上安装和配置 Solr]()
注意
确保您特别注意本章的以下部分 使用简单防火墙保护 Apache Solr,以确保没有未经授权的访问您的 Solr 接口。
-
安装 Drupal 架构。Apache Solr 现已安装到您的虚拟机中,您可以通过浏览器连接到它。恭喜!
然而,除非我们给它一些内容来索引和搜索,否则它将没有太大用处。我们需要将其连接到我们的 Drupal 网站。完成此操作的第一步是安装正确的配置文件,这些文件告诉 Solr 如何索引 Drupal 内容。
使 Solr 与 Drupal 一起工作的所需配置文件可以在
search_api_solr模块中找到。导航到 Drupal 上的模块页面以获取最新打包的tar.gz文件的链接。将其下载到您的 Apache Solr 服务器并解压文件:$ cd ~ $ wget https://ftp.drupal.org/files/projects/search_api_solr-8.x-1.0-alpha2.tar.gz $ tar -xvf search_api_solr-8.x-1.0-alpha2.tar.gz您将需要将前面命令中的文件名替换为模块的最新版本文件名。
接下来,创建一个目录,您将复制 Solr 配置文件到该目录:
$ sudo mkdir -p /var/solr/data/drupal/conf将 Drupal 模块中的文件复制到 Solr 配置目录:
$ sudo cp search_api_solr/solr-conf/5.x/* /var/solr/data/drupal/conf/设置文件的正确所有权:
$ sudo chown -R solr:solr /var/solr /opt/solr创建一个新的 Solr 核心库:
$ sudo /opt/solr/bin/solr create -c drupal重新启动 Solr 服务:
$ sudo service solr restart再次在浏览器中导航到 Solr 管理界面。在侧边栏的 核心选择器 下拉菜单中,您现在应该有一个名为 drupal 的核心:
![是时候采取行动——在 Ubuntu 上安装和配置 Solr]()
发生了什么?
您已使用包管理器在 Ubuntu 上安装了 Apache Solr 的最新版本。这是您在将 Solr 从头开始在生产服务器上设置时将使用的流程。只需确保您也保护它即可!
使用简单防火墙保护 Apache Solr
保护 Apache Solr 非常重要,以防止外部访问。如果您的 Solr 服务器可以通过互联网或甚至通过您的本地网络访问,您应该采用一种控制对它的未经授权访问的方法。
是时候采取行动——配置简单防火墙
在 Ubuntu 上,您可以使用内置的简单防火墙工具在防火墙级别设置对服务器的访问权限:
-
首先拒绝所有访问:
$ sudo ufw default deny然而,我们不想打断我们目前正在使用的 SSH 连接,所以请确保我们允许 SSH 连接的访问:
$ sudo ufw allow ssh此外,我们希望允许来自我们本地 IP 地址的所有连接:
$ sudo ufw allow from <ip address>在这里,
<IP 地址>是您连接的机器的 IP 地址。如果您要连接的 Drupal 服务器的 IP 地址与 Solr 服务器不同,您还需要在这里添加 Drupal 服务器的 IP 地址。只需再次运行前面的命令,将 IP 地址更改为您的 Drupal 服务器。有关更多详细信息,请参阅 Ubuntu UFW 文档:help.ubuntu.com/community/UFW。 -
现在启用防火墙,使用以下设置:
$ sudo ufw enable您可能会收到一个警告,表示现有的 SSH 命令可能会被中断。只要您已经使用我们之前运行的命令允许了 SSH 连接的访问,您就可以继续操作。
-
现在检查您的防火墙状态:
$ sudo ufw status -
您应该会收到如下所示的输出:
Status: active To Action From -- ------ ---- 22 ALLOW Anywhere Anywhere ALLOW <ip address> 22 (v6) ALLOW Anywhere (v6)
现在,你应该能够从您的本地机器或网络访问 Solr 管理界面,但不能从其他任何地方访问。尝试导航到以下 URL:http://<服务器 IP 地址>:8983。
刚才发生了什么?
您已配置 Ubuntu 的简单防火墙,仅允许从特定的 IP 地址访问您的 Solr 安装。
搜索 API Solr 模块
在 Drupal 8 之前,Drupal 使用一个独立于搜索 API 的模块来与 Apache Solr 集成。该模块被称为 Apache Solr 搜索。然而,决定将搜索 API 作为 Drupal 8 所有搜索集成的模块。Search API 项目和早期 Drupal 7 的 Apache Solr 搜索模块的主要贡献者是 Nick Veenhof(在 Drupal.org 上也被称为"Nick_vh")。他解释了将 Search API 模块作为新 Search API Solr 模块要求的理由:
"Drupal 8 还迫使我们重新思考某些概念,并履行我们避免在 Drupal 7 中 Apache Solr 模块和搜索 API 之间存在的分歧的承诺。Drupal 8 中的搜索 API 将作为 Apache Solr 集成的唯一提供者。Drupal 8 中的搜索 API 带有合理的默认设置(您可以选择启用或禁用),这些设置预先配置了模块,使其可以立即使用。"
行动时间 - 配置 Drupal 以使用 Apache Solr
无论您如何设置 Apache Solr,现在您都希望配置 Drupal 以使用您的新 Solr 服务器作为 Drupal 中的搜索服务器:
-
安装搜索 API Solr 模块。首先,我们需要在我们的 Drupal 网站上安装
search_api_solr模块。下载模块并将其解压到您的 Drupal 网站的/modules文件夹中,就像平常一样。该模块有一些由 composer 管理的依赖项,因此请导航到您的模块目录并运行:$ composer install在撰写本文时,为了修复在模块页面下载的官方 alpha 版本中显示的一些错误,需要使用
search_api_solr的开发版本。为了获取这个版本,最好使用 Git:$ git clone --branch 8.x-1.x https://git.drupal.org/project/search_api_solr.git -
配置搜索 API Solr 模块。当我们在本章早期安装
search_api_db_defaults模块时,它为我们创建了一个基于数据库的搜索服务器。现在我们已经安装了 Search API Solr 模块,我们可以根据 Solr 服务器设置第二个搜索服务器。
导航到配置 | 搜索和元数据 | 搜索 API,然后单击添加服务器按钮。
按照下面的示例填写表单。Solr 路径应该是
/solr/后跟您的核心名称。如果您按照之前的说明安装了 Solr 5.x,这将将是drupal;如果您按照安装 Solr 4.x 的说明操作,这将将是collection1:![操作时间 - 配置 Drupal 以使用 Apache Solr]()
如果您已经使用上一节中解释的防火墙规则保护了 Solr 管理界面,您将不需要为 HTTP 身份验证添加登录详细信息。留空并单击保存:
![操作时间 - 配置 Drupal 以使用 Apache Solr]()
现在您已经在 Drupal 配置中创建了第二个搜索服务器,Search API 模块的一个优点是您可以轻松地在它们之间切换。例如,您可能希望将您的 Solr 服务器专用于生产站点,但您仍然希望在预发布和开发站点上能够进行开发目的的搜索。
在开发和预发布环境中,您可以将搜索索引设置为使用数据库,而在生产环境中,您可以将它切换到使用 Solr。
编辑本章中较早设置的默认内容索引,并将服务器单选按钮更改为Solr:
![操作时间 - 配置 Drupal 以使用 Apache Solr]()
单击保存,然后重新索引内容。就是这样!返回您的搜索页面并测试新的搜索,这次是由 Solr 提供支持。
发生了什么?
您已安装了 Drupal 的 Search API Solr 模块,并将其配置为通过新的搜索服务器连接到您的 Apache Solr 安装。然后,您将搜索索引连接到新的搜索服务器,并使用 Solr 索引了内容。
使用只读模式
在某些情况下,您可能希望使搜索索引为只读模式。如果您在多个服务器上实施 Drupal 开发工作流程,这可能很有帮助。例如,当您将 Drupal 数据库从生产环境复制到您的预发布和开发环境以保持它们与生产站点的最新内容同步时。与其为每个环境创建一个新的 Apache Solr 服务器,您可能希望使用相同的 Apache Solr 服务器,但确保只有添加到您的生产服务器的内容被索引。
在您的预发布和生产环境中,您可以在索引选项中勾选只读复选框。这将防止您在此处创建的任何测试内容进入 Apache Solr 索引:

你应该注意,如果你向系统中添加新的内容类型,你需要确保搜索索引没有被设置为只读,以便内容类型可以被索引。当索引被设置为读写(即,取消选中只读复选框)时,使用你的新内容类型创建的任何新内容将像往常一样自动索引。
搜索分面
为了进一步展示搜索 API 的强大功能,我们将创建一个与搜索列表一起使用的分面块。你可能熟悉来自其他网站的分面块,这些网站有大量分类内容可供搜索和排序。
例如,以下是一些来自流行食谱网站的分面搜索块:

www.bbcgoodfood.com/search/recipes?query=family
行动时间 - 构建分面搜索块
让我们看看如何使用搜索 API 和 Drupal 8 分面模块构建这些分面搜索块之一:
-
按照你习惯的方式,从(
www.drupal.org/project/facets)下载并启用分面模块。 -
导航到配置 | 搜索 API | 分面,然后点击蓝色的添加分面按钮。作为一个例子,我们将创建一个使用我们食谱标题中的单词的分面块。然而,如果你已经使用分类法术语对食谱进行了分类,你也可以基于该字段创建你的分面块。
给分面一个可读的人名和一个机器名:
![行动时间 - 构建分面搜索块]()
-
分面块需要从一个源获取数据。在分面字段下拉菜单中,你将有一个选项,即我们之前创建的搜索视图。将分面字段设置为标题,并将分面的权重设置为
0,然后保存设置。 -
你也可以通过点击页面顶部的显示选项卡来配置分面的显示设置。在这里,你可以选择是否将你的分面列表显示为下拉菜单、复选框或链接列表。你还可以选择分面列表的行为方式,例如隐藏不会进一步缩小当前显示的搜索结果的项目。我们的目标是使我们的分面列表类似于本节开头的示例。因此,我们将选择链接列表作为小部件,并勾选隐藏非缩小结果:
![行动时间 - 构建分面搜索块]()
-
现在,你已经创建了一个与分面源相关的分面。你现在在配置 | 搜索 API | 分面下看到的分面表应该如下所示:
![行动时间 - 构建分面搜索块]()
-
你创建的分面将表现为一个块。我们将通过在结构 | 块布局中使用块布局配置将其放置在区域中使其可见。在“侧边栏第一”旁边,点击放置块按钮,并在弹出的列表中搜索你的块:
![动手实践 – 构建分面搜索块]()
块将具有你在配置中较早为面指定的名称。
-
点击放置块。
-
在块配置屏幕中,在其他分面块字段中,选择你创建的分面的单选按钮:
![动手实践 – 构建分面搜索块]()
-
点击保存块并导航到你的搜索页面
/search/content。 -
你将必须确保你的网站上有一些建议的内容,它们的标题中包含相同的单词,以展示分面的功能。例如,我有三个标题中包含单词
awesome的食谱。首先,分面块列出了我的食谱标题中的所有离散单词:![动手实践 – 构建分面搜索块]()
-
当我搜索
awesome时,搜索结果和分面列表被缩小:![动手实践 – 构建分面搜索块]()
-
我现在可以点击分面块中的任何其他单词,这些单词也出现在标题中,并且也包含单词
awesome。这使得过滤到所需的搜索结果变得非常容易:![动手实践 – 构建分面搜索块]()
当你在网站上积累了大量内容时,你的用户将能够通过关键词搜索和分面过滤的强大组合进行搜索、筛选和探索。
刚才发生了什么?
你已配置 Drupal 的分面模块以显示一个包含搜索词的块,以便在搜索结果旁边进行筛选。这允许你的用户通过选择内容标题中的搜索关键词列表进一步细化他们的搜索。
尝试英雄
尝试为你的食谱创建一个分类法词汇表,用于使用不同的分类进行标记。例如,菜系、起源国家、不同的饮食要求、制作难度或辣度级别。然后创建新的分面块,允许你的用户通过每个这些类别进行筛选。例如,他们可能希望从搜索rice开始,然后使用分面进一步筛选意大利,最后筛选素食以得到美味的芦笋烩饭!
快速测验
Q1. 选择以下哪些可以使用简单防火墙:
-
为了防止从某个 IP 地址远程访问服务器。
-
为了阻止用户使用 root 权限登录你的服务器。
-
为了防止你的服务器上的某个端口被访问。
-
当用户登录你的服务器时显示欢迎信息。
Q2. 选择以下哪些是使用 Apache Solr 而不是数据库搜索的有效理由:
-
您希望能够搜索您网站上节点的标题以及正文内容。
-
您的网站有很多访客,并且您的 Drupal 数据库的负载经常很高。
-
您希望匿名用户和登录用户都能在您的网站上搜索。
-
您希望当用户在搜索框中输入时,能够自动建议搜索词。
摘要
在本章中,我们学习了 Drupal 搜索概念以及如何为访客配置一个强大且灵活的搜索解决方案,以便他们在您的网站上找到他们想要的内容。我们介绍了搜索 API 模块,并了解了它如何配置与 Drupal 数据库协同工作。然后,我们介绍了 Apache Solr 搜索引擎,并探讨了使用该引擎作为搜索引擎而不是依赖 Drupal 数据库的优点。最后,我们学习了如何创建类似于在线商店或其他内容丰富的网站所使用的分面搜索界面。
在下一章中,我们将探讨 Drupal 8 中的一个主要新开发者功能:内置对 REST 的支持。我们将开发一个 Angular JS 应用程序,该应用程序将消费我们网站上的 REST API。
第十二章:Drupal 中的 RESTful 网络服务
Drupal 8 中的一个主要新开发者特性是内置对 REST 的支持。在本章中,我们将探索和配置内置的 REST 支持。然后我们将通过安装 RESTful 模块来扩展它。最后,我们将开发一个自定义的 Recipe AngularJS 应用程序,该应用程序将消费我们食谱网站的 REST API。
在本章中,我们将学习以下主题:
-
互联网服务简介
-
REST 简介
-
什么是无头 Drupal?
-
何时以及为什么要解耦 Drupal?
-
Drupal 中的 RESTful 网络服务
-
如何在 Drupal 中创建 RESTful API
-
创建一个基本的 Angular 网站,该网站将消费我们食谱的 REST API
互联网服务简介
网络服务可以被定义为网络中两个应用程序之间的通信桥梁。W3C 通常将网络服务定义为:
"一个设计用于支持网络中可互操作机器间交互的软件系统。"
互联网服务创新是组织之间以及与客户沟通的重要途径。与传统的客户/服务器模型不同,例如网络服务器或网页框架,网络服务不向用户提供图形用户界面。相反,它们通过网络上的自动接口提供业务逻辑、数据和流程。应用程序相互连接,而不是与客户连接。工程师可以将网络服务添加到 GUI(例如网页或可执行系统),以向用户提供特定功能。
让我们来看看本章中我们将要遵循的示例。我们的 Drupal 网站包含食谱内容,它们包含有关不同食谱的数据。我们希望其他应用程序和网站能够使用这些内容和数据,并在它们各自的应用程序中显示。现在,由于他们希望根据需要显示数据,他们将尝试获取数据的原始值,并按照他们的要求显示。
在此过程中,我们需要注意尝试从我们的 Drupal 应用程序获取数据的应用程序的认证,以确保我们的数据没有受到第三方攻击。
提供数据和消费它的整个场景是通过网络服务实现的,它作为我们的 Drupal 应用程序和外部世界之间的桥梁。
许多组织使用不同的编程框架进行管理。不同的编程框架通常需要相互交换信息,而网络服务是实现两个软件系统通过互联网交换这种信息的通信策略。需要信息的产品框架被称为服务请求者,在本章中是 Angular 网页,而准备请求并提供信息的产品框架被称为服务提供者,在我们的例子中是 Drupal。
简而言之,网络服务使外部应用程序和设备能够与我们的应用程序(在这种情况下,我们的 Drupal)通信,以执行CRUD操作(即创建、读取、更新和删除)。
REST 简介
REST是表示性状态转移的缩写,这是创建网络服务最受欢迎的方式之一。
REST 是面向服务架构的架构设计之一,它使用简单的 HTTP 调用与机器进行所有 CRUD 操作。仅在过去几年中,REST 因其简洁性而成为主导的网络服务设计模型,取代了大多数基于 SOAP 和 WSDL 的服务。
REST 的四个基本设计原则如下:
-
显式使用 HTTP 方法:REST 遵循纯 HTTP 方法,并鼓励开发者明确使用它,这与协议定义一致。这种基本的 REST 设计理论在 CRUD 函数和 HTTP 方法之间建立了一对一的映射,匹配以下映射:
-
POST:创建资源 -
GET:检索资源 -
PUT:更新资源 -
DELETE:删除资源
-
-
无状态:无状态网络服务创建一个响应,该响应链接到集合中的另一页,并允许客户端执行所需操作以维护此值。这种 RESTful 网络服务设计的特点可以分为两个任务单元:
-
服务器:这生成包含链接到其他资产的响应,允许应用程序在相关资源之间导航。服务器还包括 Cache-Control 和 Last-Modified 系统,以确定要缓存哪些数据以减少服务器负载。
-
客户端:这使用 Cache-Control 和 Last-Modified 系统来确定是否保留本地副本并缓存资源。
客户端应用程序与服务之间的这种协作对于 RESTful 网络服务的无状态性至关重要。它通过节省带宽和减少服务器端应用程序状态来提高性能。
![REST 简介]()
-
-
暴露类似目录结构的 URL:另一个 RESTful 网络服务的属性完全关乎 URL。RESTful 网络服务的 URL 需要直观到简单易猜的程度。将 URL 视为一种自我文档化的接口,它需要非常少的,如果有的话,解释或参考,以便任何开发者都能理解它所考虑的内容以及如何获取相关资源。例如,在我们的食谱服务中,我们将有如下结构的 URI:
-
http://www.headless.dev/api/recipes/snacks/{recipe_name},它公开了特定食谱的详细信息 -
http://www.headless.dev/api/recipes/{recipe_type},它提供了与食谱类型相关的食谱 -
URI 也应该保持静态,这样当资源发生变化或服务的实现发生变化时,链接保持不变。
-
-
传输 XML、JavaScript 对象表示法(JSON)或两者:客户端需要一种表示格式来消费 Web 服务并在其表示层上显示它们。在 RESTful 互联网服务设计中,最后一组约束与应用程序和服务在请求/响应有效负载或 HTTP 主体中交换的数据结构有关。这正是保持简单、可读性和关联性真正有益的地方。为了提供客户端应用程序请求特定文章类型的能力,以便它们能够得到完美的结果,请按照以下顺序构建您的服务:使用内置的 HTTP Recognize 标头,其中标头的优势是 MIME 类型。这里展示了 RESTful 服务中使用的某些常见 MIME 类型:
MIME 类型 内容类型 JSON application/jsonXML application/xmlXHTML application/xhtml+xml
无头 Drupal
“无头 Drupal”这个术语被用来指代 Drupal 应用程序后端和前端之间的解耦。在无头 Drupal 中,网站访客不会直接与 Drupal 交互。
从网站访客的角度来看,用户并不是直接连接到 Drupal,而是连接到一个前端 JavaScript 框架,例如 KnockoutJS 或 AngularJS。因此,网站访客看不到生成的 Drupal 主题(即头部),这不被使用:无头。
在这种情况下,Drupal 仅用作后端内容管理系统,由前端 JavaScript 框架、移动应用或其他第三方应用程序读取。因此,Drupal 后端正如您所知,但前端完全是非 Drupal 的。
数据交换几乎总是通过 JSON 进行。
关于 Drupal 未来的一份宣言已经制定,包含四个目标:
-
我们希望 Drupal 成为设计师和前端开发者的首选后端内容管理系统。
-
我们相信 Drupal 的主要优势在于其后端的力量和灵活性;其对用户的主要价值在于其构建和显示复杂内容模型的能力。
-
我们相信客户端前端框架是 Web 的未来。
-
对于 Drupal 来说,首先应该是面向服务的——而不是面向 HTML 的——否则可能会变得无关紧要。
一张无头 Drupal 网站的示意图可以解释 Pantheon 构建的大量内容:

无头 Drupal 将通过使用 REST 服务器创建 API 来实现 Web 服务概念,这些 API 将被其他应用程序或单个应用程序消费,以在前端提供服务。
在 Drupal 8 之前,可以使用 services 和 RESTWS 等模块构建无头网站,但 Drupal 8 内置了 REST API,这很好地满足了这一目的。
何时解耦 Drupal 或何时使用无头 Drupal
如前所述,无头 Drupal 将解耦后端架构和前端架构,这样前端就可以根据需求灵活地显示内容。但何时应该使用这种方法?我们特别在 Drupal 拥有强大的自身主题层时使用它。
让我们来看看使用无头 Drupal 的一些优缺点:
-
优点:
-
为所有人提供干净的 API,因为它是一致的,任何应用程序都可以用来表示数据。
-
升级前端将需要后端的所有更改,同样,升级后端也不会影响前端。然而,在设计内容 API 时需要格外小心。
-
由于前端与 Drupal 分离,对 Drupal 专业知识的依赖性减少。
-
-
缺点:
-
解耦结构的工程更难以理解和调试。弄清楚为什么某些东西出了问题是非常困难的。
-
Drupal 的即用功能需要从头开始构建。例如,Facebook 插件,它提供 Facebook 访问和登录功能,功能强大且稳定。但在解耦环境中使用时,整个功能需要重新构建。
-
由于后端团队与前端团队分离,高效开发所需的最小团队规模更大。
-
那么为什么无头 Drupal 让我们感到高兴呢?
-
Drupal 的安装更容易维护
-
系统的可扩展性变得更容易
-
与不同团队一起工作和为团队工作变得更加容易
-
性能得到提升
-
它使项目具有未来保障
自 Drupal 启动以来,它已经从仅仅是一个博客平台发展到成为一个强大的内容管理系统,无头 Drupal 的概念进一步加强了其内容管理能力。但在跳入解耦 Drupal 之前,你需要对项目有一个清晰的理解。尽管它看起来很有吸引力,并且具有很大的优势,但它也有缺点。
Drupal 中的 RESTful Web 服务
如前所述,在 Drupal 8 之前,可以使用 Services 模块或 RESTWS 模块在 Drupal 7 中实现 RESTful Web 服务。这些模块(使用 REST 服务器)可以用来构建强大的无头 Drupal 网站。但随着 Drupal 8 的发展,这一功能已被附加到 Drupal 8 默认捆绑包的核心中,以及其他推送到核心的贡献特性、模块,如 Views、Link、WYSIWYG 编辑器等。
Drupal 8 通过核心中的四个模块实现了设置基本 Web 服务环境以提供 API 的完整功能。它们如下:
-
RESTful Web 服务 (REST): 这通过 RESTful Web API 公开实体和其他资源。它依赖于 Serialization 模块来序列化发送到和从 API 发送的数据。
-
Serialization: 这提供了一种将数据序列化到 JSON 和 XML 等格式以及从这些格式中反序列化的服务。
-
超文本应用语言(HAL):使用超文本应用语言序列化实体。Drupal 核心目前使用此格式,增加了两个关键字:
_link用于链接关系,_embedded用于嵌入媒体。 -
HTTP 基本认证(basic_auth):此模块通过 HTTP 基本认证提供者实现基本用户认证,使用用户名和密码进行认证以进行 API 调用。
Drupal 中的 RESTful API
在 Drupal 中创建 RESTful API 是一个相对简单的工作,因为整个 Web 服务的力量已经移至核心。为了创建 API,我们将使用为这本书构建的食谱内容类型。
我们将创建三个基本 API;这些将提供以下信息:
-
获取所有食谱类型
-
获取特定食谱类型下的所有食谱
执行时间 – 获取所有食谱类型
这是我本地 Vagrant(http://www.headless.dev)开发服务器上运行的食谱类型列表:

我们将创建一个 API URL,结构为http://www.headless.dev/recipes,这将给出我网站上的食谱列表。让我们开始吧:
-
我们需要从模块页面启用 Web 服务模块。
-
点击管理菜单栏中的管理,然后点击扩展。
-
一旦你进入模块页面,搜索RESTful Web 服务和序列化。启用这两个模块:
![执行时间 – 获取所有食谱类型]()
-
接下来,我们需要为我们的食谱类型创建 REST 导出视图。视图现在是 Drupal 8 的核心,它使用 REST 导出模式创建 Web 服务 API。
![执行时间 – 获取所有食谱类型]()
-
然后点击视图:
![执行时间 – 获取所有食谱类型]()
-
你将看到视图页面,在这里你可以管理、配置和添加新的视图。Drupal 8 广泛使用视图来在网站上显示不同的数据和内容。然而,Web 服务模块默认不提供任何视图。
-
继续操作,点击视图页面的添加新视图:
![执行时间 – 获取所有食谱类型]()
-
在视图配置页面,按照下一张截图所示填写表单。我们已选择显示为分类术语,以及类型为食谱类型,以创建一个用于显示网站中食谱类型的 Web 服务 API。
![执行时间 – 获取所有食谱类型]()
-
填写完表单后,我们需要选择 REST 导出显示,通过 URL 导出或公开 API。
![执行时间 – 获取所有食谱类型]()
-
点击保存并编辑以配置视图。
-
现在,默认情况下,视图将提供关于分类术语的完整实体信息。
![执行时间 – 获取所有食谱类型]()
-
预览视图的结果非常混乱,难以阅读。它以一个平铺的列表形式显示。
![行动时间 – 获取所有食谱类型]()
为了使其更易于阅读,您需要安装一个名为 JSONview 的谷歌插件,该插件以更易读的方式渲染 JSON 对象。
一旦安装了插件,您可以通过点击此 URL 在浏览器中查看响应:
http://www.headless.dev/api/recipes。由于它提供了所有关于术语的信息,我们需要从响应中过滤掉不必要的字段,使其更易于阅读和消费。
-
我们将公开食谱类型的标题、tid 和描述以供消费。在 Views 设置中,在 FORMAT 下选择 Show 属性,并选择 Fields 而不是 Entity。
![行动时间 – 获取所有食谱类型]()
-
从提供的选项中选择 Fields,以便您可以将字段添加到您的 REST 导出中:
![行动时间 – 获取所有食谱类型]()
-
选择 Apply,在下一屏幕上,它会询问您是否需要为字段标签创建别名。但保留为 name 即可。
-
接下来,我们需要在 FIELDS 设置下添加剩余的字段。在 FIELDS 下选择 Add 并添加
tid和description字段:![行动时间 – 获取所有食谱类型]()
-
选择 Taxonomy term: Term ID 字段以在您的 REST 导出中获取术语 ID。
![行动时间 – 获取所有食谱类型]()
-
然后从字段列表中选择 Taxonomy term: Description 字段:
![行动时间 – 获取所有食谱类型]()
-
应用设置并保存字段为默认值,除非您需要为字段做更特殊的事情。
-
接下来,我们将重新排列字段,以便字段的顺序为 tid、name 和 description。
![行动时间 – 获取所有食谱类型]()
-
现在,如果我们访问浏览器中的
http://www.headless.dev/api/recipes,我们将得到一个更精确且易于阅读的 JSON 对象,可以由任何应用程序消费。![行动时间 – 获取所有食谱类型]()
刚才发生了什么?
我们使用 Drupal REST 服务模块和 REST 视图导出创建了一个 API,它为我们提供了 Drupal 网站上可用的食谱类型的 JSON,供消费使用。
行动时间 – 创建一个 API 以获取所有食谱类型下的食谱
现在,我们将创建我们的下一个 API,该 API 用于公开所有食谱类型下的食谱:
-
我们将以相同的方式创建一个新的视图,但我们将选择所有类型食谱的内容而不是选择分类术语。
![行动时间 – 创建一个 API 以获取所有食谱类型下的食谱]()
-
在 REST 导出设置中,将 URL 设置为
api/recipes/%。百分号%符号作为通配符,将食谱类型 tid 作为参数。![执行时间 – 创建一个 API 以获取特定食谱类型下的所有食谱]()
-
在视图设置页面,我们配置视图就像我们为食谱类型所做的那样。我们将显示的字段是:
-
节点 ID
-
标题
-
发布状态
-
食谱类型
-
食谱图片
-
准备时间
-
烹饪时间
-
总时间
-
正文
-
成分
-
说明
-
产量
-
评论
-
-
要获取与特定食谱相关的食谱,我们将使用食谱内容中的食谱类型字段,该字段引用了食谱类型词汇表。
-
在视图配置页面的高级部分,在上下文过滤器下,点击添加以将食谱类型 tid 作为参数添加:
![执行时间 – 创建一个 API 以获取特定食谱类型下的所有食谱]()
-
在上下文过滤器形式中,搜索
食谱类型并选择要配置的字段:![执行时间 – 创建一个 API 以获取特定食谱类型下的所有食谱]()
-
在上下文过滤器配置表单中,选择提供默认值单选按钮,并从类型下拉菜单中选择从 URL 获取分类术语 ID作为参数:
![执行时间 – 创建一个 API 以获取特定食谱类型下的所有食谱]()
-
JSON 对象将给出索引作为字段的机器名。因此,我们将字段别名化以使索引名称相关。
![执行时间 – 创建一个 API 以获取特定食谱类型下的所有食谱]()
-
现在,如果你通过点击 URL
http://www.headless.dev/api/recipes/6在浏览器中检查结果,你将获得与术语 ID6相关的节点 JSON 对象,该 ID 对应于 Snack 食谱类型:{ nid: "3", recipe_name: "<a href="/node/3" hreflang="en">Oven-Roasted Falafel</a>", status: "On", recipe_type: "Snack", recipe_image: "http://www.headless.dev/sites/default/files/oven-roasted-fallafel_330.png", preptime: "10Minutes", cooktime: "35Minutes", totaltime: "1Hours", summary: "<p>Crisp chickpeas balls are stuffed in soft pita bread along with juicy cucumbers and refreshing sauces.</p>", ingredients: "<ul><li class="ingredient">1 can chickpeas, rinsed and drained<br /><br /> 1 small onion, chopped<br /><br /> 2-4 garlic cloves, peeled<br /><br /> 2 Tbsp chopped fresh parsley <br /><br /> 2 Tbsp chopped fresh cilantro <br /><br /> 1 tsp ground cumin <br /><br /> 1/4 tsp salt <br /><br /> Pinch of dried chili flakes<br /><br /> 1/4 cup whole wheat flour (plus extra, if needed) <br /><br /> 1 tsp baking powder <br /><br /> Canola oil for cooking<br /><br /><br /><strong>Accompaniments:</strong><br /><br /> Fresh pitas<br /><br /> Tzatziki<br /><br /> Chopped cucumber<br /><br /> Tomatoes<br /><br /> Red onion </li> </ul>", instructions: "<p><span>Preheat oven to 425 ˚F (220 ˚C).</span><br /><br /><span>Place chickpeas, onion, garlic, parsley, cilantro, cumin, salt, and chili flakes in bowl of food processor and pulse until combined but still chunky. </span><br /><br /><span>Add flour and baking powder and pulse until it turns into soft mixture that you can roll into balls without sticking to your hands. </span><br /><br /><span>(Add another spoonful of flour if it seems too sticky.)</span><br /><br /><span>Roll dough into meatball-sized balls and gently flatten each into little patty. </span><br /><br /><span>Place patties on heavy-rimmed baking sheet, preferably one that's dark in color.</span><br /><br /><span>Brush each patty with canola oil, flip them over and brush other side. </span><br /><br /><span>Roast for 15 minutes, then flip them over and roast for another 10 minutes, until crisp and golden on both sides. </span><br /><br /><span>Serve warm, wrapped in pitas, with tzatziki, chopped cucumber, tomatoes and red onion. </span></p>", yield: "3", review: "" }
刚刚发生了什么?
我们创建了一个提供所有食谱类型的 API,以及一个提供特定食谱类型下所有食谱类型的 API。现在我们将看看我们如何使用 AngularJS 来消费这个 REST 导出的 JSON 输出并在网页上显示。
执行时间 – 使用 AngularJS 消费 RESTful Web 服务
现在我们有一个 API,它提供了包含食谱、食谱类型和单个食谱的 JSON 数据的 JSON 数据服务,让我们看看我们如何使用 HTML 和 AngularJS 在页面上显示这些数据:
-
由于它是针对 Drupal 受众的,我们不会深入研究服务的消费。
-
因此,让我们首先在 Drupal 根目录中创建一个名为
recipes的目录。在目录中,我们将有两个文件,recipe.js和index.html。 -
recipe.js文件将包含调用 API URL 并解析数据对象以供消费的代码。index.html文件将包含读取解析数据并在页面上显示的 AngularJS 代码和 HTML。 -
文件夹结构应类似于这个:
Drupal Root --recipe ----recipe.js ----index.html -
让我们来看看
recipe.js文件,这是 AngularJS 控制器。正如所说,它将包含创建 Angular 控制器模块的 JavaScript 代码,从 API URL 获取 JSON 对象并将其解析为从 JSON 对象提供原始内容:function Recipe($scope, $http) { $http.get('http://www.headless.dev/api/recipes/6'). success(function(data) { $scope.recipes = data[0]; }); } -
上述代码是 Angular 中的一个控制器,一个 JavaScript 函数。这个函数使用了两个变量,
$http和$scope。-
$http使用get方法调用 REST API URL -
$scope包含从 API 返回的 JSON,并将其传递给页面中的元素数组recipe
-
-
现在我们有了 AngularJS 控制器,我们将创建一个 HTML 页面,该页面将加载控制器到网页浏览器中:
<!doctype html> <html ng-app> <head> <title>Recipes</title> <script src="img/angular.min.js"></script> <script src="img/recipe.js"></script> </head> <body> <div ng-controller="Recipe"> <h1>{{recipes.recipe_name}}</h1> <p>{{recipes.summary}}</p> </div> </body> </html> -
第一个脚本标签从 内容分发网络(CDN)加载了最小化的 AngularJS 库(
angular.min.js),这样我们就不必下载 AngularJS 并将其放置在项目中。 -
第二个脚本从应用程序的路径加载控制器代码(
recipe.js)。 -
AngularJS 使用两个属性与 HTML DOM 交互:
-
ng-app属性用于指示此页面是 AngularJS 应用程序 -
ng-controller属性用于定义要使用的控制器
-
-
占位符引用了食谱模型对象的
recipe_name和summary属性,这些属性将在成功消费 REST 服务后设置:-
<h1>{{recipes.recipe_name}}</h1> -
<p>{{recipes.summary}}</p>
-
要运行客户端,你只需要在网页浏览器中打开目录。由于我们的 Angular 应用程序位于 Drupal 根目录中,我们可以通过打开此 URL 来运行它:http://www.headless.dev/api/recipes/6/recipes。
这将显示以下结果:

因此,这是使用 Angular JS 消费 RESTful API 的简单方法。
需要记住的一个要点:由于 Angular 应用程序位于 Drupal 根目录中,因此运行 Angular 应用程序时没有跨域问题。但如果 Angular 应用程序位于托管 web 服务的服务器之外,那么必须在托管 web 服务的服务器上启用 CORS(跨域资源共享)。这允许受限制的资源在 Angular 应用程序服务器上显示。
发生了什么?
我们创建了一个 AngularJS 应用程序,它消费了从 Drupal 实例创建的 REST 导出的 JSON 结果。此 JSON 响应被 Angular 应用程序用来在 HTML 页面上显示由 Angular 应用程序提供的输出。这有助于我们以更快的速度显示数据,并且我们可以控制页面显示和设计。
摘要
因此,我们现在有一个用于数据存储的 Drupal 网站。Drupal 网站将被用来生成内容和数据。这些数据将使用 Drupal 内置的 web 服务模块和视图以 JSON 格式表示,创建 RESTful JSON 导出,该导出可以被应用程序用来消费此 JSON 输出并在应用程序中显示。
附录 A. 临时测验答案
第十一章:– 使用搜索 API 模块搜索您的网站
临时测验
| Q1. 选择以下哪些情况下可以使用简单防火墙? | 1 和 3 |
|---|---|
| Q2. 选择以下哪些是使用 Apache Solr 而不是基于数据库的搜索的有效理由? | 2 和 4 |



































































































































































































































































浙公网安备 33010602011771号