Drupal8-开发秘籍-全-
Drupal8 开发秘籍(全)
原文:
zh.annas-archive.org/md5/1e7325d6cecc45b72ad1ecad8aa2a1e1译者:飞龙
前言
Drupal 是一个用于构建小型企业、电子商务、企业系统等网站的内容管理系统。由超过 4,500 名贡献者创建,Drupal 8 为 Drupal 带来了许多新功能。无论你是 Drupal 的新手,还是经验丰富的 Drupalista,Drupal 8 开发食谱包含了深入探索 Drupal 8 所提供内容的食谱。
本书涵盖内容
第一章,与 Drupal 8 一起启动,首先介绍了运行 Drupal 8 的要求,然后介绍了安装过程和扩展 Drupal。
第二章,内容创作体验,深入探讨了 Drupal 中的内容管理体验,包括使用新捆绑的 CKEditor。
第三章,通过视图显示内容,探讨了如何使用视图在 Drupal 中创建不同的方式来列出和显示你的内容。
第四章,扩展 Drupal,介绍了如何为 Drupal 编写模块,这是 Drupal 中功能构建块。
第五章,前端为王,涵盖了如何创建主题,使用新的模板系统 Twig,以及利用 Drupal 的响应式设计功能。
第六章,使用表单 API 创建表单,解释了如何使用 Drupal 的表单 API 创建用于收集数据的自定义表单。
第七章,插件即插即用,介绍了插件,这是 Drupal 中最新的组件之一。本章将指导你开发插件系统以与字段一起工作。
第八章,多语言和国际化,介绍了 Drupal 8 提供的功能,以创建一个国际化的网站,支持内容和管理的多语言。
第九章,配置管理 - 在 Drupal 8 中部署,解释了 Drupal 8 中引入的配置管理系统以及如何导入和导出网站配置。
第十章,实体 API,深入探讨了 Drupal 中的实体 API,允许你创建自定义配置和内容实体。
第十一章,走出 Drupalicon 岛,解释了 Drupal 如何允许采用“自豪地构建在其他地方”的口号,并在你的 Drupal 网站上包含第三方库。
第十二章,网络服务,展示了如何通过 RESTful 接口将你的 Drupal 8 网站转变为网络服务 API 提供者。
第十三章,Drupal CLI,探讨了通过 Drupal 社区创建的两个命令行工具(Drush 和 Drupal Console)与 Drupal 8 一起工作。
你需要为此书准备的内容
为了使用 Drupal 8 并运行本书中的代码示例,以下软件将是必需的:
Web 服务器软件堆栈:
-
Web 服务器:Apache(推荐)、Nginx 或 Microsoft IIS
-
数据库:MySQL 5.5 或 MariaDB 5.5.20 或更高版本
-
PHP:PHP 5.5.9 或更高版本
第一章节详细介绍了所有这些要求,并包含了一个突出显示即插即用开发服务器设置的食谱。
您还需要一个文本编辑器;以下是一些流行编辑器和 IDE 的建议:
-
Atom.io 编辑器,
atom.io/ -
Visual Code Studio,
code.visualstudio.com/ -
PHPStorm(特定 Drupal 集成),
www.jetbrains.com/phpstorm/ -
配置 Drupal 的 Vim,
www.drupal.org/project/vimrc -
您操作系统的默认文本编辑器或命令行文件编辑器
这本书面向的对象
这本书是为那些已经在使用 Drupal 的人编写的,例如网站构建者、后端和前端开发者,以及那些渴望了解当他们开始使用 Drupal 8 时将面临什么的人。
部分
在本书中,您将找到几个频繁出现的标题(准备工作、如何操作、它是如何工作的、还有更多、参见等)。
为了清楚地说明如何完成食谱,我们使用以下部分如下:
准备工作
本节告诉您在食谱中可以期待什么,并描述了如何设置任何软件或任何为食谱所需的初步设置。
如何操作…
本节包含遵循食谱所需的步骤。
它是如何工作的…
本节通常包含对上一节发生情况的详细解释。
还有更多…
本节包含有关食谱的附加信息,以便使读者对食谱有更深入的了解。
参见
本节提供了对其他有用信息的链接,以帮助读者了解食谱。
惯例
在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 处理方式如下所示:“您将看到 drupal-org.make 和 drupal-org-core.make。”
代码块设置如下:
public function alterRoutes(RouteCollection $collection) {
// Change path of mymodule.mypage to use a hyphen
if ($route = $collection->get('mymodule.mypage'))
任何命令行输入或输出都如下所示:
$ CREATE USER username@localhost IDENTIFIED BY 'password';
新术语和重要词汇以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,在文本中显示如下:“勾选复选框并点击安装。”
警告或重要注意事项如下所示。
技巧和窍门如下所示。
读者反馈
我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出你真正能从中获得最大收益的标题。
要向我们发送一般反馈,请简单地将邮件发送到 feedback@packtpub.com,并在邮件主题中提及书籍的标题。
如果你在某个领域有专业知识,并且对撰写或参与书籍的编写感兴趣,请参阅我们的作者指南,网址为 www.packtpub.com/authors。
客户支持
现在你已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助你从购买中获得最大收益。
下载示例代码
您可以从您的账户中下载此书的示例代码文件,网址为 www.packtpub.com。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的“支持”选项卡上。
-
点击“代码下载与错误清单”。
-
在搜索框中输入书籍名称。
-
选择您想要下载代码文件的书籍。
-
从下拉菜单中选择你购买此书籍的来源。
-
点击“代码下载”。
您也可以通过点击 Packt Publishing 网站上书籍网页上的“代码文件”按钮来下载代码文件。您可以通过在搜索框中输入书籍名称来访问此页面。请注意,您需要登录您的 Packt 账户。
下载文件后,请确保您使用最新版本的软件解压缩或提取文件夹:
-
Windows 系统下的 WinRAR / 7-Zip。
-
Mac 系统下的 Zipeg / iZip / UnRarX。
-
Linux 系统下的 7-Zip / PeaZip。
该书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Drupal-8-Development-Cookbook-Second-Edition。我们还有其他来自我们丰富图书和视频目录的代码包可供选择,网址为 github.com/PacktPublishing/。去看看吧!
错误清单
尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分现有的勘误列表中。
要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分。
盗版
互联网上版权材料的盗版是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的我们作品的非法副本,请立即向我们提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过copyright@packtpub.com与我们联系,并提供疑似盗版材料的链接。
我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过questions@packtpub.com与我们联系,我们将尽力解决问题。
第一章:使用 Drupal 8 运行起来
在本章中,我们将介绍 Drupal 8 并涵盖以下食谱:
-
安装 Drupal
-
使用带有 Drupal 的发行版
-
安装模块和主题
-
在 Drupal 8 中使用多站点
-
设置环境的工具
-
运行测试:Simpletest 和 PHPUnit
简介
本章将从介绍安装 Drupal 8 网站开始。我们将介绍 Drupal 的交互式安装程序。我们将介绍使用名为 Drush 的命令行工具安装 Drupal。Drupal 提供两种安装类型:标准型和最小型。在本书中,我们将使用标准安装。
一旦我们安装了我们的 Drupal 8 网站,我们将介绍扩展 Drupal 的基础知识。我们将讨论使用发行版和安装贡献项目,例如模块和主题。我们还将包括卸载模块,因为在 Drupal 8 中卸载模块的过程已经发生了变化。
本书将涉及一个与 Drupal 8 一起工作的动手示例,本章将提供有关设置本地开发环境的信息。本章还将提供如何在 Drupal 8 中设置多站点安装和运行可用测试套件的食谱。
在我们开始之前,您应该安装 Composer。Composer 是 PHP 的既定包管理工具。如果您不熟悉 Composer,它就像使用 Ruby 的 Gems、Node.js 的 npm 和前端库的 Bower 一样。请访问 Composer 文档了解如何在您的系统上全局安装 Composer:
-
Linux / Unix / macOS:
getcomposer.org/doc/00-intro.md#installation-linux-unix-osx -
Windows:
getcomposer.org/doc/00-intro.md#installation-windows
安装 Drupal
下载 Drupal 并安装有许多不同的方法。在本食谱中,我们将专注于从 www.drupal.org/ 下载 Drupal 并在基本的 Linux、Apache、MySQL 或 PHP(LAMP)服务器上设置它。
在本食谱中,我们将设置 Drupal 8 的文件并逐步介绍安装过程。
准备工作
在我们开始之前,您需要一个满足 Drupal 8 的新系统要求的开发生态系统:
-
Apache 2.0(或更高版本)或 Nginx 1.1(或更高版本)的 Web 服务器
-
需要 PHP 5.5.9 或更高版本,但推荐使用 PHP 5.6 或 PHP 7,因为 PHP 5.5 已经达到其生命周期的结束支持。
-
数据库需要 MySQL 5.5 或 MariaDB 5.5.20
您需要一个具有创建数据库权限的用户,或者一个已经创建的数据库,其中包含具有在该数据库中创建表权限的用户。
-
上传或移动文件到服务器的访问权限
-
虽然默认安装的 PHP 可以与 Drupal 一起工作,但它确实需要某些 PHP 扩展,例如 mbstring。请查看
www.drupal.org/requirements/php以获取最新的要求信息。
Drupal 8 附带 Symfony (symfony.com/)组件。Drupal 8 中的新依赖之一,以支持 Symfony 路由系统,是 Drupal 的“清洁 URL”功能。如果服务器使用 Apache,请确保已启用mod_rewrite。如果服务器使用 Nginx,则必须启用ngx_http_rewrite_module。
我们将下载 Drupal 8 并将其文件放置在您的 Web 服务器的文档根目录中。这是/var/www文件夹。如果您使用了 XAMPP、WAMP 或 MAPP 等工具,请查阅适当的文档以了解您的文档根目录。
要查看 Drupal 8 的完整系统需求,请查看www.drupal.org/docs/8/system-requirements/。Drupal.org 文档目前正在迁移。此外,请查看www.drupal.org/docs/7/system-requirements/overview上的 Drupal 7 需求页面,该页面突出显示了 Drupal 8 的项目。
如何操作...
我们需要遵循以下步骤来安装 Drupal 8:
- 首先,我们需要导航到
www.drupal.org/download并下载 Drupal 8.x 的最新版本。您可以在 Drupal 8(8.3.1、8.4.0 等)的www.drupal.org/project/drupal页面上找到最新和推荐的版本。解压缩存档并将文件放置在您的文档根目录下的drupal8文件夹中:

- 打开您的浏览器并访问您的 Web 服务器,例如
http://localhost/drupal8,这将带您到 Drupal 安装向导。您将进入新的多语言选项安装屏幕。选择您的语言并点击保存并继续:

-
在下一屏,选择安装配置的默认标准选项。这将为我们提供一个带有已安装最常用模块的标准配置。
-
下一步将验证您的系统需求。如果您的系统没有任何可报告的问题,屏幕将被跳过。如果您确实有任何需求冲突,您可以解决它们并点击按钮重试。
如果您有需求问题,安装程序将报告具体问题。几乎每个需求都会链接到一个 Drupal.org 手册页面,其中包含解决方案步骤。
- 输入 Drupal 的数据库信息。在大多数情况下,您只需要提供用户名、密码和数据库名称,并将其他设置为默认值。如果您的数据库不存在,安装程序将尝试创建数据库:

请参阅此菜谱的“更多...”部分,了解设置数据库的信息。
-
您的 Drupal 8 站点将开始安装。当它安装完基本模块后,您将被带到站点配置屏幕。
-
配置网站表单提供了你的 Drupal 网站的基本配置。输入你的网站名称和网站的电子邮件地址。网站电子邮件将用于发送管理通知,并作为从 Drupal 网站发出的电子邮件的原始电子邮件地址。此表单允许你设置有关网站的国家和时区的地区信息。设置时区确保时间值正确显示。
-
填写网站维护账户信息,也称为用户 1,它在 Unix 系统中的 root 类似。网站维护账户至关重要。正如所述,它作为第一个用户存在,并赋予用户 ID 为 1。在 Drupal 中,具有用户 ID 为 1 的用户通常可以自动绕过权限检查并拥有全局访问权限。
-
输入网站的地区信息,并检查网站是否应该检查已启用的模块和 Drupal 本身的可用更新。通过自动检查更新,你的网站将向 Drupal 报告匿名使用统计信息,并提供你的版本状态摘要。你也可以选择让网站通过电子邮件通知你新版本发布,包括安全更新。
-
当信息满足条件时,点击保存并继续,恭喜你,你已经安装了 Drupal!下一屏将为你提供一个链接到你的已安装 Drupal 网站。
它是如何工作的...
Drupal 的安装过程将为所选语言提供 Drupal 安装,并根据安装配置文件(本食谱中的标准或最小)安装模块和配置。
当你访问安装程序时,它会从浏览器中读取语言代码。有了这个语言代码,它将选择一个支持的语言。如果你选择非英语安装,翻译文件将自动从 localize.drupal.org/ 下载。Drupal 的早期版本不支持自动的多语言安装。关于多语言的内容将在第八章,多语言和国际化中进一步介绍。
安装配置文件指示 Drupal 安装默认的模块。贡献的安装配置文件被称为发行版;我们将在下一食谱中进一步讨论这一点。
在验证需求时,Drupal 会检查应用程序版本和 PHP 配置。例如,如果你的服务器已安装 PHP Xdebug (xdebug.org) 扩展,则max_nesting_level的最小值必须是 256,否则 Drupal 将无法安装 (www.drupal.org/node/2393531)。
还有更多...
Drupal 的安装过程很简单,但有一些值得讨论的地方。
创建数据库用户和数据库
如前所述,要安装 Drupal,您需要能够访问数据库服务器(或创建一个)以及现有的数据库(或创建一个)。此过程将取决于您的环境设置。
如果您与托管提供商合作,很可能有一个基于 Web 的控制面板。这应该允许您创建数据库和用户。有关此主题的更多信息,请参阅您的托管提供商的文档。
如果您在服务器上使用phpMyAdmin (www.phpmyadmin.net/),通常由 MAMP、WAMP 和 XAMPP 安装,并且拥有 root 权限,您可以通过以下步骤创建数据库和用户:
-
以 root 用户身份登录到 phpMyAdmin。
-
从权限页面底部点击“添加新用户”。
-
填写用户信息。
-
选择为用户创建数据库并授予所有权限。
-
您现在可以使用该用户信息将 Drupal 连接到您的数据库。
如果您没有用户界面但有命令行访问权限,您可以使用 MySQL 命令行设置数据库和用户。这些说明可以在core/INSTALL.mysql.txt文件中找到。从您站点的命令行执行以下操作:
- 登录到 MySQL:
$ mysql -u username -p
- 创建数据库;您将使用以下命令创建
my_database数据库:
$ CREATE DATABASE my_database CHARACTER SET utf8 COLLATE utf8_general_ci;
- 创建一个新用户以访问数据库:
$ CREATE USER username@localhost IDENTIFIED BY 'password';
- 授予新用户数据库权限,具体操作如下:
$ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES ON databasename.* TO 'username'@'localhost' IDENTIFIED BY 'password';
如果您使用 PostgreSQL 或 SQLite 数据库安装 Drupal,请查看相应的安装说明,即INSTALL.pgsql.txt或INSTALL.sqlite.txt。
数据库前缀
Drupal 与其他内容管理系统类似,允许您从数据库设置表单中为数据库表设置前缀。此前缀将放置在表名之前,以帮助使它们独特。虽然不推荐这样做,但这将允许多个安装共享一个数据库。利用表前缀可以在一定程度上通过隐藏提供安全性,因为表将不会使用它们的默认名称:

使用 Drush 下载和安装
您还可以使用 PHP 命令行工具 Drush 安装 Drupal。Drush 是由 Drupal 社区创建的命令行工具,如果您想使用它,则必须安装 Drush。Drush 在第十三章,The Drupal CLI中有介绍。
截至 Drush 9,它支持 Drupal 8.3+,本节已被弃用。使用 Drush 下载 Drupal 核心或贡献模块将抛出警告,建议使用 Composer。
pm-download命令将从 Drupal.org 下载包。site-install命令允许您指定安装配置文件和其他选项以安装 Drupal 站点。本食谱中的安装步骤可以通过 Drush 执行,如下所示:
$ cd /path/to/web
$ drush pm-download drupal-8 drupal8
$ cd drupal8
$ drush site-install standard -locale=en-US --account-name=admin --account-pass=admin -account-email=demo@example.com -db-url=mysql://user:pass@localhost/database
我们使用 Drush 下载最新的 Drupal 8 并将其放置在名为 drupal8 的文件夹中。然后,site-install 命令指示 Drush 使用标准安装配置文件,配置维护账户,并提供数据库 URI 字符串,以便 Drupal 可以连接到其数据库。
使用 Composer 创建 Drupal 网站
您可以使用 Composer 下载 Drupal,这是事实上的 PHP 包管理器。社区提供的 Drupal Composer 项目模板是首选方法。
要构建您的 Drupal 8 网站,请运行以下命令:
$ cd /path/to/document/root
$ composer create-project drupal-composer/drupal-project drupal8 --stability dev
等待命令完成--这可能需要一些时间,因为它会下载所有必需的依赖项。您可以随意拿一杯咖啡(第一次可能需要一段时间;它会预热缓存。请有信心,下次会快得多。)
完成后,您将在 drupal8 目录内找到一个不同的目录结构。vendor 目录包含所有第三方 PHP 库,而 web 目录包含您的 Drupal 8 网站。您需要修改您的 web 服务器,以便在 drupal8 目录中使用 web 目录作为新的 docroot。
项目及其详细信息可在 github.com/drupal-composer/drupal-project 找到,以及其完整文档。
安全更新
如果您选择禁用更新选项,您将不得不手动检查模块升级。虽然大多数升级是为了修复错误或添加功能,但其中一些是为了安全更新。强烈建议您订阅 Drupal 安全团队的更新。这些更新可在 Twitter 上的 @drupalsecurity (twitter.com/drupalsecurity) 或相关订阅源上找到。
参见
-
想了解更多关于多语言的内容,请查看第 8 章,多语言和国际化
-
想了解更多关于使用命令行和 Drupal 的信息,请查看第 13 章,Drupal CLI
-
查看关于在 Drupal.org 上安装 Drupal 的手册,链接为
www.drupal.org/documentation/install -
查看讨论 Drupal 8 和 Composer 的 Drupal.org 手册,链接为
www.drupal.org/docs/develop/using-composer/using-composer-with-drupal -
查看有关 Drush 网站安装的更多信息,链接为
drushcommands.com/drush-8x/core/site-install/
使用带有 Drupal 的发行版
为什么您想使用分发?分发是一个由 Drupal 核心不提供的贡献安装配置文件。分发提供具有特定已安装模块和主题以及特定配置(内容类型和块)的 Drupal 特殊版本。在 Drupal.org 上,当您下载安装配置文件时,它不仅包括配置文件及其模块,还包括 Drupal 核心的一个版本,因此得名分发。您可以在 www.drupal.org/project/project_distribution 找到所有 Drupal 分发的列表。
如何操作...
我们将遵循以下步骤下载分发,用作 Drupal 8 的定制版本:
-
从 Drupal.org 下载分发。对于这个配方,让我们使用 Acquia 提供的 Demo Framework,网址为
www.drupal.org/project/df。 -
选择 8.x 分支推荐版本。
-
将文件夹内容提取到您的 Web 服务器文档根目录中--您会注意到其中包含 Drupal 核心文件;在
profiles文件夹中,有安装配置文件的文件夹--df。 -
由于当前 Drupal.org 打包限制,您需要运行一个手动步骤来安装额外的依赖项。在提取内容内的终端中运行以下命令:
$ composer require "commerceguys/intl: ~0.7" "commerceguys/addressing: ~1.0" "commerceguys/zone: ~1.0" "embed/embed: ~2.2
-
按照常规方式安装 Drupal,通过在浏览器中导航到您的 Drupal 网站。
-
按照网站上的安装说明安装分发。
它是如何工作的...
安装配置文件通过包含贡献项目领域或自定义模块的额外模块来工作。然后配置文件将它们定义为与 Drupal 一起安装的依赖项。当您选择安装配置文件时,您是在指示 Drupal 在安装时安装一组模块。
Demo Framework 声明自己为独家安装配置文件。声明此配置文件的分发将自动选中并假定是默认安装选项。独家标志是在 Drupal 7.22 中添加的,以改善使用 Drupal 分发的体验 (drupal.org/node/1961012)。
还有更多...
分发提供具有特定功能集的 Drupal 特殊版本,但有一些项目值得讨论。
Makefiles
生成构建分发的当前标准是使用 Drush 和 makefiles。Makefiles 允许用户定义 Drupal 核心和其它项目(如主题、模块和第三方库)的特定版本,这些项目将构成 Drupal 代码库。它不是一个像 Composer 那样的依赖管理流程,而是一个构建工具。
如果您查看 Demo Framework 的 profile 文件夹,您将看到 drupal-org.make 和 drupal-org-core.make。这些文件被 Drupal.org 打包器解析,以编译代码库并将其打包成 .zip 或 .tar.gz 格式,就像您下载的那样。
使用 Drush 安装
如第一道菜谱中的 还有更多... 部分所述,您可以通过 Drush 命令行工具安装 Drupal 网站。您可以通过将其作为第一个参数提供来指示 Drush 使用特定的安装配置文件。
截至 Drush 9,它支持 Drupal 8.3+,本节已被弃用。使用 Drush 下载 Drupal 核心或贡献模块将抛出警告,建议使用 Composer。
以下命令将使用 Demo Framework 安装 Drupal 8 网站:
$ cd /path/to/drupal8
$ drush pm-download df
$ drush site-install df -db-url=mysql://user:pass@localhost/database
使用 Composer
目前,Drupal.org 不使用 Composer 打包发行版,这就是为什么在安装发行版时需要额外步骤来添加依赖项。许多发行版都提供了项目模板,以简化项目构建。
例如,以下命令将设置一个以 docroot 作为 Web 服务器文档根目录的 Demo Framework 网站,其中包含 Drupal 8:
$ composer create-project acquia/df-project df
项目模板可在 Acqua 的 GitHub 上找到,链接为 github.com/acquia/df-project/。
另一个发行版 Open Social 提供了自己的模板:
$ composer create-project goalgorilla/social_template
项目模板可在 github.com/goalgorilla/social_template 上找到。
参见...
-
请参阅 第十三章,Drupal CLI,了解 makefile 的信息。
-
请参阅 Drupal 上的发行版文档
www.drupal.org/documentation/build/distributions。 -
请参阅
glamanate.com/blog/managing-your-drupal-project-composer上的 使用 Composer 管理您的 Drupal 项目。 -
请参阅
glamanate.com/blog/managing-your-drupal-platform-drush上的 使用 Drush 管理您的 Drupal 平台。
安装模块和主题
Drupal 8 比之前的 Drupal 版本提供了更多的内置功能,让您可以以更少的努力做更多的事情。然而,Drupal 更吸引人的一个方面是扩展和定制的功能。
在本菜谱中,我们将下载并启用 Honeypot 模块 (www.drupal.org/project/honeypot),并告诉 Drupal 使用 Bootstrap 主题 (www.drupal.org/project/bootstrap)。Honeypot 模块为 Drupal 网站提供 Honeypot 和时间戳反垃圾邮件措施。此模块有助于保护表单免受垃圾邮件提交。Bootstrap 主题实现了 Bootstrap 前端框架,并支持使用 Bootswatch 风格来为主题 Drupal 网站。
本章的食谱将使用标准方式安装模块,即下载 Drupal.org 上可用的存档。截至 Drupal 8.2.0,通过 Composer 安装模块已成为可能,并且对于某些模块来说是必需的方法。使用 Composer 安装模块和主题的内容包含在本食谱的“更多内容...”部分,并强烈推荐。
准备工作
如果你之前使用过 Drupal,请注意文件夹结构已更改。模块、主题和配置文件现在放置在root目录下的相应文件夹中,而不是在sites/all下。有关开发者体验变更的更多信息,请参阅www.drupal.org/node/22336。
下载示例代码:您可以从您在www.packtpub.com的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册自己,以便将文件直接通过电子邮件发送给您。
如何操作...
让我们安装模块和主题:
-
访问
www.drupal.org/project/honeypot并下载 Honeypot 的最新 8.x 版本。 -
解压存档并将
honeypot文件夹放置在您的 Drupal 核心安装目录下的modules文件夹中:

-
在 Drupal 中登录并选择“扩展”选项以访问可用模块的列表。
-
使用搜索文本框输入
Honeypot。勾选复选框并点击“安装”。 -
一旦启用,再次搜索它。点击模块的描述将展开行并显示配置权限和模块设置的链接:

-
访问
www.drupal.org/project/bootstrap并下载 Bootstrap 的最新 8.x 版本。 -
解压存档并将
bootstrap文件夹放置在您的 Drupal 核心安装目录下的themes文件夹中:

-
在 Drupal 中,选择“外观”选项以管理您的 Drupal 主题。
-
滚动到页面底部并点击“安装”和“Bootstrap”下的“设置为默认”以启用并设置主题为默认:

它是如何工作的...
以下部分概述了安装模块或主题的流程以及 Drupal 如何发现这些扩展。
发现模块和主题
Drupal 扫描特定文件夹位置以识别由其目录中的.info.yml文件定义的模块和主题。以下为项目将被发现的顺序:
-
它们各自的核心文件夹(模块或主题)
-
当前安装的配置文件
-
根
modules或themes文件夹 -
当前站点目录(默认或当前域名)
模块安装
通过将模块放置在根目录下的 modules 文件夹中,我们允许 Drupal 发现该模块并允许其安装。当模块安装时,Drupal 将通过 module_installer 服务将其代码注册到系统中。该服务将检查所需的依赖项,并在需要时提示启用它们。配置系统将在安装时运行模块提供的任何配置定义。如果有冲突的配置项,模块将不会安装。
主题安装
主题是通过 theme_installer 服务安装的,并设置主题提供的任何默认配置,同时重建主题注册表。将主题设置为默认是在 system.theme.default 配置中更改到主题的机器名(在菜谱中将是 bootstrap)。
更多内容...
以下部分概述了安装模块或主题的步骤,并包括一些有关安装的附加信息。
使用 Composer 安装模块或主题
虽然这不是安装扩展的必需方式,但这应该成为你的默认方法。为什么?因为每个模块都是你项目中的一个依赖项,而且每个模块都可能有自己的依赖项。Composer 可以为你管理依赖项,或者你可以手动管理它们。你的时间和能力可能不会像 Composer 那样增长到规模。更不用说,它还提供了一个标准方式,让 PHP 项目能够相互操作和加载类。
你可以使用以下两个命令获取 Honeypot 模块和 Bootstrap:
$ cd /path/to/drupal8
$ composer require drupal/honeypot
$ composer require drupal/bootstrap
这里是一个贡献项目的示例,这些项目需要 Composer 进行安装,因为它们利用了整个 PHP 社区中的现有库:
-
Drupal Commerce
-
GeoIP
-
搜索 API Solr
-
实体打印
随着越来越多的模块集成了现有的 SDK 库,使用 Composer 的需求将会增加。
使用 Drush 安装模块
可以通过命令行使用 drush 下载和启用模块。复制菜谱的命令可能类似于以下内容:
$ drush pm-download honeypot
$ drush pm-enable honeypot
截至 Drush 9,它支持 Drupal 8.3+,本节已弃用。使用 Drush 下载 Drupal 核心或贡献模块将抛出警告,建议使用 Composer。
它将提示你确认你的操作。如果模块有依赖项,它将询问你是否想启用这些依赖项。
Drush 仅下载来自 Drupal.org 的存档。如果模块或主题需要第三方 PHP 库依赖项,这些依赖项将不会被下载或在 Drupal 的类自动加载过程中可用。
卸载模块
Drupal 8 中的一个重大变化是模块禁用和卸载过程。以前,模块首先被禁用,然后禁用后卸载。这造成了一个令人困惑的过程,它会禁用其功能,但不会清理任何数据库模式更改。在 Drupal 8 中,模块不能只是被禁用,而必须被卸载。这确保了当模块被卸载时,它可以安全地从代码库中删除。
如果一个模块不是另一个模块的依赖项,或者没有正在使用的配置项(例如字段类型),则可以卸载该模块。这可能会破坏安装的完整性。
使用标准安装,在删除文章内容类型上的所有Comment字段之前,无法卸载Comment模块。这是因为field类型正在使用中。
参考信息
-
参考第第四章,扩展 Drupal,了解在启用模块时设置默认值。
-
参考第第九章,配置管理 - 在 Drupal 8 中部署。
在 Drupal 8 中使用多站点
Drupal 提供了从单个 Drupal 代码库实例运行多个站点的功能。这个功能被称为多站点。每个站点都有一个独立的数据库;然而,存储在modules、profiles和themes中的扩展可以被所有站点安装。站点文件夹也可以包含它们自己的模块和主题。当提供时,这些只能由该站点使用。
default文件夹是在没有匹配域名时使用的默认文件夹。
准备工作
如果您将要使用多站点功能,您应该了解如何设置您的 Web 服务器上的虚拟主机配置。在这个菜谱中,我们将使用 localhost 下的两个子域名,称为dev1和dev2。
如何操作...
我们将通过 localhost 下的两个子域名来使用 Drupal 8 的多站点:
-
将
sites/example.sites.php复制到sites/sites.php。 -
在
sites文件夹内创建一个dev1.localhost和dev2.localhost文件夹。 -
将
sites/default/default.settings.php文件复制到dev1.localhost和dev2.localhost,并在各自的文件夹中作为settings.php使用:

-
前往
dev1.localhost并运行安装向导。 -
前往
dev2.localhost并验证您是否仍然有安装站点的选项!
它是如何工作的...
sites.php必须存在,多站点功能才能工作。默认情况下,您不需要修改其内容。sites.php文件提供了一种将别名映射到特定站点文件夹的方法。该文件包含了使用别名的文档。
DrupalKernel 类提供了 findSitePath 和 getSitePath 方法来发现站点文件夹路径。在 Drupal 的引导过程中,这被启动并读取传入的 HTTP 主机以从适当的文件夹加载正确的 settings.php 文件。然后加载并解析 settings.php 文件到 \Drupal\Core\Site\Settings 实例。这允许 Drupal 连接到适当的数据库。
还有更多...
让我们了解使用多站点的安全问题。
安全问题
如果你使用多站点,可能会有一些担忧。在 Drupal 站点上执行的任意 PHP 代码可能会影响共享相同代码库的其他站点。Drupal 8 标记了移除 PHP 过滤器 (www.drupal.org/docs/8/modules/php/overview) 模块,该模块允许站点管理员在管理界面中使用 PHP 代码。尽管这减少了管理员通过界面轻松运行 PHP 的各种方式,但它并没有完全减轻风险。例如,PHP 过滤器模块现在是一个贡献项目,可以被安装。
域名别名
sites.php 文件提供了一种添加域名别名的方法。当你使用多站点功能并在本地开发时,这可能很有用。一个简单的例子是为每个站点提供一个 local.alias。
如果你将 example.com 和 mycompany.com 作为不同的站点目录,以下映射将允许 local.example.com 和 local.mycompany.com 映射到这些目录:
<?php
$sites['example.com'] = 'example.com';
$sites['local.example.com'] = 'example.com';
$sites['mycompany.com'] = 'mycompany.com';
$sites['local.mycompany.com'] = 'mycompany.com';
参见...
- 参考 Drupal 的多站点文档,请访问
www.drupal.org/documentation/install/multi-site。
设置环境的工具
开始使用 Drupal 的一个初步障碍是本地开发环境。本食谱将介绍如何通过 Jeff Geerling 的 DrupalVM 项目设置。DrupalVM 是通过 Vagrant 运行的 VirtualBox 虚拟机,通过 Ansible 配置和部署。它将为你设置所有服务并构建一个 Drupal 安装。
幸运的是,你只需要在你的机器上安装 VirtualBox 和 Vagrant,DrupalVM 支持 Windows、macOS X 和 Linux。
准备工作
要开始,你需要安装 DrupalVM 所需的两个依赖项:
-
VirtualBox:
www.virtualbox.org/wiki/Downloads -
Vagrant:
www.vagrantup.com/downloads.html
如何操作...
让我们按照以下步骤设置 Jeff Geerling 的 DrupalVM 项目:
-
从
github.com/geerlingguy/drupal-vm/archive/master.zip下载 DrupalVM 存档。 -
解压存档并将项目放置在你选择的目录中。
-
将
example.drupal.make.yml复制到drupal.make.yml。 -
将
default.config.yml复制到config.yml。 -
编辑
config.yml并修改local_path设置,使其成为您放置 DrupalVM 项目的目录。这将同步到虚拟机中:
vagrant_synced_folders:
local_path: /path/to/drupalvm
destination: /var/www
type: nfs
create: true
-
打开终端并导航到您放置文件的目录。
DrupalVM 项目。
-
输入
vagrant up命令来告诉 Vagrant 构建虚拟机并开始配置过程。 -
在此过程进行时,修改您的 hosts 文件以提供对开发站点的便捷访问。将
192.168.88.88 drupalvm.dev行添加到您的 hosts 文件中。 -
打开您的浏览器并访问
www.drupalvm.com/。 -
使用用户名
admin和密码admin登录到您的 Drupal 站点。
工作原理...
DrupalVM 是一个开发项目,它利用 Vagrant 工具创建一个 VirtualBox 虚拟机。Vagrant 通过项目的Vagrantfile进行配置。然后,Vagrant 使用 Ansible(一个开源的 IT 自动化平台)在虚拟机上安装 Apache、PHP、MySQL 和其他服务。
config.yml文件已经设置好,提供了一种简单的方式来自定义虚拟机和配置过程中的变量。它还使用 Drush 创建和安装 Drupal 8 站点,或者drupal.make.yml中指定的任何组件。此文件是一个 Drush make文件,默认包含 Drupal 核心的定义,并且可以修改以包含其他贡献项目。
vagrant up命令告诉 Vagrant 要么启动一个现有的虚拟机,要么以无头方式创建一个新的虚拟机。当 Vagrant 创建一个新的虚拟机时,它会触发配置过程。在这种情况下,Ansible 将读取provisioning/playbook.yml文件,并遵循每个步骤来创建最终的虚拟机。然而,需要修改的文件只有config.yml和drupal.make.yml文件。
更多内容...
自动化和简化本地环境的主题目前非常流行,有相当多的不同选项。如果你不习惯使用 Vagrant,还有一些其他选项可以提供服务器安装和 Drupal。
Acquia Dev Desktop
Acquia Dev Desktop 由 Acquia 开发,可在docs.acquia.com/dev-desktop2找到。它是一个 Windows 和 Mac 的自动化环境安装程序。它是一个 xAMP 堆栈(或 DAMP 堆栈)安装程序,提供包含 Apache、MySQL 和 PHP 的完整 Drupal 特定堆栈。Dev Desktop 应用程序允许您创建一个常规的 Drupal 安装或从分发版中选择。
XAMPP + Bitnami
XAMPP - Apache + MySQL + PHP + Perl - 是一个跨平台环境安装。XAMPP 是 Apache Friends 的开源项目。XAMPP 与 Bitnami (bitnami.com/) 合作,为常见应用程序提供免费的集成安装,包括 Drupal 8。您可以在 www.apachefriends.org/download.html 下载 XAMPP。
Kalabox
Kalabox 由 Kalamuna 团队开发,旨在成为 Drupal 开发的强大工作流程解决方案。Kalabox 兼容跨平台,允许您轻松地在 Windows 机器上工作。它基于命令行,并提供应用程序二进制文件供您安装。您可以在 www.kalamuna.com/products/kalabox/ 上了解更多关于 Kalabox 的信息。
参见
-
请参阅 第十三章,Drupal CLI,了解 makefile 的信息。
-
DrupalVM 文档
docs.drupalvm.com/en/latest/。 -
请参考 Drupal.org 社区文档中的本地环境设置信息,链接为
www.drupal.org/node/157602。
运行测试 - Simpletest 和 PHPUnit
Drupal 8 随带两个测试套件。之前,Drupal 只支持 Simpletest。现在,还有 PHPUnit 测试。在官方变更记录中,PHPUnit 被添加以提供无需完整 Drupal Bootstrap 的测试,这在每次 Simpletest 测试时都会发生。您可以在 www.drupal.org/node/2012184 阅读变更记录。
目前在 Drupal 核心开发中有一个活跃的 PHPUnit 创新项目。目标是到 Drupal 9 时完全移除 Simpletest 框架。至少从 8.2 版本开始,不再编写新的 Simpletest 测试。所有当前测试正在由贡献者转换。更多关于这个项目的信息可以在这个问题中找到,www.drupal.org/node/2807237,那里正在进行协调。
我们将使用 run-tests.sh 测试运行器来运行测试。这是一个由 Drupal 提供的测试运行器,支持并发运行所有各种测试套件。在接下来的 还有更多... 部分将介绍如何直接使用 PHPUnit 运行测试。
准备工作
Drupal 8.1.0 引入了执行 JavaScript 浏览器测试的能力。这是使用 PhantomJS (phantomjs.org/) 实现的,它使用由 Mink PHP 库 (mink.behat.org/) 提供的浏览器模拟器。为了运行 FunctionalJavascript 测试套件,您必须运行 PhantomJS。
要安装 PhantomJS,请参考官方安装说明,链接为 phantomjs.org/download.html。
如何操作...
-
首先,安装
Simpletest模块。即使你可能只想运行 PHPUnit,这也是运行测试运行器脚本的软依赖。 -
打开命令行终端,导航到您的 Drupal 安装目录。
-
接下来,我们将运行测试运行器脚本。我们将传递一个
url选项,以便功能测试可以正确运行浏览器模拟器。我们还将指定要运行的测试套件。这允许我们跳过功能 JavaScript 测试,因为 PhantomJS 在测试运行器中无法正确处理并发:
$ php core/scripts/run-tests.sh **--url** http://localhost--types Simpletest,PHPUnit-Unit,PHPUnit-Kernel,PHPUnit-Functional **--concurrency 20 --all**
- 运行
FunctionalJavascripts测试需要 PhantomJS 正在运行。由于 PhantomJS 将输出打印到终端,请打开一个新标签或终端并运行以下命令:
phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768
- 当 PhantomJS 运行时,我们现在可以执行功能 JavaScript 测试套件:
**php core/scripts/run-tests.sh --url** http://localhost--types PHPUnit-FunctionalJavascript --concurrency 1 --all
- 查看每个测试套件运行的测试输出。
它是如何工作的...
run-tests.sh脚本自 2008 年以来一直与 Drupal 一起提供,当时命名为
run-functional-tests.php。此命令与 Drupal 中的测试套件交互,运行所有或特定测试,并设置其他配置项。
有几个不同的测试套件,它们以特定的方式运行:
-
Simpletest:已弃用的测试系统,完全引导并安装 Drupal,并使用 curl 和 XPath 的自己的浏览器模拟模式。
-
PHPUnit-Unit:由 PHPUnit 支持的单元测试。这些测试旨在测试特定的类,而不与数据库交互。
-
PHPUnit-Kernel:由 PHPUnit 支持的集成级别测试。这是一个能够将模式配置安装到数据库中的测试,它以最小化引导 Drupal 进行基本集成测试。
-
PHPUnit-Functional:功能测试是需要在完全引导的 Drupal 中运行的测试,并通过 Mink 提供浏览器模拟。这些可以被认为是 Simpletest 测试的直接替代品,但利用第三方测试库。
-
PHPUnit-FunctionalJavascript:具有与 PhantomJS 交互的能力的功能测试,以测试 JavaScript,例如 AJAX 操作和特定的用户界面交互。
以下是一些有用的选项:
-
--help:此选项显示以下要点涵盖的内容 -
--list:此选项显示可以运行的可用测试组 -
--url:除非 Drupal 站点可通过http://localhost:80访问,否则此选项是必需的 -
--sqlite:这允许您在没有安装 Drupal 的情况下运行测试 -
--concurrency:这允许你定义并行运行的测试数量
还有更多...
我们现在将讨论更多关于运行 Drupal 测试套件的技巧和信息。
run-tests 是一个 shell 脚本吗?
run-tests.sh实际上不是一个 shell 脚本。它是一个 PHP 脚本--这就是为什么你必须用 PHP 来执行它。实际上,在core/scripts中,每个文件都是一个 PHP 脚本文件,旨在通过命令行执行。这些脚本不是旨在通过 Web 服务器运行,这是.sh扩展名的原因之一。
在跨平台之间可能会有问题,这阻止了提供 shebang 行以允许将文件作为正常的 bash 或 bat 脚本执行。有关更多信息,请参阅此 Drupal.org 问题 www.drupal.org/node/655178。
在未安装 Drupal 的情况下运行测试
在 Drupal 8 中,测试也可以从 SQLite 运行,不再需要安装数据库。这可以通过将 sqlite 和 dburl 选项传递给
run-tests.sh 脚本。这需要安装 PHP SQLite 扩展。
这里是一个从 DrupalCI 测试运行器改编的示例,用于 Drupal 核心测试。DrupalCI 是持续集成服务,它在 Drupal.org 上为所有提交的补丁和提交运行:
php core/scripts/run-tests.sh --sqlite /tmp/.ht.sqlite --die-on-fail --dburl sqlite://tmp/.ht.sqlite --all
结合内置的 PHP 网络服务器进行调试,您可以在没有完整环境的情况下运行测试套件。
运行特定测试
到目前为止的每个示例都使用了 all 选项来运行所有可用的 Simpletest。有各种方法来运行特定的测试:
-
--module: 这允许您运行特定模块的所有测试 -
--class: 运行一个由完整命名空间路径标识的特定路径 -
--file: 从指定的文件运行测试 -
--directory: 在指定的目录内运行测试
在 Drupal 中,测试以前是分组在 module.test 文件中,这也是文件选项的来源。Drupal 8 使用 PSR-4 自动加载 方法,并要求每个文件有一个类。
PhpStorm - Drupal 测试运行器
Drupal 8 在 Drupal 核心和贡献项目中的测试覆盖率都有所增加,这很可能是由于 PHPUnit 的采用。作为回应,作者编写了一个名为 Drupal Test Runner 的 PhpStorm 插件,该插件简化了执行 run-tests.sh 脚本运行器的操作。
插件的页面可以在 plugins.jetbrains.com/plugin/8384-drupal-test-runner 找到,其公开源代码可以在 github.com/mglaman/intellij-drupal-run-tests 找到。
DrupalCI
随着 Drupal 8 的推出,一个新的举措是为了升级 Drupal.org 上的测试基础设施。结果是 DrupalCI。DrupalCI 是开源的,可以下载并在本地运行。DrupalCI 的项目页面是 www.drupal.org/project/drupalci。
测试机器人使用 Docker,可以本地下载以运行测试。项目附带一个 Vagrant 文件,允许它在虚拟机或本地运行。有关测试机器人的更多信息,请访问其项目页面 www.drupal.org/project/drupalci_testbot。
参见...
-
参考 PHPUnit 手册
phpunit.de/manual/4.8/en/writing-tests-for-phpunit.html。 -
参考 Drupal PHPUnit 手册
drupal.org/phpunit。 -
请参考从命令行运行测试。
-
请参考《Commerce 2.x:单元、内核和功能测试,哦,我的天!》作者博客文章和教程,了解如何在 Drupal 8 中为 Drupal Commerce 运行测试,链接为
drupalcommerce.org/blog/45322/commerce-2x-unit-kernel-and-functional-tests-oh-my。
第二章:内容创作体验
在本章中,我们将探讨 Drupal 8 为内容创作体验带来的变化:
-
配置所见即所得编辑器
-
添加和编辑内容
-
创建菜单并链接内容
-
提供内联编辑
-
创建自定义内容类型
-
应用新的 Drupal 8 核心字段类型
-
自定义节点表单显示
-
自定义节点显示输出
简介
在本章中,我们将介绍 Drupal 8 的内容创作体验。我们将向您展示如何配置文本格式并设置随 Drupal 8 一起提供的捆绑 CKEditor。我们将探讨如何添加和管理内容以及如何使用菜单链接到内容。Drupal 8 提供了前端对每个字段的修改的内联编辑。
本章深入探讨创建自定义内容类型并利用不同的字段创建高级内容。我们将介绍添加到 Drupal 8 核心的五个新字段以及如何使用它们,以及通过贡献项目配置新字段类型。我们将探讨自定义内容的显示以及修改 Drupal 8 中添加的新表单显示。
配置所见即所得编辑器
Drupal 8 见证了 Drupal 开发社区和 CKEditor 开发社区的协作。因此,Drupal 现在默认附带 CKEditor 作为所见即所得(WYSIWYG)编辑器。新的编辑器模块提供了一个 API 来集成所见即所得编辑器。尽管 CKEditor 默认提供,但贡献模块可以提供与其他所见即所得编辑器的集成。
文本格式控制内容格式化和内容作者所见即所得编辑器配置。标准的 Drupal 安装配置文件提供了一个完全配置的文本格式,启用了 CKEditor。我们将逐步介绍重新创建此文本格式的步骤。
在本食谱中,我们将创建一个新的文本格式,并带有自定义 CKEditor WYSIWYG 配置。
准备工作
在开始之前,请确保已启用 CKEditor 模块,这还要求启用编辑器
作为依赖项。编辑器是提供 API 以集成所见即所得编辑器的模块
与文本格式一起。
如何操作...
让我们创建一个新的文本格式,并带有自定义 CKEditor WYSIWYG 配置:
-
从管理工具栏访问“配置”并转到“内容创作”下的“文本格式和编辑器”。
-
点击“添加文本格式”开始创建新的文本格式:

-
为文本格式输入一个名称,例如编辑器格式。
-
选择哪些角色可以访问此格式--这允许你对用户在创作内容时可以使用的内容进行细粒度控制。
-
从文本编辑器选择列表中选择 CKEditor。然后,将加载 CKEditor 的配置表单。
-
您现在可以使用内联编辑器将按钮拖放到提供的工具栏中,以配置您的 CKEditor 工具栏!:![img/f3f5e25d-ee6a-47aa-a1eb-3cd534782d4f.png]
-
选择你想要的任何启用过滤器,除了“显示任何 HTML 为纯文本”。这在使用所见即所得编辑器时可能会让人感到不直观!
-
当你满意时,点击“保存配置”以保存你的配置并创建文本过滤器。
它是如何工作的...
过滤器模块提供了控制如何向用户展示富文本字段的文本格式。Drupal 将根据字段的定义文本格式渲染在文本区域中保存的富文本。标题中包含“格式化”的文本字段将尊重文本格式设置;其他字段将以纯文本形式渲染。
文本格式和编辑器屏幕警告由于配置不当存在安全风险。这是因为你可能会授予匿名用户访问允许完整 HTML 或允许图像来源来自远程 URL 的文本格式的权限。
编辑器模块提供了一个连接到 WYSIWYG 编辑器和文本格式的桥梁。它修改文本格式表单和渲染,以允许集成 WYSIWYG 编辑器库。这使得每个文本格式都可以为其 WYSIWYG 编辑器拥有自己的配置。
默认情况下,编辑器模块本身不提供编辑器。CKEditor 模块与编辑器 API 一起工作,以启用 WYSIWYG 编辑器的使用。
Drupal 可以通过贡献模块支持其他 WYSWIG 编辑器,例如 markItUp (markitup.jaysalvat.com/home/) 或 TinyMCE (www.tinymce.com/)。
还有更多...
Drupal 提供了对富文本渲染的细粒度控制,以及可扩展的方式,我们将在后面进一步讨论。
过滤器模块
当将字符串数据添加到支持文本格式的字段时,数据将按原始输入保存并保留。对于文本格式的启用过滤器将在内容查看时才应用。Drupal 以保存原始内容并在显示时进行过滤的方式工作。
启用过滤器模块后,你可以指定基于创建文本的用户角色如何渲染文本。理解使用 WYSIWYG 编辑器的文本格式中应用的过滤器非常重要。例如,如果你选择了“显示任何 HTML 为纯文本”选项,当查看时,WYSIWYG 编辑器所做的格式化将被移除。
改进的链接
WYSIWYG 编辑的一个主要组成部分是插入指向其他内容或外部网站的链接的能力。与 CKEditor 集成的默认链接按钮允许进行基本的链接嵌入。这意味着你的内容编辑必须在事先知道内部内容 URL 的情况下才能链接到它们。解决这个问题的解决方案是 Linkit 模块,位于www.drupal.org/project/linkit。
可以通过运行以下命令使用 Composer 安装该模块:
$ cd /path/to/drupal8
$ composer require drupal/linkit
Linkit模块提供了对默认链接功能的直接替换。它增加了对内部内容的自动完成搜索,并为显示字段添加了额外的选项。Linkit通过创建不同的配置文件来实现,允许您控制可以引用的内容、可以管理的属性以及哪些用户和角色可以使用 Linkit 配置文件。

CKEditor 插件
CKEditor模块提供了一个名为CKEditorPlugin的插件类型。插件是 Drupal 8 中可互换功能的小块。插件和插件开发在第七章,插件即插即用中有所介绍。此类型提供了CKEditor和 Drupal 8 之间的集成。
图片和链接功能是定义在CKEditor模块内的插件。可以通过贡献项目或自定义开发提供额外的插件。
请参阅\Drupal\ckeditor\Annotation\CKEditorPlugin类以获取插件定义,以及作为工作示例的\Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImage类。
参见
-
关于 Drupal 如何采用
CKEditor作为官方 WYSIWYG 编辑器的官方博客文章,请参阅ckeditor.com/blog/CKEditor-Joins-Drupal。 -
请参阅第七章,插件即插即用。
添加和编辑内容
内容管理系统的主要功能在其名称本身中就体现出来了——管理内容的能力;也就是说,添加、编辑和组织内容。Drupal 提供了一个中央表单,允许您管理网站内的所有内容,并允许您创建新内容。此外,您可以在查看内容时查看内容,并点击编辑链接。
准备工作
此菜谱假设您已安装标准安装配置文件,并且可以使用默认的节点内容类型。
如何操作...
通过添加、编辑和组织内容来管理内容:
-
前往内容以查看内容管理概述。
-
点击添加内容以查看可用的内容类型列表。
-
点击文章作为您想要制作的内容。
-
为内容提供标题。内容标题总是必需的。填写文章的主体文本:

您可以更改文本格式以自定义允许的文本类型。如果用户只有一个格式可用,则不会有选择框,但“关于文本格式”链接仍然存在。
-
在您添加文本后,点击表单底部的保存并发布。然后您将被重定向到查看新创建的内容。
-
注意,内容 URL 为
/node/#。这是内容的默认路径,可以通过编辑内容进行更改。 -
点击内容上方右侧的“编辑”标签页。
-
从右侧边栏中,点击“URL 路径设置”以展开部分并输入自定义别名,例如
/awesome-article(注意所需的/符号):

-
保存内容,并注意您的文章 URL 为
/awesome-article。 -
你也可以通过点击内容表中的“编辑”来编辑这篇文章,而不是从查看内容处编辑。
它是如何工作的...
内容页面是一个视图,将在第三章,通过视图显示内容中讨论。这创建了一个包含你网站上所有内容的表格,可以进行搜索和筛选。从这里,你可以查看、编辑或删除任何单个内容。
在 Drupal 中,有内容实体提供创建、编辑、删除和查看的方法。节点是一种内容实体。当你创建一个节点时,它将构建适当的表单,允许你填写内容的数据。编辑内容的过程遵循相同的步骤。
当你保存内容时,Drupal 将节点的内容及其所有相关字段数据写入数据库。
还有更多...
Drupal 8 的内容管理系统提供了许多功能;我们将涵盖一些额外信息。
保存为草稿
Drupal 8 新增了轻松将内容保存为草稿而不是直接发布的功能。不是点击“保存并发布”,而是点击旁边的箭头以展开“保存为未发布”选项:

上述按钮有几项可用性和用户体验审查,将在 Drupal 的未来版本中改进。要跟踪的一个问题位于www.drupal.org/node/1899236。该问题强调了根据现有前端库中定义的统一用户体验模式提出的不同修复方案。
Pathauto
有一个名为 Pathauto 的捐赠项目,它简化了提供 URL 别名的过程。它允许你定义模式,这些模式将自动为内容创建 URL 别名。此模块利用标记来允许内容拥有非常健壮的路径。
Pathauto 项目可以在www.drupal.org/project/pathauto找到。
有一个提议要为 Drupal 核心提供 Pathauto 的功能,可以在www.drupal.org/node/229568跟踪。
批量管理
你还可以对内容执行批量操作。这在内容管理屏幕上可用。列出网站内容的表格在每个行的开头都有复选框。对于每个选定的项目,你可以从“与选择一起”中选择一个项目来执行批量更改,例如删除、发布和取消发布内容:

参见
- 参考本章的自定义节点表单显示配方。
创建菜单和链接内容
Drupal 提供了一种将正在创建的内容链接到网站上的指定菜单的方法,通常是主菜单。然而,您也可以创建一个自定义菜单以提供内容链接。在本教程中,我们将向您展示如何创建自定义菜单并将内容链接到它。然后,我们将把菜单作为一个块放置在页面的侧边栏中。
准备工作
本配方假设您已安装标准安装配置文件,并且有默认节点内容类型可供使用。您应该有一些内容创建以创建链接。
如何操作...
-
访问结构并点击菜单。
-
点击添加菜单。
-
提供标题“侧边栏”和可选摘要,然后点击保存。
-
保存菜单后,点击添加链接按钮。
-
输入链接标题,然后输入内容的标题。表单将提供可链接内容的自动完成建议:

-
点击保存以保存菜单链接。
-
保存菜单链接后,转到结构,然后是块布局。
-
首先点击侧边栏旁边的放置块。在模态窗口中,搜索侧边栏菜单并点击放置块:

-
在以下表单中,点击保存块。
-
通过点击管理菜单中的“首页”来查看您的 Drupal 网站,您将看到以下菜单:

工作原理...
菜单和链接是 Drupal 核心的一部分。创建自定义菜单和菜单链接的能力是通过菜单 UI模块提供的。此模块在标准安装配置文件中已启用,但在其他配置文件中可能未启用。
菜单链接表单的链接输入允许您开始键入节点标题并轻松地将它们链接到现有内容。这是以前 Drupal 版本中不可用的功能。它将自动将标题转换为内部路径。链接输入还接受常规路径,例如/node/1或外部路径。
您必须有一个有效的路径;您不能向菜单添加空链接。正在进行工作以允许添加空或 ID 选择器链接路径--请参阅www.drupal.org/node/1543750。
更多...
链接可以通过内容编辑表单本身进行管理,这将在下一部分介绍。
从其表单管理内容菜单链接
一块内容可以从添加或编辑表单链接到菜单。菜单设置部分允许您切换菜单链接的可用性。默认情况下,菜单链接标题将反映内容的标题。
父项目允许您决定它将出现在哪个菜单和哪个项目下。默认情况下,内容类型仅允许主菜单。编辑内容类型可以允许使用多个菜单或仅选择自定义菜单。
这允许您填充主菜单或辅助菜单,而无需访问菜单管理屏幕。
提供内联编辑
Drupal 8 的一个备受推崇的功能是提供内联编辑。内联编辑通过标准安装配置文件默认启用,通过快速编辑模块实现。快速编辑模块允许在查看内容时编辑单个字段,并将其与编辑器模块集成以实现 WYSIWYG 编辑器!
如何操作...
让我们提供内联编辑:
-
前往一个已创建的内容页面。
-
为了启用内联编辑,您必须通过在管理工具栏右上角点击“编辑”来切换页面上的上下文链接:

- 这将切换页面上的上下文链接。点击内容旁边的上下文链接并选择快速编辑:

- 将鼠标悬停在正文文本上并点击“编辑”。现在您可以使用 WYSIWYG 编辑器工具栏的简化版本来编辑文本:

-
修改文本后,点击保存。
-
变更将立即保存。
它是如何工作的...
上下文链接模块为特权用户提供修改块或内容的快捷链接。上下文链接通过在工具栏中点击“编辑”来切换。编辑链接切换页面上的上下文链接的可见性。在 Drupal 7 中,上下文链接在悬停特定区域时显示为齿轮。
快速编辑模块建立在上下文链接功能之上。它允许字段格式化器,用于显示字段数据,描述它们将如何交互。默认情况下,快速编辑将其设置为表单。点击一个元素将使用 JavaScript 加载表单并通过 AJAX 调用保存数据。
快速编辑功能不适用于管理页面。
更多...
随着 Drupal 8 的每个小版本发布,内联编辑体验都有更多改进。
从外向内的方法
Drupal 8.2 中有一个实验性模块允许您从网站的前端编辑块和其他网站配置,就像内容的快速编辑一样。要启用此功能,请安装设置托盘模块。
当您浏览 Drupal 网站时,您会在工具栏左侧注意到一个新的编辑按钮。点击此按钮将允许您编辑块和网站配置。

更多信息,请参阅 Drupal.org 手册中关于此功能的文档,网址为www.drupal.org/docs/8/core/modules/outside-in/overview。
创建自定义内容类型
Drupal 在内容管理领域表现出色,允许不同类型的内容。在这个菜谱中,我们将向您展示如何创建自定义内容类型。我们将创建一个具有一些基本字段并用于关注公司提供服务的场景的服务类型。
您还将学习如何在菜谱中向内容类型添加字段,这通常与在 Drupal 网站上创建新的内容类型相辅相成。
如何操作...
-
前往结构,然后内容类型。点击添加内容类型以开始创建新的内容类型。
-
输入服务作为名称,并可选地输入描述。
-
选择显示设置,取消选中显示作者和日期信息复选框。这将隐藏作者和提交时间从服务页面:

-
点击保存和管理字段按钮以保存新的内容类型并管理其字段。
-
默认情况下,新内容类型会自动添加一个正文字段。我们将保留这个字段。
-
我们将添加一个字段,提供一种输入服务营销标题的方法。点击添加字段。
-
从下拉菜单中选择文本(纯文本),并将营销标题作为标签输入:

文本(纯文本)选项是一个常规文本字段。文本(格式化)选项将允许您在字段中显示的文本上使用文本格式。
-
在下一个表单上点击保存字段设置。在随后的表单上,点击保存设置以完成字段的添加。
-
字段现在已经添加,可以创建此类内容:

工作原理...
在 Drupal 中,有一些实体具有捆绑包。捆绑包只是可以附加特定配置和字段的一种实体类型。当与节点一起工作时,捆绑包通常被称为内容类型。
只要启用节点模块,就可以创建内容类型。当通过用户界面创建内容类型时,它会调用 node_add_body_field() 函数。此函数为内容类型添加默认的正文字段。
只有在启用字段 UI 模块的情况下,才能管理或添加字段。字段 UI 模块公开了实体(如节点和块)的管理字段、管理表单显示和管理显示。
应用新的 Drupal 8 核心字段类型
字段系统是使在 Drupal 中创建内容如此强大的原因。在 Drupal 8 中,一些最常用的贡献字段类型已经被合并到 Drupal 核心作为它们自己的模块。实际上,实体引用不再是一个模块,而是现在已经是主字段 API 的一部分了。
这个菜谱实际上是一系列迷你菜谱,用于突出 Drupal 8 核心提供的新字段:链接、电子邮件、电话、日期和实体引用。
准备工作
标准安装配置文件没有启用提供这些字段类型的所有模块。对于此配方,您需要手动启用选择模块,以便您可以创建字段。提供字段类型及其在标准配置文件中的安装状态的模块将被突出显示。
每个配方都将开始假设您已启用模块(如果需要),并且您正在内容类型的“管理字段”表单中,已点击“添加字段”并提供了字段标签。这里的配方涵盖了每个字段的设置。
如何做到这一点...
本节包含一系列迷你配方,展示了如何使用每个新的核心字段类型。
链接
链接字段由链接模块提供。它默认安装在标准安装配置文件中。它是菜单 UI、自定义菜单链接和快捷方式模块的依赖项。
-
链接字段类型在所有捆绑包中都没有任何额外的字段级别设置。
-
点击“保存字段设置”以自定义此特定捆绑包的字段。
-
使用允许的链接类型设置,您可以控制提供的 URL 是否可以是外部链接、内部链接或两者都可以。选择内部或两者都会允许通过自动完成标题来链接到内容。
-
允许链接文本定义用户是否必须提供与链接一起的文本。如果没有提供文本,则显示 URL 本身。
-
链接字段的字段格式化器允许您指定
rel="nofollow"或链接是否应在新窗口中打开。
电子邮件字段
电子邮件字段由 Drupal 核心提供,无需安装任何附加模块即可使用:
-
电子邮件字段类型在所有捆绑包中都没有任何额外的字段级别设置。
-
点击“保存字段设置”以自定义此特定捆绑包的字段。
-
对于电子邮件字段实例,没有更多的设置。此字段使用 HTML5 电子邮件输入,将利用浏览器输入验证。
-
电子邮件字段的字段格式化器允许您将电子邮件显示为纯文本或
mailto:链接。
电话字段
电话字段由电话模块提供。它默认不与标准安装配置文件一起安装,必须通过扩展表单安装。
-
电话字段类型在所有捆绑包中都没有任何额外的字段级别设置。
-
点击“保存字段设置”以自定义此特定捆绑包的字段。
-
对于电话字段实例,没有更多的设置。此字段使用 HTML5 电话输入,将利用浏览器输入验证。
-
电话字段的字段格式化器允许您将电话号码显示为纯文本项,或使用带有可选替换标题的
tel:link。
日期字段
日期字段由日期时间模块提供。它默认与标准安装配置文件一起启用。
-
日期模块有一个设置,用于定义它将存储哪种类型的数据:日期和时间,或仅日期。一旦字段数据已保存,此设置就不能更改。
-
点击“保存字段设置”以自定义此特定捆绑包的字段。
-
日期字段有两种提供默认值的方式。它可以是当前日期或使用 PHP 的日期时间修改符语法表示的相对日期。
-
默认情况下,日期字段使用 HTML5 日期和时间输入,从而提供浏览器提供的原生日期和时间选择器。
-
此外,日期字段可以配置为为每个日期和时间组件使用选择列表:

-
默认日期字段格式化器显示使用 Drupal 的时间格式来渲染时间格式。这些配置在“配置”和“区域和语言”下的日期和时间格式表中进行。
-
日期和时间可以显示为“时间前”,以提供对时间在未来或过去距离的语义显示。这两种格式的显示设置都是可定制的。
-
最后,日期和时间可以使用 PHP 日期格式指定的自定义格式进行显示。
实体引用字段
实体引用字段是 Drupal 核心的一部分,无需启用其他模块即可使用。与其他字段不同,实体引用在添加字段时显示为特定项目的分组。这是因为您必须选择要引用的实体类型。请按照以下步骤操作:
-
界面允许您选择内容、文件、图像、分类术语、用户或其他。选择预定义选项将预配置字段的目标实体类型。
-
当使用“其他”选项创建实体引用字段时,您必须指定要引用的项目类型。一旦您的数据已保存,此选项就不能更改。
您会注意到有两个组:内容和配置。Drupal 使用配置实体。尽管配置是一个选项,您可能不会从引用这些实体类型中受益。只有内容实体才有被查看的方式。引用配置实体将属于高级用例实现。
-
点击“保存字段设置”以自定义此特定捆绑包的字段。
-
实体引用字段有两种不同的方法允许用户搜索内容:使用默认的自动完成或视图。
-
根据您引用的实体类型,将会有不同的实体属性,您可以根据这些属性对结果进行排序。
-
实体引用字段的默认字段小部件是自动完成,然而,您可以选择使用选择列表或复选框来显示可用选项。
-
实体引用字段的值可以显示引用实体的标签或渲染后的输出。在渲染标签时,可以可选地将其链接到实体本身。在显示渲染后的实体时,您可以选择特定的视图模式。
它是如何工作的...
在 Drupal 8 中处理字段时,有两个步骤需要注意。当你第一次创建一个字段时,你正在定义一个要保存的基本字段。这种配置是一个基础配置,它指定了一个字段可以支持多少个值以及是否由字段类型定义了任何额外的设置。当你将字段附加到一个捆绑包上时,它被视为一个字段存储,并包含特定捆绑包的独特配置。如果你在文章和页面内容类型上都有相同的链接字段,那么标签、链接类型和链接文本设置是针对每个实例的。
每个字段类型都提供了一种存储和呈现特定类型数据的方法。使用这些字段的优点来自于验证和数据操作。它还允许你利用 HTML5 表单输入。使用 HTML5 的电话、电子邮件和日期,作者体验使用浏览器提供的工具而不是额外的第三方库。这也为使用移动设备进行创作提供了更原生体验。
还有更多...
Drupal 8 发布时带有新字段,这是将广泛使用的贡献模块集成到 Drupal 核心中的重大改进。在接下来的章节中,我们将介绍更多的改进和一些额外的主题。
即将更新的内容
每个配方都涵盖了一个曾是贡献项目空间一部分的字段类型。这些项目在撰写本书时提供了比 Drupal 核心更多的配置选项。随着时间的推移,越来越多的功能将从它们的源项目中引入到 Drupal 核心中。
例如,Datetime 模块基于贡献的 Date 项目。然而,并非所有贡献项目的功能都进入了 Drupal 核心。Drupal 8 的每个小版本都看到更多功能被移动到核心。一个例子是 Datetime 范围模块,这是一个计划在 Drupal 8.4 附近稳定的实验性模块。此模块为 Datetime 字段添加了对开始日期和结束日期的支持。Datetime 范围模块的文档可以在www.drupal.org/docs/8/core/modules/datetime-range找到。
查看和实体引用
在第三章中,通过视图显示内容中涵盖了使用实体引用字段查看的内容。使用查看,你可以自定义获取引用字段结果的方式。
参见
- 请参阅第三章中的提供实体引用结果视图配方,通过视图显示内容。
自定义节点表单显示
Drupal 8 的最新进展是表单显示模式的可用性。表单模式允许网站管理员为每个内容实体捆绑包的编辑表单配置不同的字段配置。在节点的情况下,你有能力重新排列和改变节点编辑表单上字段和属性显示。
在本食谱中,我们将修改标准安装配置文件中包含的文章内容类型的默认表单:

如何操作...
-
要自定义表单显示模式,请转到结构,然后是内容类型。
-
我们将修改文章内容类型的表单。点击展开操作下拉菜单,并选择管理表单显示。

- 首先,我们将修改评论字段。点击左侧的拖动图标,将行拖入禁用部分。对于列表顶部的固定选项,遵循相同的步骤:

- 点击正文字段的设置齿轮。输入字段的占位符,例如
在此处输入您的文章文本。点击更新。
占位符仅会在使用不提供所见即所得编辑器的文本格式的textarea中显示。
-
点击页面底部的保存按钮以保存您的更改。您现在已自定义了表单显示。
-
前往内容 | 添加内容,然后转到文章。请注意,评论设置不再显示,推广选项下的固定选项也不再显示:

它是如何工作的...
Drupal 中的实体为每个捆绑包提供各种视图模式。在 Drupal 7 中,只有显示视图模式,这将在下一食谱中介绍。Drupal 8 引入了新的表单模式,以允许更精确地控制实体编辑表单的显示方式。
表单显示模式是配置实体。表单显示模式决定了当编辑实体时\Drupal\Core\EntityContentEntityForm类将如何构建表单。这始终设置为默认值,除非更改或以编程方式指定为不同的模式。
由于表单显示模式是配置实体,因此可以使用配置管理进行导出。
隐藏字段属性除非提供默认值,否则将没有价值。例如,如果你隐藏了作者信息但没有提供设置默认值的代码,内容将由匿名用户(无用户)创建。
还有更多...
在下一节中,我们将讨论更多关于管理内容实体表单的内容。
管理表单显示模式
所有实体的表单显示模式都在一个区域中管理,并为每个捆绑包类型启用。您必须首先创建显示模式,然后可以通过捆绑包管理界面进行配置。
以编程方式为隐藏表单项提供默认值
在第六章,使用表单 API 创建表单中,我们将有一个食谱详细说明如何修改表单。为了为在表单显示中隐藏的实体属性提供默认值,您需要修改表单并提供默认值。字段 API 提供了一种在创建字段时设置默认值的方法。
参见
-
参考第第十章,实体 API。
-
参考第第六章,使用表单 API 创建表单。
自定义节点显示输出
Drupal 提供了显示视图模式,允许自定义附加到实体的字段和其他属性。在本教程中,我们将调整文章的摘要显示模式。每个字段或属性都有一个用于显示标签、显示信息的格式以及格式的额外设置的控件。
利用视图显示,您可以完全控制内容在您的 Drupal 站点上的查看方式。
如何做到这一点...
-
现在,是时候通过导航到结构然后内容类型来自定义表单显示模式了。
-
我们将修改文章内容类型的显示。点击下拉按钮箭头并选择管理显示。
-
点击“摘要视图模式”选项来修改它。摘要视图模式用于节点列表中,例如默认的首页:

-
将标签的格式更改为“隐藏”。此外,这也可以通过将其拖动到隐藏部分来完成。在查看摘要视图模式时,文章上的标签将不再显示。
-
点击正文字段的设置齿轮来调整截断限制。截断限制是当没有提供
textarea字段的摘要时作为摘要或截断的回退。将其从600更改为300。 -
点击“保存”以保存您所做的所有更改。
-
查看首页并查看您所做的更改是否生效。

它是如何工作的...
视图显示模式是配置实体。视图显示模式决定了当查看实体时\Drupal\Core\EntityContentEntityForm类将如何构建视图显示。这始终设置为默认值,除非更改或以不同的模式程序化指定。
由于视图显示模式是配置实体,它们可以使用配置管理进行导出。
第三章:通过 Views 显示内容
本章将介绍 Views 模块及其主要功能的用法。在本章中,我们将涵盖以下食谱:
-
列出内容
-
编辑默认管理界面
-
从视图创建块
-
利用动态参数
-
在视图中添加关系
-
提供实体引用结果视图
简介
对于之前使用过 Drupal 的用户,Views 是 Drupal 8 的核心功能。如果你是 Drupal 的新用户,请注意,Views 一直是 Drupal 6 和 Drupal 7 中最常用的贡献项目之一。
简要来说,Views 是一个可视化查询构建器,允许您从数据库中提取内容并以多种格式渲染。Drupal 提供的所有内置管理区域和内容列表都是由 Views 驱动的。我们将深入了解如何使用 Views 自定义管理界面,自定义显示内容的方式,以及与实体引用字段交互。
列出内容
Views 只做一件事,而且做得很好——列出内容。Views 模块背后的力量在于它给予最终用户在多种形式中显示内容的可配置能力。
本食谱将涵盖创建内容列表并将其链接到主菜单。我们将使用标准安装提供的文章内容类型,并创建一个文章的落地页面。
准备工作
必须安装Views UI模块才能从用户界面操作视图。默认情况下,这通过标准安装配置启用。
如何操作...
让我们列出视图列出的内容:
- 前往结构,然后是视图,如图所示;这将带您到所有已创建视图的管理概览:

-
点击添加视图以创建一个新的视图。
-
第一步是提供文章视图的名称,这将作为管理和(默认)显示的标题。
-
接下来,我们将修改视图设置。我们希望显示类型为
文章的内容,并将标记字段留空。这将强制视图只显示文章内容类型的内容。 -
选择创建页面选项。页面标题和路径将根据视图名称自动填充,可以根据需要修改。目前,请保留显示和其他设置为其默认值:

-
点击保存并编辑以继续修改您的新视图。
-
在中间列的页面设置部分下,我们将更改菜单项设置。点击无菜单以更改默认设置。
-
选择正常菜单条目。提供菜单链接标题和可选描述。将父级设置为<主导航>:

-
点击表单底部的应用按钮。
-
点击保存以保存您的视图。
-
保存你的视图后,从管理菜单中点击“返回站点”。现在你将在 Drupal 站点的主菜单中看到链接。
它是如何工作的...
创建视图的第一步是选择你将显示的数据类型。这被称为基本表,可以是任何类型的实体或专门暴露给视图的数据。
在视图中,节点被标记为内容,你将在 Drupal 中找到这种术语的互换。
当创建视图页面时,我们添加一个可访问的菜单路径。它告诉 Drupal 调用视图以渲染页面,这将加载你创建的视图并将其渲染。
有显示样式和行插件,它们格式化要渲染的数据。我们的配方使用了未格式化的列表样式,将每一行包裹在一个简单的div元素中。我们可以将其更改为表格以形成格式化列表。行显示控制每行如何输出。
更多...
视图模块自首次推出以来一直是必用模块之一,以至于几乎每个 Drupal 7 站点都使用了此模块。在下一节中,我们将进一步深入了解视图。
Drupal 核心中的视图倡议
视图模块直到 Drupal 8 都是一个贡献模块。事实上,它是最常用的模块之一。尽管该模块现在是 Drupal 核心的一部分,但它仍然需要许多改进,并且正在被提交。
通过它们的 8.1、8.2 和 8.3 版本发布,已经进行了许多改进。我们将在每个未来的小版本更新中继续看到这种模式。
视图和显示
当与视图一起工作时,你会看到一些不同的术语。其中需要掌握的关键项目之一是了解什么是显示。一个视图可以包含多个显示。每个显示都是某种类型。视图包含以下显示类型:
-
附件:这是一个附加到同一视图中的另一个显示的显示
-
块:这允许你将视图作为一个块放置
-
嵌入:此显示旨在以编程方式嵌入
-
实体引用:这允许视图为实体引用字段提供结果
-
Feed:此显示返回基于 XML 的 feed,可以附加到另一个显示以渲染 feed 图标
-
页面:这允许你从特定的路由显示视图
每个显示也可以有自己的配置。然而,每个显示将共享相同的基本表(内容、文件等)。这允许你以不同的方式表示相同的数据。
格式样式插件 - 样式和行
在视图内部,有两种类型的样式插件代表你的数据如何显示:样式和行:
-
样式插件代表整体格式
-
行插件代表每行结果的格式
例如,网格样式将输出多个具有指定类的div元素以创建响应式网格。同时,表格样式创建带有标签的表格输出,用作表格标题。
行插件定义了如何渲染行。默认内容将根据其选定的显示模式渲染实体。如果你选择字段,你可以手动选择要包含在你的视图中的字段。
每个格式样式插件都有一个对应的Twig文件,主题层使用它。请参阅第五章,前端为王,以了解更多关于 Drupal 8 中的 Twig 的信息。
你可以在自定义模块中定义新插件或使用贡献模块来访问不同的选项。
使用嵌入显示
可用的每种显示类型都有一种方法可以通过用户界面暴露自己,除了嵌入。通常,贡献和自定义模块使用视图来渲染显示,而不是手动编写查询和渲染输出。Drupal 8 提供了一个特殊的显示类型来简化这一点。
如果我们要在菜谱中创建的视图中添加嵌入显示,我们可以传递以下渲染数组来程序化地输出我们的视图:
$view_render = [
'#type' => 'view',
'#name' => 'articles',
'#display_id' => 'embed_1',
];
当渲染时,#type键告诉 Drupal 这是一个视图元素。然后我们将其指向我们新的显示embed_1。嵌入显示类型没有特殊功能,实际上,它是一个简单的显示插件。好处是它没有为了性能而进行的额外操作。
当你想在自定义页面、块或甚至表单中使用视图时,使用嵌入显示是有益的。例如,Drupal Commerce 使用这种模式为其购物车块和结账中的订单摘要。视图用于在自定义块和表单中显示订单信息。
参见
-
参考 VDC 倡议
www.drupal.org/community-initiatives/drupal-core/vdc。 -
参考第第七章,插件即插即用,以了解更多关于插件的信息。
编辑默认管理界面
在 Drupal 核心中添加视图后,许多管理界面都由视图提供支持。这允许自定义默认管理界面,以增强网站管理和内容创作体验。
在 Drupal 6 和 7 中,有一个名为“Administrative Views”的贡献模块,它提供了一种使用视图覆盖管理页面的方法。由于该功能现在直接包含在 Drupal 核心中,因此不再需要此模块。
在这个菜谱中,我们将修改默认的内容概览表单,该表单用于查找和编辑内容。我们将添加按作者过滤内容的能力。
如何操作...
-
前往结构,然后是视图。这将带您到所有现有视图的管理概览。
-
从“已启用”部分,在操作列中选择内容视图的编辑选项。这是在管理内容时在
/admin/content上显示的视图。 -
要按内容作者进行筛选,我们必须向我们的视图添加一个过滤器标准,我们将公开以下内容供用户修改:

- 在过滤器标准部分点击添加以添加新的过滤器。在搜索框中输入“由...创建”以搜索可用选项。为内容类别选择“由...创建”。点击添加并配置过滤器标准:

-
选择“允许访客公开此过滤器以允许他们更改它”复选框。这将允许用户修改过滤器数据。
-
您可以修改标签并添加描述来提高过滤器选项在您用例中的可用性。
-
再次点击应用以完成配置过滤器。现在它将显示在过滤器标准列表中。您还会在表单下方的预览中看到新的过滤器。
-
点击保存以提交对视图的所有更改。
-
查看
/admin/content,您将找到您的过滤器。内容编辑员将能够通过自动完成的用户名搜索搜索由用户创建的内容:

它是如何工作的...
当创建的视图路径与现有路由匹配时,它将覆盖它并显示自己。这就是/admin/content和其他管理页面能够由视图提供动力的原因。
如果您禁用了视图模块,您仍然可以管理内容和用户。默认表单是表格,不提供过滤器或其他额外功能。
Drupal 使用覆盖的路由并使用视图来渲染页面。从那时起,页面就像任何其他视图页面一样被渲染。
更多...
我们将深入了解通过视图提供的其他功能,这些功能可以增强您使用视图的方式,并在您的 Drupal 网站上展示它们。
公开与非公开
过滤器允许您缩小视图显示的数据范围。过滤器可以是公开的或不公开的;默认情况下,过滤器不是公开的。一个例子是使用内容:发布状态设置为“是”(已发布)以确保视图始终包含发布的内容。这是一项您会配置以向网站访客显示内容的设置。然而,如果是用于管理显示,您可能希望公开该过滤器。这样,内容编辑员可以轻松地查看尚未发布或已取消发布的内容。
所有过滤和排序标准都可以标记为公开。
过滤器标识符
公开的过滤器通过解析 URL 中的查询参数来工作。例如,在内容管理表单中,更改类型过滤器将添加type=Article等参数到当前 URL 中。
使用此配方,作者过滤器将显示为 URL 中的 uid。公开的过滤器有
可以更改 URL 组件的过滤器标识符选项:

这可以更改为作者或其他值,以增强 URL 背后的用户体验,或掩盖 Drupal 的特性。
使用视图覆盖路由
由于 Drupal 中路由和模块系统的工作方式,视图可以替换管理页面,以增强版本。模块按照模块的权重顺序执行,如果权重相同,则按字母顺序执行。自然地,在英文字母表中,字母V位于字母表的末尾。这意味着任何由视图提供的路由都将添加到路由发现周期的末尾。
如果创建了一个视图并提供了一个路由路径,它将覆盖该路径上存在的任何视图。没有碰撞检查机制(在合并到 Drupal 核心之前,视图中没有这样的机制)来防止这种情况。
这允许您轻松地自定义大多数现有路由,但请注意,您可能会轻易地遇到冲突的路由,并且视图通常会覆盖其他路由。
从视图创建块
之前的配方已经展示了如何创建和操作由视图创建的页面。视图提供了不同的显示类型,可以创建,例如块。在这个配方中,我们将创建一个由视图驱动的块。视图块将列出已添加到文章内容类型的所有标签分类术语。
准备工作
此配方假设您已安装标准安装配置文件,并且有默认节点内容类型可供使用。
如何做到这一点...
-
前往结构然后视图。这将带您到所有创建的视图的管理概览。
-
点击添加视图以创建新视图。
-
第一步是提供标签的视图名称,这将作为管理标题和(默认情况下)显示标题。
-
接下来,我们将修改视图设置。我们希望显示类型为标签的分类术语。这将使视图默认只显示在标签词汇表下创建的分类术语。
-
在块设置部分勾选创建块复选框。
-
从显示格式选择中,选择 HTML 列表选项。保留样式为字段:

-
点击保存并编辑以创建视图。
-
我们希望显示所有可用的标签。在分页部分,点击使用分页旁边的字段,选择显示所有项目并点击应用。
-
接下来,我们将按标签名称而不是创建顺序对视图进行排序。在排序标准部分点击添加。选择名称:分类术语的复选框,点击添加并配置排序标准以使用默认设置,即升序。点击应用:

-
点击保存以保存视图。
-
前往结构并布局块,将块放置在您的 Drupal 网站上。在 Bartik 主题中,首先点击侧边栏第一个区域的放置块。
-
通过输入您的视图名称(
标签)来过滤列表。点击放置块以将您的视图块添加到布局中。 -
最后,点击保存块以提交您的更改。
它是如何工作的...
在 Drupal 8 插件系统中,有一个称为派生的概念。插件是 Drupal 8 中可互换功能的小块。插件和插件开发在第七章,插件即插即用中有所介绍。派生允许模块动态地提供插件的多个变体。在视图的情况下,它允许模块为每个具有块显示的视图提供 ViewsBlock 插件的变体。视图实现了 \Drupal\views\Plugin\Block\ViewsBlock\ViewsBlock 类,为这些块的动态可用性提供了基础。每个派生块是这个类的一个实例。
当 Drupal 初始化块时,视图传递所需的正确配置。然后视图被执行,并在块显示时渲染显示。
更多内容...
我们现在将探讨视图与块交互的其他一些方式。
暴露表单作为块
如果您的视图使用了暴露的过滤器,您可以选择将暴露的表单放置在块中。启用此选项后,您可以将块放置在页面的任何位置,甚至是非您视图的页面。
在块中使用暴露表单的一个例子是用于搜索结果视图。您将为控制搜索结果的关键字添加一个暴露的过滤器。在块中的暴露过滤器,您可以轻松地将它放置在您网站的页眉中。当提交暴露的过滤器块时,它将用户引导到视图的显示。
要启用作为块的暴露过滤器,您必须首先展开视图编辑表单右侧的高级部分。从高级部分点击暴露表单在块中的选项。在打开的选项对话框中,选择是单选按钮,然后点击应用。然后您可以从块布局表单放置块:

相关内容
- 参考第七章,插件即插即用,了解更多关于派生的信息
利用动态参数
视图可以被配置为接受上下文过滤器。上下文过滤器允许您提供一个动态参数,该参数修改视图的输出。值应从 URL 传递;然而,如果它不存在,有方法可以提供默认值。
在这个菜谱中,我们将创建一个名为 My Content 的新页面,该页面将在 /user/%/content 路由上显示用户的创作内容。
如何操作...
-
前往结构,然后是视图。这将带您查看所有创建的视图的行政概览。点击添加视图以创建一个新的视图。
-
将视图名称设置为 My Content。
-
接下来,我们将修改视图设置。我们希望显示类型为所有内容的条目,并将标记为空。这将允许显示所有内容。
-
选择“创建页面”。保持页面标题不变。我们需要将路径更改为
user/%user/content。点击“保存并编辑”以移动到下一个屏幕并添加上下文过滤器。
在构建视图页面时,在路径中添加百分号标识一个路由变量。通过添加实体类型的名称,Drupal 将将输入匹配为该类型的实体标识符。
-
在页面右侧的表单的“高级”部分切换。在上下文过滤器部分点击“添加”。
-
选择“作者:内容”,然后点击“添加”并配置上下文过滤器。
-
将“当过滤器值不在 URL 中时”的默认值更改为“显示“访问被拒绝””,以防止所有内容都显示为具有不良路由值的错误:

-
点击“应用”,然后点击“保存”以保存视图。
-
前往
/user/1/content,您将看到由第一个用户创建的内容。
它是如何工作的...
上下文过滤器模仿 Drupal 路由系统中找到的路由变量。变量由百分号作为视图路径中的占位符表示。视图将根据它们的放置顺序将每个占位符与上下文过滤器匹配。这允许您有多个上下文过滤器;您只需确保它们按正确顺序排列。
视图模块知道如何处理占位符,因为您在添加过滤器时选择了数据类型。一旦添加了上下文过滤器,就会有额外的选项来处理路由变量。
更多内容...
我们现在将探索使用上下文过滤器时可用的一些额外选项。
使用上下文过滤器预览
您仍然可以从编辑表单预览视图。您只需将上下文过滤器值添加到由正斜杠(/)连接的文本表单中。在这个配方中,您可以
将导航到 /user/1/content 的操作替换为在预览表单中简单地输入 1 并更新预览。
在用户页面上显示为标签页
即使在配方中创建的视图遵循 /user 下的路由,它也不会作为本地任务标签显示,直到它有一个菜单条目定义。
返回并编辑“我的内容”视图。在页面设置部分,您需要将菜单选项中的“无菜单”更改为“菜单”。点击该链接将打开菜单链接设置对话框。
选择“菜单”标签页,提供一个菜单链接标题,例如“我的内容”。选择“<用户账户菜单)”作为父级。点击“应用”并保存您的视图。当您再次访问 /user 页面时,它将提供“我的内容”页面。
您可能需要重建 Drupal 的缓存,以便重新构建路由系统,使 Drupal 能够意识到菜单标签。
修改页面标题
使用上下文过滤器,您有权操作当前页面的标题。当添加或编辑上下文过滤器时,您可以修改页面标题。您可以在“当过滤器值存在于 URL 中或提供默认值”部分中检查“覆盖标题”选项。
此文本框允许您输入将显示的新标题。此外,您可以使用格式为%#的路由上下文传递的信息,其中#是参数顺序。
验证
上下文过滤器可以附加验证。如果不指定额外的验证,视图将采用预期的参数并尝试使其“正常工作”。您可以通过添加验证来帮助限制此范围并过滤掉无效的路由变量。
您可以通过勾选“当过滤器值存在于 URL 中或提供默认值”部分中的“指定验证标准”来启用验证。默认设置为基本验证,这允许您指定如果数据无效,视图应该如何反应;根据我们的配方,这将是如果用户未找到。
验证器选项列表不会被您选择的上下文过滤器项过滤,因此其中一些可能不适用。对于我们的配方,可能需要选择“用户 ID”并验证用户是否有权访问“用户”。此验证器将确保当前用户能够查看路由的用户配置文件。此外,还可以根据其角色进一步限制:

这为您提供了更细粒度的控制,当使用上下文过滤器进行路由参数时,视图如何操作。
多重和排除
您还可以配置上下文过滤器以允许AND或OR操作以及排除。这些选项在添加或编辑上下文过滤器时位于“更多”部分。
可以勾选“允许多个值”选项以启用AND或OR操作。如果上下文过滤器参数包含由加号(+)连接的值系列,则它充当OR操作。如果值由逗号(,)连接,则它充当AND操作。
当勾选“排除”选项时,值将被排除在结果之外,而不是视图被其限制。
在视图中添加关系
如本章开头所述,视图是一个可视化查询构建器。当您首次创建视图时,会指定一个基础表,从中提取数据。视图自动知道如何连接表以获取字段数据,例如正文文本或自定义附加字段。
当使用实体引用字段时,您可以显示原始标识符、引用实体的标签或整个渲染的实体。然而,如果您基于引用字段添加关系,您将能够显示该实体可用的任何字段。
在本配方中,我们将更新用于管理文件的“文件”视图,以显示上传文件的用户的用户名。
如何做到这一点...
-
前往结构然后是视图。这将带您到所有已创建视图的管理概览
-
找到文件视图并点击编辑。
-
点击高级以展开该部分,然后点击位于关系旁边的添加。
-
搜索用户。选择上传关系的用户选项,然后点击应用(此显示):

-
接下来,我们将看到一个关系的配置表单。点击应用(此显示)以使用默认设置。
-
在字段部分点击添加以添加新字段。
-
搜索名称并选择名称:用户字段,然后点击应用(所有显示)。
-
此视图使用聚合,当您第一次添加字段时,将显示一个新的配置表单。点击应用并继续使用默认设置。
我们将在本食谱的更多内容...部分讨论视图和聚合。
- 我们将使用默认的字段设置,这将提供名称标签并将其格式化为用户名并链接到用户的个人资料。点击应用(此显示)。

-
点击保存以完成编辑视图并提交您的更改。
-
当在
/admin/content/files中查看文件列表时,现在将显示上传文件的用户名:

它是如何工作的...
Drupal 以规范化的格式存储数据。简而言之,数据库规范化涉及在特定相关表中组织数据。每个实体类型都有自己的数据库表,所有字段都有自己的数据库表。当您创建一个视图并指定将显示哪种数据时,您正在指定数据库中的基础表,视图将查询此表。视图将自动为您关联属于实体及其关系的字段到那些表。
当一个实体有一个实体引用字段时,您可以将关系添加到引用实体类型的表中。这是一个显式定义,而字段是隐式的。当关系被显式定义时,所有引用实体类型的字段都将进入作用域。然后可以显示、过滤和排序引用实体类型上的字段。
更多内容...
在视图中使用关系允许您创建一些强大的显示。我们将讨论聚合和关于关系的附加信息。
实体引用字段提供的关系
视图模块使用一系列钩子来检索数据,然后使用这些数据来表示与数据库交互的方式。其中之一是hook_field_views_data钩子,它处理字段存储配置实体并将其数据注册到视图中。视图模块代表 Drupal 核心实现这一点,以添加关系和反向关系,用于实体引用字段。
由于实体引用字段已设置架构信息,视图可以通过理解字段表名、目标实体表名和目标实体标识列来动态生成这些关系。
通过自定义代码提供的关联
有时候您需要自己定义一个关系,使用自定义代码。通常,当在 Drupal 中处理自定义数据时,您更有可能创建一个新的实体类型;这个主题在第九章,配置管理 - 在 Drupal 8 中部署中有所涉及。然而,这并不总是情况,您可能只需要一种简单的方法来存储数据。一个例子可以在数据库日志模块中找到。数据库日志模块定义了一个数据库表的架构,然后使用 hook_views_data 将其数据库表暴露给视图。
dblog_schema 钩子实现返回由模块创建的监视数据库表上的 uid 列。然后,该列通过以下定义暴露给视图:
$data['watchdog']['uid'] = array(
'title' => t('UID'),
'help' => t('The user ID of the user on which the log entry
was written..'),
'field' => array(
'id' => 'numeric',
),
'filter' => array(
'id' => 'numeric',
),
'argument' => array(
'id' => 'numeric',
),
'relationship' => array(
'title' => t('User'),
'help' => t('The user on which the log entry as written.'),
'base' => 'users',
'base field' => 'uid',
'id' => 'standard',
),
);
此数组告诉视图,watchdog 表有一个名为 uid 的列。它具有数字性质,用于显示、过滤和排序能力。关系
键是一个信息数组,指示视图如何使用它来在 users 表上提供关系(LEFT JOIN)。User 实体使用 users 表,其主键为 uid。
使用聚合和视图。
在高级部分有一个视图设置,允许您启用聚合。
此功能允许您启用使用 SQL 聚合函数,例如 MIN、MAX、SUM、AVG 和 COUNT。在本食谱中,文件视图使用聚合来汇总 Drupal 网站上每个文件的用法计数。
聚合设置针对每个字段设置,启用时,它们有自己的链接来配置这些设置:

提供实体引用结果视图
在第二章,内容创作体验中介绍的实体引用字段可以利用自定义视图来提供可用的字段值。默认的实体引用字段将显示它可以引用的所有类型的实体。唯一可用的过滤器是基于实体捆绑,例如仅返回 Article 节点。使用实体引用视图,您可以提供更多过滤器,例如仅返回用户创作的内容。
在本食谱中,我们将创建一个实体引用视图,通过作者过滤内容。我们将添加字段到用户账户表单中,允许用户选择他们喜欢的贡献内容。
如何做到这一点...
-
前往结构,然后是视图。这将带您到所有已创建视图的管理概览。点击添加视图以创建一个新的视图。
-
将视图名称设置为“我的内容引用视图”,并保留当前的视图设置配置。
-
不要选择创建页面或块。点击保存并编辑以继续编辑视图。
-
点击添加按钮创建新的显示。选择实体引用选项以创建显示:

-
格式将自动设置为实体引用列表,它使用字段。点击旁边的设置来修改样式格式。
-
对于搜索字段,勾选“内容:标题”选项,然后点击应用。这就是字段将执行自动完成搜索的地方。
-
您需要修改“内容:标题”字段以停止将其作为链接包装结果。点击字段标签并取消选中链接到内容。点击应用以保存字段设置:

-
然后,我们将使用上下文筛选器来限制结果仅限于当前登录用户。在高级部分点击上下文筛选器的添加。
-
选择“由谁创建:内容”选项,然后点击添加并配置上下文筛选器。
-
将“当筛选器值不可用时的设置”更改为“提供默认值”。从登录用户中选择用户 ID 作为类型值。点击应用以配置上下文筛选器。
-
点击保存以保存视图。
-
前往配置然后账户设置,以便能够管理用户账户上的字段。
-
添加一个新的实体引用字段,引用内容,命名为“突出贡献”,并允许它有无限值。点击保存字段设置按钮。
-
将引用类型方法更改为使用视图:通过实体引用视图进行筛选并选择我们刚刚创建的视图:

- 现在,当用户编辑他们的账户时,他们只能引用在此引用字段中创建的内容。
它是如何工作的...
实体引用字段定义提供了选择插件。视图模块提供了一个实体引用选择插件。这允许实体引用将数据收集到视图中以接收可用结果。
视图的显示类型要求您选择在使用自动完成小部件时用于搜索的字段。如果您不使用自动完成小部件,而是使用选择列表或复选框和单选按钮,则它将返回视图的所有结果。
参见
- 参考第第七章,插件即插即用,了解更多关于插件的信息。
第四章:扩展 Drupal
本章深入探讨使用自定义模块扩展 Drupal:
-
创建模块
-
定义自定义页面
-
定义权限
-
在安装或更新时提供配置
-
创建事件订阅者
-
使用 Features 3.0
简介
Drupal 的一项特性使其变得令人向往,那就是能够通过模块进行自定义。无论是自定义还是贡献的,模块都扩展了 Drupal 的功能和能力。模块不仅可以扩展 Drupal,还可以创建提供配置和可重用功能的方式。
本章将讨论如何创建模块并允许 Drupal 发现它,从而可以从扩展页面安装它。权限、自定义页面和默认配置都来自模块。我们将探讨如何通过自定义模块提供这些功能。
除了创建模块外,我们还将讨论 Features 模块,它提供了一套生成模块和导出其配置的工具。
创建模块
扩展 Drupal 的第一步是创建一个自定义模块。尽管这项任务听起来令人畏惧,但它可以通过几个简单的步骤完成。模块可以提供功能性和对其他模块提供的功能的自定义,或者它们可以用作包含配置和站点状态的方式。
在本食谱中,我们将通过定义一个 info.yml 文件来创建一个模块,该文件包含 Drupal 用于发现扩展的信息,并启用该模块。
如何操作...
-
在您的 Drupal 站点的
modules文件夹的根目录下创建一个名为mymodule的文件夹。这将成为您的模块目录。 -
在您的模块目录中创建一个
mymodule.info.yml文件。它包含标识模块给 Drupal 的元数据。 -
在
name键下添加一行以提供模块的名称:
name: My Module!
- 我们需要提供
type键来定义扩展的类型。我们提供module值:
type: module
description键允许您提供有关您的模块的额外信息,这些信息将在模块列表页面上显示:
description: This is an example module from the Drupal 8 Cookbook!
- 所有模块都需要定义
core键以指定主要版本兼容性:
core: 8.x
- 保存
mymodule.info.yml文件,其代码如下:
name: My Module!
type: module
description: This is an example module from the Drupal 8 Cookbook!
core: 8.x
-
登录您的 Drupal 网站,并从管理工具栏转到扩展。
-
搜索“我的模块”以过滤选项列表。
-
打勾并点击安装以启用您的模块:

它是如何工作的...
Drupal 利用 info.yml 文件来定义扩展。Drupal 有一个发现系统,用于定位这些文件并将它们解析为发现模块。由 \Drupal\Core\Extension\InfoParser 类提供的 info_parser 服务读取 info.yml 文件。解析器保证存在所需类型、core 和 name 键。
当模块安装时,它会被添加到core.extension配置对象中,该对象包含已安装模块和主题的列表。core.extension模块数组中的模块集合将被安装,并将解析 PHP 命名空间、加载服务和注册钩子。
当 Drupal 准备执行钩子或注册服务时,它将遍历core.extension中的module键的值。
更多...
关于 Drupal 模块和模块info.yml文件,我们可以探索更多细节。
模块命名空间
Drupal 8 使用由PHP 框架互操作性小组(PHP-FIG)开发的 PSR-4 标准。PSR-4 标准是针对基于包的 PHP 命名空间自动加载。它定义了一个标准,以了解如何根据命名空间和类名自动包含类。Drupal 模块在 Drupal 根命名空间下有自己的命名空间。
使用配方中的模块,我们的 PHP 命名空间将是Drupal\mymodule,这代表modules/mymodule/src文件夹。
使用 PSR-4,文件只需要包含一个类、接口或特质。这些文件需要与包含的类、接口或特质的名称具有相同的文件名。这允许类加载器将命名空间解析为目录路径并知道类的文件名。当文件被使用时,它将被自动加载。
模块发现位置
Drupal 支持多个模块发现位置。模块可以放置在以下目录中并被发现:
-
/profiles/CURRENT PROFILE/modules -
/sites/all/modules -
/modules -
/sites/default/modules -
/sites/example.com/modules
\Drupal\Core\Extension\ExtensionDiscovery类通过类型处理扩展的发现。它将迭代扫描每个位置并发现可用的模块。发现顺序很重要。如果同一模块放置在/modules中,但也放在sites/default/modules目录中,则后者将具有优先权。
定义一个包组
模块可以定义一个package键来在模块列表页面上分组模块:

包含多个子模块的项目,如 Drupal Commerce,指定包以规范化模块列表形式。Drupal Commerce 项目的贡献模块使用包名Commerce (contrib)在模块列表页面上分组。
模块依赖
模块可以定义依赖关系以确保在您的模块启用之前,那些模块已被启用。
这是Responsive Image模块的info.yml文件:
name: Responsive Image
type: module
description: 'Provides an image formatter and breakpoint mappings to output responsive images using the HTML5 picture tag.'
package: Core
version: VERSION
core: 8.x
dependencies:
- breakpoint
- image
dependencies键指定在启用Responsive Image模块之前需要首先启用breakpoint和image模块。当启用需要依赖项且依赖项已禁用的模块时,安装表单将提供一个提示,询问你是否想安装依赖项。如果缺少依赖模块,则无法安装。依赖项将显示为(缺失)的状态。
作为另一个模块依赖项的模块将在其描述中声明信息,以及其他模块的状态。例如,Breakpoint 模块将显示 Re 模块需要它作为依赖项并且已禁用:

指定模块的版本
有一个version键定义了当前模块的版本。Drupal.org 上的项目不会直接指定此版本,因为当创建发布时,Drupal.org 扩展打包器会添加它。然而,这个键对于私有模块跟踪发布信息可能很重要。
预期版本是单个字符串,例如1.0-alpha1和2.0.1。你也可以传递VERSION,它将解析为 Drupal 核心的当前版本。
Drupal.org 目前不支持贡献项目的语义版本。在问题队列中有一个现在已推迟的政策讨论,可以在www.drupal.org/node/1612910找到。
参见...
-
参考 PSR-4:自动加载规范
www.php-fig.org/psr/psr-4/ -
参考 Drupal.org 文档创建模块
www.drupal.org/docs/8/creating-custom-modules
定义自定义页面
在 Drupal 中,有一些代表 URL 路径的路由,Drupal 将这些路径解释为返回内容。模块可以定义路由和方法,这些方法返回要渲染并显示给最终用户的数据。
在这个菜谱中,我们将定义一个控制器,它提供输出和路由。路由提供了一个 URL 路径,Drupal 会将该路径与我们的控制器关联起来以显示输出。
准备工作
创建一个新的模块,就像第一个菜谱中的那样。在整个菜谱中,我们将把该模块称为mymodule。根据需要使用你的模块名称。
如何做到这一点...
-
首先,我们将设置控制器。在你的模块基本目录中创建一个
src文件夹,并在其中创建一个名为Controller的另一个文件夹。 -
创建将包含路由控制器类的
MyPageController.php:

- PSR-4 标准规定,文件名与它们包含的类名相匹配,因此我们将创建一个
MyPageController类:
<?php
namespace Drupal\mymodule\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Returns responses for My Module module.
*/
class MyPageController extends ControllerBase {
}
这将创建一个MyPageController类,它扩展了\Drupal\Core\Controller\ControllerBase类。这个基类提供了一些与容器交互的实用工具。
Drupal\mymodule\Controller命名空间允许 Drupal 自动从/modules/mymodule/src/Controller目录加载文件。
- 接下来,我们将在我们的
MyPageController类中创建一个返回文本字符串的方法。将以下方法添加到我们的MyPageController类中:
/**
* Returns markup for our custom page.
*/
public function customPage() {
return [
'#markup' => t('Welcome to my custom page!'),
];
}
customPage方法返回一个渲染数组,Drupal 的主题层可以解析。#markup键表示一个没有额外渲染或主题处理的价值。
-
在您模块的根目录中创建一个
mymodule.routing.yml文件,以便可以向此控制器和方法添加路由。 -
第一步是为路由定义一个内部名称,以便引用该路由:
mymodule.mypage:
- 给路由指定一个路径(
mypage):
mymodule.mypage:
path: '/mypage'
defaults键允许我们向控制器提供完全限定的类名、要使用的方法和页面的标题:
mymodule.mypage:
path: '/mypage'
defaults:
_controller: '\Drupal\mymodule\Controller\MyPageController::customPage'
_title: 'My custom page'
在提供完全限定的类名时,需要提供初始的反斜杠\。
记住,Drupal 使用 PSR-4 自动加载。Drupal 能够确定具有\Drupal\mymodule\Controller命名空间的类位于/path/to/drupal/modules/mymodule/src/Controller目录中。
- 最后,定义一个
requirements键来设置访问回调:
mymodule.mypage:
path: '/mypage'
defaults:
_controller: '\Drupal\mymodule\Controller\MyPageController::customPage'
_title: 'My custom page'
requirements:
_permission: 'access content'
-
前往配置,然后在开发部分中的性能,点击清除所有缓存按钮以重建 Drupal 的路由。
-
访问您的 Drupal 站点上的
/mypage并查看您的自定义页面:

它是如何工作的...
Drupal 使用路由,这些路由定义了一个路径,用于返回内容。每个路由在控制器类中都有一个方法,该方法以渲染数组的形式生成内容,以便发送给用户。当请求到达 Drupal 时,系统会尝试将路径与已知的路由进行匹配。如果找到路由,则使用路由的定义来显示页面。如果找不到路由,则显示 404 页面。
HTTP 内核接收请求并加载路由。它将调用定义的控制器方法或过程函数。调用方法或函数的结果随后被交给 Drupal 的表现层,以将其渲染成可以发送给用户的内容。
Drupal 8 基于 Symfony HTTP 内核构建,以提供其路由系统的底层功能。它增加了提供访问要求、将占位符转换为加载的对象以及提供部分页面响应的能力。
还有更多...
路由具有额外的可配置功能;我们将在下一节中探讨这些功能。
路由中的参数
路由可以接受动态参数,这些参数可以传递给路由控制器的方法。可以在路由中使用花括号定义占位符元素,以表示动态值。
以下示例代码显示了路由可能的样子:
mymodule.cats:
path: '/cat/{name}'
defaults:
_controller: '\Drupal\mymodule\Controller\MyPageController::cats'
requirements:
_permission: 'access content'
此路由指定了/cat/{name}路径。{name}占位符将接受动态值并将它们传递给控制器的方法:
class MyPageController {
// ...
public function cats($name) {
return [
'#markup' => t('My cats name is: @name', [
'@name' => $name,
]),
];
}
}
此方法接受路由中的name变量并将其替换到渲染数组中,以显示为文本。
Drupal 的路由系统提供了一种将变量提升为加载对象的方法。在 Drupal 中,提升是将路由参数转换为更丰富的数据的过程。这包括将实体 ID 转换为提供加载的实体给系统。在\Drupal\Core\ParamConverter命名空间下有一组参数转换类。EntityConverter类将读取路由中定义的选项,并用加载的实体对象替换占位符值。
如果我们有一个名为cat的实体类型,我们可以将name占位符转换为在控制器方法中提供加载的cat对象的方法:
mymodule.cats:
path: '/cat/{name}'
defaults:
_controller: '\Drupal\mymodule\Controller\MyPageController::cats'
requirements:
_permission: 'access content'
options:
parameters:
name:
type: entity:cat
对于实体来说,这不是必需的,因为定义的实体路由处理器可以自动生成它。实体在第十章,实体 API中有详细说明。
验证路由中的参数
Drupal 提供了对路由参数的正则表达式验证。如果参数未通过正则表达式验证,将返回 404 页面。使用示例路由,我们可以添加验证以确保在路由参数中只使用字母字符:
mymodule.cats:
path: '/cat/{name}'
defaults:
_controller: '\Drupal\mymodule\Controller\MyPageController::cats'
requirements:
_permission: 'access content'
name: '[a-zA-z]+'
在requirements键下,你可以添加一个与占位符名称匹配的新值。然后你可以将其设置为使用正则表达式。这将防止c@ts或cat!成为有效的参数。
路由要求
路由可以通过requirements键定义不同的访问要求。可以添加多个验证器。然而,必须有一个提供真值的结果,否则路由将返回 403,拒绝访问。如果路由没有定义要求验证器,这也是正确的。
路由要求验证器是通过实现\Drupal\Core\Routing\Access\AccessInterface定义的。以下是在 Drupal 核心中定义的一些常见要求验证器:
-
_access: TRUE:始终授予路由访问权限 -
_entity_access:验证当前用户是否有执行的能力entity_type.operation,例如node.view -
_permission:检查当前用户是否有提供的权限 -
_user_is_logged_in:验证当前用户是否已登录,这在routing.yml文件中以布尔值定义
提供动态路由
路由系统允许模块以编程方式定义路由。这可以通过提供一个routing_callbacks键来实现,该键定义了一个类和方法,该方法将返回一个包含\Symfony\Component\Routing\Route对象的数组。
如果你正在处理实体,请参考第十章,实体 API来了解如何覆盖默认路由处理器以创建动态路由。
在模块的routing.yml中,你将定义路由回调键和相关类:
route_callbacks:
- '\Drupal\mymodule\Routing\CustomRoutes::routes'
\Drupal\mymodule\Routing\CustomRoutes类将有一个名为routes的方法,它返回一个 Symfony 路由对象的数组:
<?php
namespace Drupal\mymodule\Routing;
use Symfony\Component\Routing\Route;
class CustomRoutes {
public function routes() {
$routes = [];
// Create mypage route programmatically
$routes['mymodule.mypage'] = new Route(
// Path definition
'mypage',
// Route defaults
[
'_controller' => '\Drupal\mymodule\Controller\MyPageController::customPage',
'_title' => 'My custom page',
],
// Route requirements
[
'_permission' => 'access content',
]
);
return $routes;
}
}
如果一个模块提供了一个与路由交互的类,最佳实践是将它放在模块命名空间的路由部分。这有助于你识别其目的。
调用的方法预期返回一个已启动的路由对象数组。路由类接受以下参数:
-
路径: 这代表路由 -
默认值: 这是一个默认值数组 -
需求: 这是一个必需验证器的数组 -
选项: 这是一个可以传递和可选使用的数组
修改现有路由
当 Drupal 的路由系统因模块启用或缓存重建而重建时,会触发一个事件,允许模块更改在 YAML 中静态定义或在动态中定义的路由。这涉及到通过扩展\Drupal\Core\Routing\RouteSubscribeBase实现事件订阅者,该订阅者订阅RoutingEvents::ALTER事件。
在你的模块中创建一个src/Routing/RouteSubscriber.php文件。它将包含路由订阅者类:
<?php
namespace Drupal\mymodule\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
public function alterRoutes(RouteCollection $collection) {
// Change path of mymodule.mypage to use a hyphen
if ($route = $collection->get('mymodule.mypage')) {
$route->setPath('/my-page');
}
}
}
上述代码扩展了RouteSubscribeBase并实现了alterRoutes()方法。我们尝试加载mymodule.mypage路由,如果存在,则将其路径更改为my-page。由于对象总是通过引用传递,我们不需要返回一个值。
为了让 Drupal 识别订阅者,我们需要在模块的services.yml文件中描述它。在你的模块基本目录中创建一个mymodule.services.yml文件,并添加以下代码:
services:
mymodule.route_subscriber:
class: Drupal\mymodule\Routing\RouteSubscriber
tags:
- { name: event_subscriber }
这将我们的路由订阅者类注册为容器中的服务,以便 Drupal 在事件触发时执行它。
本章后面的创建事件订阅者配方将涵盖更多关于事件分派和订阅的内容。
参考信息
-
请参阅
symfony.com/doc/current/book/routing.html上的 Symfony 路由文档 -
请参阅第十章,实体 API
-
请参阅
www.drupal.org/node/2122195上的路由访问检查社区文档
定义权限
在 Drupal 中,有角色和权限用于定义用户强大的访问控制列表。模块使用权限来检查当前用户是否有权执行操作、查看特定项目或执行其他操作。然后模块定义使用的权限,以便 Drupal 了解它们。开发者可以构建角色,这些角色由启用的权限组成。
在这个配方中,我们将定义一个新的权限来查看模块中定义的自定义页面。该权限将被添加到自定义路由中,并将访问限制为具有包含权限的角色的用户。
准备工作
创建一个新的模块,就像第一个配方中的那样。在整个配方中,我们将把这个模块称为mymodule。在以下配方中,根据需要使用你的模块名称。
此配方还会修改模块中定义的路由。我们将把这个路由称为mymodule.mypage。修改你模块的routing.yml文件中的适当路径。
如何操作...
-
权限存储在
permissions.yml文件中。在你的模块基本目录中添加一个mymodule.permissions.yml。 -
首先,我们需要定义用于识别此权限的内部字符串,例如
查看我的模块页面:
view mymodule pages:
- 每个权限都是一个包含数据的 YAML 数组。我们需要提供一个
title键,该键将在权限页面上显示:
view mymodule pages:
title: 'View my module pages'
- 权限有一个
description键,用于在权限页面上提供权限的详细信息:
view mymodule pages:
title: 'View my module pages'
description: 'Allows users to view pages provided by My Module'
-
保存你的
permissions.yml并编辑模块的routing.yml以使用权限来控制对路由的访问。 -
修改路由的
requirements键,使其包含一个等于定义权限的_permission键:
mymodule.mypage:
path: '/mypage'
defaults:
_controller: '\Drupal\mymodule\Controller\MyPageController::customPage'
_title: 'My custom page'
requirements:
_permission: 'view mymodule pages'
-
前往配置,然后在开发部分的性能中点击清除所有缓存以重建 Drupal 的路由。
-
前往人员,然后到权限,以认证用户和匿名用户角色为我的模块添加你的权限!:

- 从你的 Drupal 站点注销并查看
/mypage页面。你会看到内容,而不会收到访问拒绝页面。
它是如何工作的...
权限和角色由User模块提供。user.permissions服务发现由已安装模块提供的permissions.yml。默认情况下,服务是通过\Drupal\user\PermissionHandler类定义的。
Drupal 不会保存所有可用权限的列表。当权限页面被加载时,系统的权限会被加载。角色包含一个权限数组。
当检查用户对某个权限的访问时,Drupal 会检查所有用户角色,以查看它们是否支持该权限。
你可以将一个未定义的权限传递给用户访问检查,而不会收到错误。除非用户是 UID 1,否则访问检查将简单地失败。
更多内容...
在接下来的章节中,我们将介绍更多在模块中处理权限的方法。
权限的访问限制标志
如果启用,权限可能会被标记为具有安全风险;这是restrict access标志。当此标志设置为restrict access: TRUE时,它将在权限描述中添加一个警告。
这允许模块开发者提供更多上下文,以说明权限可能给予用户的控制程度:

我们从配方中定义的权限看起来会像以下这样:
view mymodule pages:
title: 'View my module pages'
description: 'Allows users to view pages provided by My Module'
restrict access: TRUE
以编程方式定义权限
权限可以通过模块以编程方式或静态方式在 YAML 文件中定义。模块需要在它的 permissions.yml 中提供一个 permission_callbacks 键,该键包含一个类及其方法的数组或一个过程函数名。
例如,过滤器模块根据 Drupal 中创建的不同文本过滤器提供细粒度的权限:
permission_callbacks:
- Drupal\filter\FilterPermissions::permissions
这告诉 user_permissions 服务执行权限方法
\Drupal\Filter\FilterPermissions 类。该方法预期返回一个与 permissions.yml 文件相同结构的数组。
使用生成的权限的示例将在 第十章,实体 API 的 实现实体自定义访问控制 菜谱中介绍。
检查用户是否有权限
用户账户界面提供了一个检查用户实体是否有权限的方法。要检查当前用户是否有权限,你需要获取当前用户,并需要调用 hasPermission 方法:
\Drupal::currentUser()->hasPermission('my permission');
\Drupal::currentUser() 方法返回当前活动的用户对象。这允许你检查活动用户是否有执行某些类型操作所必需的权限。
在安装或更新时提供配置
Drupal 提供了一个配置管理系统,这在 第九章,配置管理 - 在 Drupal 8 中部署 中有讨论,模块可以在安装或通过更新系统提供配置。模块在首次安装时通过 YAML 文件提供配置;然而,可以通过 Drupal 更新系统在代码中对配置进行更新。
在这个菜谱中,我们将提供一个配置 YAML,它创建一个新的联系表单,然后通过更新系统中的模式版本更改来操作它。
准备工作
创建一个类似于第一个菜谱中的新模块。在整个菜谱中,我们将把这个模块称为 mymodule。在必要时,请使用你模块的适当名称。
如何做...
-
在你模块的基本目录中创建一个
config文件夹。Drupal 要求其配置 YAML 必须位于config的子文件夹中。 -
在
config文件夹中创建一个名为install的文件夹。此文件夹中的配置 YAML 将在模块安装时导入。 -
在
install文件夹中,创建一个contact.form.contactus.yml文件来存储联系表单的 YAML 定义,联系我们:![img/7ec6fd6a-85a9-4d4e-879a-6683f5a26cd9.png] -
我们将根据 Contact 模块提供的
contact.schema.yml文件定义联系表单的配置。将以下 YAML 内容添加到文件中:
langcode: en
status: true
dependences: {}
id: contactus
label: 'Contact Us'
recipients:
- webmaster@example.com
reply: ''
weight: 0
配置条目基于模式定义,我们将在第九章,“配置管理 - 在 Drupal 8 中部署”中介绍。langcode、status和dependencies是必需的配置管理键。
id是联系表单的机器名称,而label是人类的显示名称。recipients键是一个有效的电子邮件地址的 YAML 数组。reply键是自动回复字段的文本字符串。最后,weight定义了表单在管理列表中的权重。
-
前往“扩展”并启用您的模块以导入配置项。
-
联系我们表单现在将位于“联系表单概览”页面,位于“结构”下:

-
在模块的基本目录中创建一个
mymodule.install文件。Drupal 检查.install文件以查找更新钩子。 -
我们将创建一个名为
mymodule_update_8001()的函数,该函数将被更新系统读取并执行我们的配置更改:
<?php
/**
* Update "Contact Us" form to have a reply message.
*/
function mymodule_update_8001() {
$contact_form = \Drupal\contact\Entity\ContactForm::load('contactus');
$contact_form->setReply(t('Thank you for contacting us, we will reply shortly'));
$contact_form->save();
}
此函数使用实体的类来加载我们的配置实体对象。它加载了我们的模块提供的contactus,并将回复属性设置为新的值。
- 在浏览器中前往
/update.php以运行 Drupal 的数据库更新系统。点击“应用挂起更新”以运行更新系统:

- 检查联系我们表单设置并验证回复消息是否已设置。
它是如何工作的...
Drupal 的moduler_installer服务,通过\Drupal\Core\Extension\ModuleInstaller提供,确保在安装时处理模块的config文件夹中定义的配置项。当安装模块时,通过\Drupal\Core\Config\ConfigInstaller提供的config.installer服务被调用以处理模块的默认配置。
在事件中,config.installer服务尝试从已存在的install文件夹导入配置,并将抛出异常。模块不能通过静态 YAML 定义提供对现有配置所做的更改。
由于模块不能通过提供给 Drupal 的静态 YAML 定义来调整配置对象,因此它们可以利用数据库更新系统来修改配置。Drupal 为模块使用模式版本。模块的基本模式版本是8000。模块可以提供hook_update_N形式的更新钩子,其中N代表下一个模式版本。当 Drupal 运行更新时,它们将执行适当的更新钩子并更新模块的模式版本。
配置对象默认是不可变的。要编辑配置,需要通过配置工厂服务加载可变对象。
更多...
我们将在第九章,配置管理 - 在 Drupal 8 中部署中讨论配置;然而,现在我们将深入探讨在处理模块和配置时的一些重要注意事项。
配置子目录
配置管理系统将在模块的 config 文件夹中检查三个目录,如下所示:
-
install -
optional -
schema
install 文件夹指定要导入的配置。如果配置对象存在,安装将失败。optional 文件夹包含在以下条件满足时将安装的配置:
-
配置尚未存在
-
它是一个配置实体
-
其依赖关系可以满足
如果任何一个条件失败,配置将不会安装,但不会停止模块的安装过程。
schema 文件夹提供配置对象定义。这使用 YAML 定义来结构化配置对象,并在第九章,配置管理 - 在 Drupal 8 中部署中进行了深入探讨。
在安装时修改现有配置
配置管理系统不允许模块在已存在的安装上提供配置。例如,如果模块尝试提供 system.site 并定义站点的名称,它将无法安装。这是因为当您首次安装 Drupal 时,系统模块提供了此配置对象。
Drupal 提供了 hook_install(),模块可以在其 .install 文件中实现。此钩子在模块的安装过程中执行。以下代码将在模块安装时将站点标题更新为 Drupal 8 Cookbook!
/**
* Implements hook_install().
*/
function mymodule_install() {
// Set the site name.
\Drupal::configFactory()
->getEditable('system.site')
->set('name', 'Drupal 8 Cookbook!')
->save();
}
可配置对象在默认的 config 服务加载时默认是不可变的。要修改配置对象,您需要使用配置工厂来接收一个可变对象。可变对象可以有 set 和 save 方法,这些方法用于在配置对象中更新配置。
参见
- 参见第九章,配置管理 - 在 Drupal 8 中部署
创建事件订阅者
Drupal 8 的新特性之一是事件调度系统。Drupal 的许多优点之一是能够对特定过程做出反应并对其进行更改或做出反应。与存在于 Drupal 8 中并且已经存在许多版本的 Drupal 中的钩子系统不同,事件调度系统使用显式注册到事件。
事件调度系统来自 Symfony 框架,允许组件之间轻松交互。在 Drupal 中,集成的 Symfony 组件会触发事件,事件订阅者可以监听事件并对更改或其他过程做出反应。
在这个菜谱中,我们将订阅 REQUEST 事件,该事件在请求首次处理时触发。如果用户未登录,我们将将其导航到登录页面。
如何操作...
-
在你的模块中创建
src/EventSubscriber/RequestSubscriber.php。 -
定义
RequestSubscriber类,该类实现了EventSubscriberInterface接口:
<?php
namespace Drupal\mymodule\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RequestSubscriber implements EventSubscriberInterface {
}
- 为了满足接口要求,我们必须添加一个
getSubscribedEvents方法。这告诉系统我们正在订阅哪些事件以及需要调用的方法:
<?php
namespace Drupal\mymodule\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
class RequestSubscriber implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => ['doAnonymousRedirect', 28],
];
}
}
KernelEvents 类提供了可用事件的常量。我们返回的数组指定了要调用的方法和该事件的优先级。
优先级将在 如何工作... 部分讨论。它提供在启用 dynamic_page_cache 模块时解决可能冲突的示例。
- 创建我们指定的
doAnonymousRedirect方法,该方法将接收一个GetResponseEvent参数:
<?php
namespace Drupal\mymodule\EventSubscriber;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class RequestSubscriber implements EventSubscriberInterface {
/**
* Redirects all anonymous users to the login page.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event.
*/
public function doAnonymousRedirect(GetResponseEvent $event) {
// Make sure we are not on the user login route.
if (\Drupal::routeMatch()->getRouteName() == 'user.login') {
return;
}
// Check if the current user is logged in.
if (\Drupal::currentUser()->isAnonymous()) {
// If they are not logged in, create a redirect response.
$url = Url::fromRoute('user.login')->toString();
$redirect = new RedirectResponse($url);
// Set the redirect response on the event, cancelling default response.
$event->setResponse($redirect);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => ['doAnonymousRedirect', 28],
];
}
}
为了防止重定向循环,我们将使用 RouteMatch 服务来获取当前路由对象并验证我们是否已经在 user.login 路由页面。
然后我们检查用户是否为匿名用户,如果是匿名用户,则将事件的响应设置为重定向响应。
-
现在我们已经创建了我们的类,在你的模块目录中创建一个
mymodule.services.yml文件。 -
我们必须将我们的类注册到服务容器中,以便 Drupal 理解它将作为事件订阅者。
services:
mymodule.request_subscriber:
class: Drupal\mymodule\EventSubscriber\RequestSubscriber
tags:
- { name: event_subscriber }
event_subscriber 标签告诉容器调用 getSubscribedEvents 方法并注册其方法。
-
如果模块已经安装,请安装模块或重建 Drupal 的缓存。
-
以匿名用户身份访问任何页面--你将被重定向到登录表单。
如何工作...
在 Drupal 和 Symfony 组件中,甚至在其他第三方 PHP 库中,事件可以被传递给事件调度器。Drupal 中的 event_dispatcher 服务是 Symfony 提供的优化版本,但完全兼容。
当容器构建时,所有标记为 event_subscribers 的服务都被收集。然后它们被注册到 event_dispatcher 服务中,键为 getSubscribedEvents 方法返回的事件。
当 event_dispatcher 服务被告知分发一个事件时,它将在所有已订阅的服务上调用适当的方法。对于 KernelEvents::REQUEST、KernelEvents::EXCEPTION 和 KernelEvents::VIEW,你有机会在控制器被调用之前提供响应。然后还有像 ConfigEvents::SAVE 和 ConfigEvents::DELETE 这样的事件被分发,允许你对配置的保存或删除做出反应,但实际上无法通过事件对象直接调整配置实体。
还有更多...
事件订阅者需要了解创建服务、注册它们以及甚至依赖注入。我们将在下一节中进一步讨论。
使用依赖注入
使用 Drupal 8 和服务容器的实现,引入了依赖注入的概念。依赖注入是一种软件设计概念,在其基本层面上,它提供了一种使用类而不必直接引用它的方法。在我们的例子中,我们多次使用全局静态类 \Drupal 来检索服务。这在服务中是一种不良做法,可能会使测试变得更加困难。
要实现依赖注入,首先,我们将向我们的类添加一个构造函数,该构造函数接受使用的服务(current_route_match 和 current_user)并将受保护的属性匹配以存储它们:
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Account proxy.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $accountProxy;
/**
* Creates a new RequestSubscriber object.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\Core\Session\AccountProxyInterface $account_proxy
* The current user.
*/
public function __construct(RouteMatchInterface $route_match, AccountProxyInterface $account_proxy) {
$this->routeMatch = $route_match;
$this->accountProxy = $account_proxy;
}
我们可以替换任何对 \Drupal:: 的调用为 $this->:
/**
* Redirects all anonymous users to the login page.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event.
*/
public function doAnonymousRedirect(GetResponseEvent $event) {
// Make sure we are not on the user login route.
if ($this->routeMatch->getRouteName() == 'user.login') {
return;
}
// Check if the current user is logged in.
if ($this->accountProxy->isAnonymous()) {
// If they are not logged in, create a redirect response.
$url = Url::fromRoute('user.login')->toString();
$redirect = new RedirectResponse($url);
// Set the redirect response on the event, cancelling default response.
$event->setResponse($redirect);
}
}
最后,我们将更新 mymodule.services.yml 以指定我们的构造函数参数,以便在容器运行我们的事件订阅者时进行注入:
services:
mymodule.request_subscriber:
class: Drupal\mymodule\EventSubscriber\RequestSubscriber
arguments: ['@current_route_match', '@current_user']
tags:
- { name: event_subscriber }
依赖注入一开始感觉和看起来都很神奇。然而,随着使用和实践,它将开始变得更有意义,并在使用 Drupal 8 开发时变得习以为常。
参考信息
-
请参考 Drupal.org API 文档中的事件和可用事件列表,链接为
api.drupal.org/api/drupal/core%21core.api.php/group/events/8.3.x -
请参考 Drupal.org API 文档中关于服务和依赖注入的内容,链接为
api.drupal.org/api/drupal/core%21core.api.php/group/container/8.3.x
使用 Features 3.0
许多 Drupal 用户创建自定义模块以提供特定的功能集,他们可以在多个网站上重复使用这些功能。事实上,有一个模块专门用于提供导出配置和创建提供功能的模块的方法。这就是 Features 模块得名的原因。
Features 模块有两个子模块。主 Features 模块提供所有功能。Features UI 模块提供了一个用户界面来创建和管理功能。
我们将使用 Features 导出包含默认页面和文章内容类型的配置模块,以便它们可以在其他安装配置文件中使用。
如何操作...
- 首先,我们将使用 Composer 安装 Features 模块,这将同时下载其依赖项,即 配置更新管理器 模块:
$ cd /path/to/drupal8
$ composer require drupal/features
-
前往“扩展”并安装 Features UI 模块,确认安装 Features 和配置更新管理器的需求。
-
前往“配置”,在“开发”部分中,你会找到访问 Features 用户界面的链接;点击 Features:
![图片]()
-
点击“创建新功能”以开始制作自定义功能模块。
-
为功能提供“名称”,例如内容创作。
-
可选地,你可以提供描述。这作为模块的
info.yml文件中的description键。 -
切换内容类型分组并勾选文章和基本页面复选框以标记它们用于导出。
-
功能模块将自动添加检测到的依赖项或重要的配置项,例如字段和视图模式,以便也导出:
![图片]()
-
点击写入,将模块写入导出到你的 Drupal 站点
/modules/custom目录中的模块和配置。 -
前往扩展,搜索内容创作模块,并安装你新创建的模块。
它是如何工作的...
功能将静态 YAML 配置文件导出到模块的config/install文件夹中。功能通过确保存在特定类型的配置来修改标准的配置管理工作流程。配置管理不允许模块覆盖现有的配置对象,但功能管理和允许这样做。
为了实现这一点,功能(Features)提供了\Drupal\features\FeaturesConfigInstaller,它扩展了默认的config.install服务类。然后它修改服务定义以使用其FeaturesConfigInstaller类而不是默认的\Drupal\Core\Config\ConfigInstaller类。
除了调整config.install服务外,功能(Features)利用了配置管理系统的所有功能,以提供一种更简单的方式来生成模块。
任何模块都可以通过在它的info.yml中添加features: true键来被视为功能模块。这将允许通过功能 UI 来管理它。
更多内容...
功能是一个强大的工具,可以轻松提供捆绑配置;我们将在下一节中讨论更多使用功能模块的方法。
推荐的功能模块
功能模块提供了一个智能捆绑方法,它审查当前 Drupal 站点的配置,并建议应该创建以保留配置的功能模块。这些通过包分配插件提供。
这些插件使用逻辑将配置分配给特定的包:

当你访问功能 UI 时,它将向你展示建议导出的功能模块。展开项目将列出将要捆绑的配置项。点击建议功能链接将打开创建表单。或者,可以使用复选框与表单底部的下载存档或写入按钮一起使用。
未打包部分显示了一个未满足任何打包规则以将配置分组到指定模块的配置。这需要手动添加到创建的功能模块中。
功能捆绑包
在功能模块中,有捆绑包,捆绑包有自己的分配方法配置。功能内部捆绑包的目的是提供一种可以将配置分组到导出模块中的自动分配配置:

一个捆绑包有一个人类显示名称和机器名称。该捆绑包的机器名称将作为前缀添加到该捆绑包下生成的所有功能模块。你还可以指定捆绑包作为安装配置文件。在 Drupal 7 中,features UI 被广泛用于构建发行版和产生捆绑包功能的概念。
可以重新排列和配置分配方法,以满足你的需求。
管理 Features 的配置状态
Features UI 提供了一种方式来查看对功能配置所做的更改。如果一个由功能模块控制的项目被修改了,它将在 Features UI 的差异部分显示出来。这将允许你导入或更新功能模块以包含这些更改。
导入选项将强制网站使用模块配置 YAML 文件中定义的配置。例如,在下面的屏幕截图中,我们有一个导出的内容类型,其描述在导出后在用户界面中被修改了:

功能模块创建的差异被突出显示。如果检查了差异,并且你点击了“导入更改”,内容类型的描述将重置为配置中定义的版本。
从主要功能概览表中,可以将功能模块重新导出以包含更改并更新导出的 YAML 文件。
参见
- 请参考 Drupal.org 手册中的 Features 模块,链接为
www.drupal.org/docs/8/modules/features
第五章:前端为王
在本章中,我们将探索 Drupal 8 的前端开发世界。在本章中,我们将涵盖以下食谱:
-
基于 Classy 创建自定义主题
-
使用新的资产管理系统
-
Twig 模板
-
使用 Breakpoint 模块
-
使用 Responsive Image 模块
简介
Drupal 8 在前端方面带来了许多变化。现在它专注于以移动端为先的响应式设计。前端性能被赋予了高优先级,与 Drupal 的先前版本不同。有一个基于库的新资产管理系统,将为带有 Drupal 8 的页面提供所需的最小资产。
在 Drupal 8 中,我们有一个新特性,即 Twig 模板引擎,它取代了之前使用的 PHPTemplate 引擎。Twig 是大型 PHP 社区的一部分,并拥抱了更多 Drupal 8 的“在其他地方制作”的倡议。Drupal 7 支持库来定义 JavaScript 和 CSS 资源。然而,它非常基础,并且不支持库依赖的概念。
Drupal 核心提供了两个模块来实现带有服务器端组件的响应式设计。Breakpoint 模块提供了模块可以利用的媒体查询表示。Responsive Image 模块实现了 HTML5 的<picture>标签,用于图像字段。
本章深入探讨如何利用 Drupal 8 的前端特性以获得最佳效果。
基于 Classy 创建自定义主题
Drupal 8 附带了一个新的基础主题,旨在展示最佳实践和 CSS 类管理。Classy 主题由 Drupal 核心提供,是默认前端主题 Bartik 和管理主题 Seven 的基础主题。
与 Drupal 的先前版本不同,Drupal 8 提供了两个基础主题--Classy 和 Stable--以快速启动 Drupal 主题开发。Stable 提供了一个更精简的前端主题方法,具有更少的类和包装元素,并保证不会引入可能破坏子主题的更改。在本食谱中,我们将创建一个名为mytheme的新主题,其基础为 Classy。
如何操作...
-
在您的 Drupal 站点根目录中,在
themes文件夹内创建一个名为mytheme的文件夹。 -
在
mytheme文件夹内,创建一个mytheme.info.yml文件,以便 Drupal 能够发现主题。然后我们将编辑此文件:

- 首先,我们需要使用
name键定义themes名称:
name: My Theme
- 所有主题都需要提供一个
description键,该键将在外观页面上显示:
description: My custom theme
- 接下来,我们需要定义扩展的类型,即主题和核心版本的支持:
type: theme
core: 8.x
base theme调用允许我们指示 Drupal 使用特定的主题作为基础:
base theme: classy
- 最后一个项目是一个
regions键,用于定义可以放置的块区域,这是一个基于 YAML 的键值对数组:
regions:
header: 'Header'
primary_menu: 'Primary menu'
page_top: 'Page top'
page_bottom: 'Page bottom'
breadcrumb: 'Breadcrumb'
content: 'Content'
区域在页面 template 文件中渲染,这将在下一节中介绍,Twig 模板。
-
登录到你的 Drupal 网站,并从管理工具栏转到“外观”。
-
在“我的主题”条目中点击“安装”并设置默认值以启用和使用新的自定义主题:

它是如何工作的...
在 Drupal 8 中,info.yml 文件定义 Drupal 主题和模块。创建主题的第一步是提供 info.yml 文件,以便主题可以被发现。Drupal 将解析这些值并注册主题。
当你定义一个主题时,以下键是必需的,至少需要这些键:
-
name -
description -
type -
base theme -
core
name 键定义了将在外观页面上显示的主题的易读名称。描述将在外观页面的主题显示名称下方显示。所有 Drupal 项目都需要定义 type 键以指示正在定义的扩展类型。对于主题,类型必须始终是 theme。你还需要定义项目兼容的 Drupal 版本,使用 core 值。当你定义一个主题时,你还需要提供基本的 theme 键。如果你的主题不使用基本主题,则需要将值设置为 false。
libraries 和 region 键是可选的,但这是大多数主题提供的键。Drupal 的资产管理系统解析主题的 info.yml 文件,并在需要时添加这些库。区域在 info.yml 文件中定义,并为块模块提供放置块的区域。
还有更多...
接下来,我们将深入了解主题的附加信息。
主题截图
主题可以提供一个在“外观”页面上显示的截图。可以将一个 screenshot.png 图片文件放置在 theme 文件夹中,或者在一个 info.yml 文件中指定 screenshot 键下的文件来提供主题的截图。
如果截图缺失,将使用默认值,如 Classy 和 Stark 主题所示。通常,截图是使用主题的 Drupal 网站,包含通用内容。
主题、logo 和 favicon
Drupal 通过主题设置控制网站的 favicon 和 logo 设置。主题设置按主题逐个激活,而不是全局性的。主题可以通过在 theme 根文件夹中提供 logo.svg 来提供一个默认的 logo。放置在 theme 文件夹中的 favicon.ico 也将是网站 favicon 的默认值。
目前,无法为主题指定不同文件类型的 logo。Drupal 的早期版本寻找 logo.png。Drupal 8.5 计划了一个功能,允许 themes 定义 logo 的文件名和扩展名。有关更多信息,请参阅核心问题www.drupal.org/node/1507896。
您可以通过导航到外观并点击当前主题的设置来更改网站的标志和 favicon。取消选中 favicon 和标志设置的默认复选框,允许您提供自定义文件:

基础主题和共享资源
许多具有主题系统且支持基础(或父)主题的内容管理系统在术语使用上有所不同。基础主题的概念用于提供共享的既定资源,从而减少创建新主题所需的工作量。
在基础主题中定义的所有库都将默认继承并使用,允许子主题重用现有的样式和 JavaScript。这允许前端开发者重用他们的工作,并且只需创建对子主题所需的特定更改。
子主题也将继承基础主题提供的所有 Twig 模板覆盖。这是创建 Classy 主题所使用的倡议之一。与之前的版本相比,Drupal 8 对元素上应提供哪些类名做出了更少的假设。Classy 覆盖了核心的所有模板,并提供了合理的默认类,使主题能够使用它们并接受这些类名或提供一个空白画布。
CKEditor 样式表
如在第第二章“内容创作体验”中讨论的,Drupal 默认配备了 WYSIWYG 支持和 CKEditor 作为默认编辑器。CKEditor 模块将检查活动主题及其基础主题(如果提供),并加载在ckeditor_stylesheets键中定义的任何样式表作为值数组。
例如,以下代码可以在bartik.info.yml中找到:
ckeditor_stylesheets:
- css/base/elements.css
- css/components/captions.css
- css/components/table.css
这允许主题提供样式表,以美化 CKEditor 模块内的元素,从而增强编辑器的“所见即所得”体验。
参考信息
-
要使用
info.yml文件定义主题,请参阅www.drupal.org/node/2349827 -
要使用 Classy 作为基础主题,请参阅社区文档
www.drupal.org/theme-guide/8/classy -
请参阅核心主题文档
www.drupal.org/docs/8/core/themes -
要创建 Drupal 8 子主题,请参阅社区文档
www.drupal.org/node/2165673
使用新的资产管理系统
资产管理系统是最近加入 Drupal 8 的。资产管理系统允许模块和主题注册库。库定义了需要与页面一起加载的 CSS 样式表和 JavaScript 文件。Drupal 8 采用这种方法来提高前端性能。而不是加载所有 CSS 或 JavaScript 资产,只有指定库中当前页面所需的那些资产会被加载。
在这个菜谱中,我们将定义一个 libraries.yml 文件,该文件将定义由自定义主题提供的 CSS 样式表和 JavaScript 文件。
准备工作
这个菜谱假设你已经创建了一个自定义主题,例如你在第一个菜谱中创建的主题。当你在这个菜谱中看到 mytheme 时,请使用你创建的主题的机器名。
如何做到这一点...
-
在你的主题基本目录中创建一个名为
css的文件夹。 -
在你的
css文件夹中,添加一个style.css文件,该文件将包含主题的 CSS 声明。为了演示目的,将以下 CSS 声明添加到style.css:
body {
background: cornflowerblue;
}
-
然后,在你的主题目录中创建一个名为
js的文件夹,并添加一个scripts.js文件,该文件将包含主题的 JavaScript 项目。 -
在你的主题文件夹中,创建一个
mytheme.libraries.yml文件,如下截图所示:![图片]()
-
编辑
mytheme.libraries.yml文件。添加以下YAML文本以定义你的主题的global-styling库,该库将加载 CSS 和 JavaScript 文件:
global-styling:
version: VERSION
css:
theme:
css/style.css: {}
js:
js/scripts.js: {}
-
前面的文本告诉 Drupal 存在一个
global-styling库。你可以指定库版本并使用主题的VERSION默认值。它还定义了css/styles.css样式表作为theme组库的一部分。 -
编辑你的
mytheme.info.yml,并向你的global-styling库添加声明:
name: My Theme
description: My custom theme
type: theme
core: 8.x
base theme: classy
libraries:
- mytheme/global-styling
-
主题可以指定一个
libraries键来定义应该始终加载的库。此YAML数组列出了为每个页面加载的库。 -
前往配置,然后在开发部分下的性能中重建 Drupal 的缓存。
-
将你的主题设置为默认,导航到你的 Drupal 网站的首页。
-
你的主题的
global-styling库将被加载,页面的背景颜色将被适当地样式化:

它是如何工作的...
Drupal 聚合所有可用的 library.yml 文件并将它们传递给 library.discovery.parser 服务。此服务提供者的 default 类是 \Drupal\Core\Asset\LibraryDiscoveryParser。此服务从每个 library.yml 读取库定义并将其值返回给系统。在解析文件之前,解析器允许主题提供库的覆盖和扩展。
库是队列,因为它们附加到渲染的元素上。主题可以通过它们的 info.yml 文件通过 libraries 键通用地添加库。当主题处于活动状态时,这些库将始终在页面上加载。
CSS 样式表被添加到构建页面 head 标签的数据中。出于性能原因,JavaScript 资源默认在页面底部渲染。
还有更多...
我们将在下一节中更详细地探讨 Drupal 8 中围绕库的选项。
CSS 组
使用库,你可以通过不同的组指定 CSS。Drupal 的资产管理系统提供了以下 CSS 组:
-
基础
-
布局
-
组件
-
状态
-
主题
样式表按组列表的顺序加载。每一个都与 /core/includes/common.inc 中定义的 PHP 常量相关。这允许在处理样式表时分离关注点。Drupal 8 的 CSS 架构借鉴了 Scalable and Modular Architecture for CSS (SMACSS) 系统的概念来组织 CSS 声明。您可以在 smacss.com/ 上了解更多关于构建灵活和可扩展的 CSS 样式表的技巧。
库资产选项
库资产可以附加配置数据。如果没有提供配置项,将添加一个简单的空括号集合。因此,在每个示例中,行都以 {} 结尾。
以下示例取自 core.libraries.yml,添加了 HTML5shiv:
assets/vendor/html5shiv/html5shiv.min.js: { weight: -22, browsers: { IE: 'lte IE 8', '!IE': false }, minified: true }
让我们看看 html5shiv.min.js 的属性:
-
weight键确保脚本比其他库更早渲染 -
browser标签允许您指定条件规则来加载脚本 -
如果资产已经被压缩,您应该始终传递
minified为true
对于 CSS 资产,您可以传递一个媒体选项来指定资产的媒体查询。查看实现 \Drupal\Core\Asset\AssetCollectionRendererInterface 的类。
库依赖项
库可以指定其他库作为依赖项。这允许 Drupal 在前端性能上提供最小的占用。
只有当 JavaScript 库将其指定为依赖项时,才会加载 jQuery;有关库依赖项的更多信息,请参阅 www.drupal.org/node/1541860。
以下是从快速编辑模块的 libraries.yml 文件中的一个示例:
quickedit:
version: VERSION
js:
...
css:
...
dependencies:
- core/jquery
- core/jquery.once
- core/underscore
- core/backbone
- core/jquery.form
- core/jquery.ui.position
- core/drupal
- core/drupal.displace
- core/drupal.form
- core/drupal.ajax
- core/drupal.debounce
- core/drupalSettings
- core/drupal.dialog
快速编辑模块定义了 jQuery、jQuery Once 插件、Underscore 和 Backbone,并将其他定义的库作为依赖项。Drupal 将确保在 quickedit/quickedit 库附加到页面时这些库都存在。
Drupal 核心提供的默认库的完整列表可以在 core.libraries.yml 中找到,该文件位于 core/core.libraries.yml。
覆盖和扩展其他库
主题可以通过在它们的 info.yml 中使用 libraries-override 和 libraries-extend 键来覆盖库。这允许主题轻松地自定义现有库,而无需在特定库附加到页面时添加条件性地移除或添加其资产的逻辑。
libraries-override 键可以用来替换整个库,替换库中选定的文件,从库中移除资产,或者禁用整个库。以下代码将允许主题提供自定义 jQuery UI 主题:
libraries-override:
core/jquery.ui:
css:
component:
assets/vendor/jquery.ui/themes/base/core.css: false
theme:
assets/vendor/jquery.ui/themes/base/theme.css: css/jqueryui.css
覆盖声明模仿了原始配置。指定 false 将移除资产,否则提供的路径将替换该资产。
可以使用 libraries-extend 键来加载与现有库相关的额外库。以下代码将允许主题将 CSS 样式表与选定的 jQuery UI 声明覆盖相关联,而无需始终将其包含在主题的其他资产中:
libraries-extend:
core/jquery.ui:
- mytheme/jqueryui-theme
使用 CDN 或外部资源作为库
库也可以与外部资源一起工作,例如通过 CDN 加载的资产。这是通过提供文件位置的 URL 以及选定的文件参数来完成的。
以下是一个示例,说明如何从 MaxCDN 提供的 BootstrapCDN 添加 FontAwesome 字体图标库:
mytheme.fontawesome:
remote: http://fontawesome.io/
version: 4.4.0
license:
name: SIL OFL 1.1
url: http://fontawesome.io/license/
gpl-compatible: true
css:
base:
https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css: { type: external, minified: true }
远程库需要额外的元信息才能正常工作:
remote: http://fontawesome.io/
remote 键描述了库使用外部资源。虽然此键的验证仅限于其存在,但最好使用外部资源的主要网站来定义它:
version: 4.4.0
与所有库一样,需要一个版本号。这应该与添加的外部资源的版本相匹配:
license:
name: SIL OFL 1.1
url: http://fontawesome.io/license/
gpl-compatible: true
如果库定义了 remote 键,它还需要定义 license 键。这定义了许可证名称、许可证的 URL 以及检查它是否与 GPL 兼容。如果没有提供此键,将抛出 \Drupal\Core\Asset\Extension\LibraryDefinitionMissingLicenseException 异常:
css:
base:
https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css: { type: external, minified: true }
最后,特定的外部资源以正常方式添加。而不是提供相对文件路径,提供外部 URL。
从钩子中操作库
模块具有提供动态库定义和修改库的能力。一个模块可以使用 hook_library_info() 钩子来提供库定义。这不是定义库的推荐方式,但提供它是为了应对边缘用例。
模块没有使用 libraries-override 或 libraries-extend 的能力,需要依赖于 hook_library_info_alter() 钩子。您可以在 core/lib/Drupal/Core/Render/theme.api.php 或 api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!theme.api.php/function/hook_library_info_alter/8 中查看此钩子。
将 JavaScript 放置在头部
默认情况下,Drupal 确保 JavaScript 被放置在页面最后。这通过首先加载页面的关键部分来提高页面加载性能。现在将 JavaScript 放置在头部是一个可选的选项。
为了在头部渲染库,您需要添加 header: true 键/值对:
js-library:
header: true
js:
js/myscripts.js: {}
这将在页面的头部加载自定义 JavaScript 库及其依赖项。
参见
-
参考 Drupal 8 的 CSS 架构:在
www.drupal.org/node/1887918#separate-concerns中分离关注点 -
更多关于 SMACSS 的信息,请参阅
smacss.com/book/
Twig 模板
Drupal 8 的主题层由 Symfony 框架的组件 Twig 补充。Twig 是一种模板语言,其语法类似于 Django 和 Jinja 模板。Drupal 之前版本使用 PHPTemplate,这要求前端开发者对 PHP 有基本的了解。
在本食谱中,我们将覆盖 Twig 模板以提供对电子邮件表单元素的定制。我们将使用基本的 Twig 语法添加一个新类并提供一个默认占位符。
准备工作
本食谱假设你已经创建了一个自定义主题,例如你在第一个食谱中创建的主题。当你看到以下食谱中的 mythemein 时,请使用你创建的主题的机器名。
在撰写本书时,Classy 主题不提供电子邮件输入的模板建议,也没有对与 Drupal 核心不同的输入模板进行任何定制。
如何操作...
-
在你的主题基本目录中创建一个
templates文件夹来存放你的 Twig 模板。 -
要开始,你需要将
input.html.twig文件从core/themes/classy/templates/form/input.html.twig复制到你的主题的templates文件夹。 -
将
input.html.twig文件重命名为input--email.html.twig以使用正确的主题钩子建议,如以下截图所示:

- 我们将使用
addClassTwig 函数来添加一个input__email类:
<input{{ attributes.addClass('input__email') }}/>{{ children }}
- 在上一行之前,我们将使用三元运算符创建一个 Twig 变量以提供一个客户占位符:
{% set placeholder = attributes.placeholder ? attributes.placeholder : 'email@example.com' %}
<input{{ attributes.addClass('input__email').setAttribute('placeholder', placeholder) }}/>{{ children }}
-
这使用设置运算符创建了一个名为
placeholder的新变量。问号 (?) 运算符检查attributes对象中的placeholder属性是否为空。如果不为空,则使用现有值。如果值为空,则提供一个默认值。 -
转到配置选项卡,然后在开发部分下的性能下重建 Drupal 的缓存。我们需要这样做,因为 Drupal 缓存生成的 Twig 输出和模板覆盖。
主题提供的新 Twig 模板覆盖以及对 Twig 模板的任何更改都需要重建缓存。
- 假设你已经使用了标准的 Drupal 安装,请转到
/contact/feedback上安装的反馈联系表单,在未登录状态下,并审查电子邮件字段的更改:

这个截图包含了主题调试输出。在本章的 更多内容... 部分中,我们将讨论如何启用主题调试注释的输出。
它是如何工作的...
Drupal 的主题系统是围绕钩子和钩子建议构建的。电子邮件输入元素的元素定义定义了 input__email 主题钩子。如果没有通过 Twig 模板或 PHP 函数实现 input__email 钩子,它将降级到仅输入。
Drupal 主题钩子使用下划线 (_) 定义,但在 Twig 模板文件中使用时使用连字符 (-)。
处理器,如 Drupal 的主题层,将变量传递给 Twig。可以通过将变量名用花括号括起来来打印变量或对象属性。Drupal 核心的所有默认模板都在文件的文档块中提供信息,详细说明了可用的 Twig 变量。
Twig 具有简单的语法,包括基本的逻辑和函数。addClass方法将接受attributes变量,并添加除现有内容之外的类。
当提供主题钩子建议或修改现有模板时,您需要重新构建 Drupal 的缓存。编译后的 Twig 模板,就像 PHP 一样,被 Drupal 缓存,这样 Twig 就不需要在每次调用模板时都进行编译。
更多...
我们将在接下来的章节中讨论更多关于使用 Twig 的内容。
安全第一
Twig 默认自动转义输出,这使得 Drupal 8 成为迄今为止最安全的版本之一。对于 Drupal 7,大多数安全顾问都关注于贡献项目中跨站脚本(XSS)漏洞。使用 Twig,这些安全警告应该会大幅减少。
主题钩子建议
Drupal 利用主题钩子建议来允许基于不同条件输出变化。它允许站点主题为某些实例提供更具体的模板。
当一个主题钩子包含双下划线(__)时,Drupal 的主题系统会理解这一点,并且可以分解主题钩子以找到更通用的模板。例如,电子邮件元素定义提供了input__email作为其主题钩子。Drupal 理解如下:
-
寻找名为
input--email.html.twig的 Twig 模板或定义input__email的主题钩子 -
如果您不满意,请寻找名为
input.html.twig的 Twig 模板或定义输入的主题钩子
主题钩子建议可以通过.module或.theme文件中的hook_theme_suggestions()钩子提供。
调试模板文件选择和钩子建议
可以启用调试来检查构成页面的各种模板文件及其主题钩子建议,并检查哪些是活动的。这可以通过编辑sites/default/services.yml文件来完成。如果不存在services.yml文件,请复制default.services.yml以创建一个。
您需要在文件的twig.config部分下将debug: false更改为debug: true。这将导致 Drupal 主题层打印出包含模板信息的源代码注释。当调试开启时,Drupal 不会缓存编译后的 Twig 模板版本,而是即时渲染它们。
另有一个设置可以防止您在每次模板文件更改时都重新构建 Drupal 的缓存,但不要留下调试开启。可以将twig.config.auto_reload boolean设置为true。如果设置为true,当源代码更改时,Twig 模板将被重新编译。
Twig 逻辑和运算符
Twig 有三元运算符用于逻辑。使用问号(?),我们可以执行基本的是否为真或非空操作,而问号和冒号(?:)执行基本的是否为假或为空操作。
你也可以使用if和else逻辑根据变量提供不同的输出。
参见
-
请参阅 Twig 文档
twig.sensiolabs.org/documentation -
请参阅
api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21theme.api.php/function/hook_theme_suggestions_HOOK/8的hook_theme_suggestionsAPI 文档
使用 Breakpoint 模块
Breakpoint 模块提供了一个在 Drupal 中创建媒体查询断点定义的方法。这些可以被其他组件,如响应式图像和工具栏模块使用,以使 Drupal 响应。
断点是插件的一种类型,可以在模块或主题的目录中的breakpoints.yml中定义。在这个菜谱中,我们将在一个自定义组下定义三个不同的断点。
断点仅由安装的模块和主题的YAML文件定义,并且不能通过用户界面进行配置。
准备工作
确保 Breakpoint 模块已启用--如果你使用了标准的 Drupal 安装,该模块已启用。
此菜谱假设你已经创建了一个自定义模块。当你看到mymodule时,请使用你创建的模块的机器名。
如何做...
-
在你的模块基本目录中创建
mymodule.breakpoints.yml。此文件将包含断点配置。 -
首先,我们将添加一个标准的移动断点,它没有媒体查询,遵循移动优先的实践:
mymodule.mobile:
label: Mobile
mediaQuery: ''
weight: 0
- 第二,我们将创建一个标准断点,它将在更大的视口中运行:
mymodule.standard:
label: Standard
mediaQuery: 'only screen and (min-width: 60em)'
weight: 1
- 第三,我们将为具有大视口的设备创建一个宽断点:
mymodule.wide:
label: Wide
mediaQuery: 'only screen and (min-width: 70em)'
weight: 2
- 你的
mymodule.breakpoints.yml应该类似于以下内容:
mymodule.mobile:
label: Mobile
mediaQuery: ''
weight: 0
mymodule.standard:
label: Standard
mediaQuery: 'only screen and (min-width: 60em)'
weight: 1
mymodule.wide:
label: Wide
mediaQuery: 'only screen and (min-width: 70em)'
weight: 2
- 前往配置选项卡,然后转到性能以重建 Drupal 的缓存并使系统意识到新的断点。
它是如何工作的...
Breakpoint 模块定义了断点配置实体。断点没有任何特定的直接功能形式,除了提供保存媒体查询和分组的方式。
Breakpoint 模块提供了一个默认的管理服务。该服务被其他模块用来发现断点组,然后是组内的所有断点。
还有更多...
使用 Breakpoint 模块的更多信息将在接下来的章节中介绍。
提供断点时的注意事项
主题有提供断点的功能;然而,如果安装后添加了新的断点,它们将无法自动发现。Drupal 仅在主题安装或卸载时读取主题提供的断点。
在 breakpoint.manager 中,有两个钩子:一个用于 theme install,另一个用于 theme uninstall。每个钩子都会检索断点管理器服务并重建断点定义。除非这些钩子被触发,否则新添加到主题中的新断点不会被发现。
以编程方式访问断点
断点(Breakpoints)是其他模块的实用配置。可以通过断点管理器服务和指定一个组来加载断点。例如,以下代码返回了由工具栏模块使用的所有断点:
\Drupal::service('breakpoint.manager')->getBreakpointsByGroup('toolbar');
此代码调用 Drupal 容器以返回管理断点的服务,默认为 \Drupal\breakpoint\BreakpointManager。getBreakpointsByGroup 方法返回组内的所有断点,这些断点作为 \Drupal\breakpoint\BreakpointInterface 对象启动。
工具栏元素类利用此工作流程将断点媒体查询值作为 JavaScript 设置推送到 JavaScript 模型进行交互。
倍数
倍数值用于支持像素分辨率倍数。这个倍数与 视网膜 显示一起使用。它是视口设备分辨率的度量,作为设备物理尺寸和独立像素尺寸的比率。以下是一个标准倍数的示例:
-
1x 是正常情况
-
5x 支持 Android
-
2x 支持 Mac 视网膜设备
参见
- 要在 Drupal 8 中使用断点,请参考社区文档
www.drupal.org/documentation/modules/breakpoint
使用响应式图像模块
响应式图像模块为使用 HTML5 图片标签和源集的图像字段提供字段格式化器。利用断点模块,将断点映射到表示每个断点要使用的图像样式。
响应式图像字段格式化器与定义的响应式图像样式一起使用。响应式图像样式是将图像格式映射到特定断点和修饰符的配置。首先,您需要定义一个响应式图像样式,然后可以将其应用于图像字段。
在这个菜谱中,我们将创建一个名为 Article image 的响应式图像样式集,并将其应用于 Article 内容类型的图像字段。
准备工作
您需要启用 Responsive Image 模块,因为它在标准安装中不会自动启用。
如何操作...
-
前往配置,然后在媒体部分下的响应式图像样式。点击添加响应式图像样式以创建一个新的样式集。
-
提供一个标签,该标签将用于管理上识别响应式图像样式集。
-
选择一个将用作定义图片样式映射的断点源的断点组。
-
每个断点都会有一个
fieldset。展开fieldset并选择“选择单个图片样式”,然后选择合适的图片样式:

-
此外,选择一个回退图片样式,以防浏览器不支持源集,例如 Internet Explorer 8。
-
点击“保存”以保存配置,并添加新的样式集:

-
前往结构并选择内容类型,从文章内容类型的下拉菜单中选择“管理显示”。
-
将图片字段的格式化器更改为响应式图片。
-
点击字段格式化器的“设置”选项卡以选择您的新响应式图片样式集。从响应式图片样式下拉菜单中选择“文章图片”:

- 点击“更新”以保存字段格式化设置,然后点击“保存”以保存字段显示设置。
它是如何工作的...
响应式图片样式提供三个组件:一个响应式图片元素、响应式图片样式配置实体和响应式图片字段格式化器。配置实体被字段格式化器消费并通过响应式图片元素显示。
响应式图片样式实体包含一个断点到图片样式映射的数组。可用的断点由所选断点组定义。断点组可以随时更改;然而,之前的映射将会丢失。
响应式图片元素为每个断点打印一个 picture 元素,定义一个新的 source 元素。断点的媒体查询值作为元素的 media 属性提供。
对于 Internet Explorer 9,Drupal 8 随带 picturefill 多重填充。Internet Explorer 9 不识别由 picture 元素包裹的源元素。多重填充将源元素包裹在 picture 元素内的视频元素周围。
还有更多...
在以下章节中,我们将更详细地讨论响应式图片字段。
首先考虑性能
使用响应式图片格式化器的优点是性能。浏览器将只下载在适当的 source 标签的 srcset 中定义的资源。这不仅允许你提供更合适的图片大小,而且在小型设备上也会携带更小的负载。
移除 picturefill 多重填充
响应式图片模块将 picturefill 库附加到响应式图片元素定义。元素的模板还提供了实现多重填充的 HTML。可以通过覆盖元素的模板并覆盖 picturefill 库来禁用多重填充。
以下片段,当添加到主题的 info.yml 中时,将禁用 picturefill 库:
libraries-override:
core/picturefill: false
然后,responsive-image.html.twig 必须由主题覆盖以删除模板中为 polyfill 生成的额外 HTML:
-
将
responsive-image.html.twig从core/modules/responsive_image/templates复制到theme模板文件夹。 -
编辑
responsive-image.html.twig并删除 Twig 注释和 IE 条件语句以输出初始视频标签。 -
删除最后一个条件语句,该语句提供了关闭视频标签。
参考以下内容
-
参考 Mozilla 开发者网络(MDN)上的图片元素,链接为
developer.mozilla.org/en-US/docs/Web/HTML/Element/picture -
参考针对 IE9 的
picturefill,链接为scottjehl.github.io/picturefill/#ie9
第六章:使用 Form API 创建表单
在本章中,我们将探讨各种与 Drupal 中的表单一起工作的菜谱:
-
创建一个表单
-
使用新的 HTML5 元素
-
验证表单数据
-
处理提交的表单数据
-
修改其他表单
简介
Drupal 提供了一个强大的 API 来创建和管理表单,而无需编写任何 HTML。Drupal 处理表单构建、验证和提交。Drupal 处理构建表单或处理 HTTP POST 请求的请求。这允许开发者简单地定义表单中的元素,提供所需的任何额外验证,然后通过特定方法处理成功的提交。
本章包含各种通过 Form API 与 Drupal 中的表单一起工作的菜谱。在 Drupal 8 中,表单和表单状态是对象。
创建一个表单
在这个菜谱中,我们将创建一个表单,它可以通过菜单路径访问。这涉及到创建一个路由,告诉 Drupal 调用我们的表单并将其显示给最终用户。
表单被定义为类,这些类实现了\Drupal\Core\Form\FormInterface。\Drupal\Core\Form\FormBase作为一个实用类,旨在被扩展。我们将扩展这个类来创建一个新的表单。
准备工作
由于我们将编写代码,你将需要一个自定义模块。在 Drupal 中创建自定义模块很简单:创建一个文件夹和一个info.yml文件。对于这个菜谱,我们将在你的 Drupal 文件夹的/modules下创建一个名为drupalform的文件夹。
在drupalform文件夹中,创建drupalform.info.yml。Drupal 将解析info.yml文件以发现模块。一个模块的info.yml文件示例如下:
name: Drupal form example
description: Create a basic Drupal form, accessible from a route
type: module
version: 1.0
core: 8.x
名称将是你的模块名称,描述将在扩展页面上列出。指定核心告诉 Drupal 它为哪个版本的 Drupal 构建。第四章,扩展 Drupal,涵盖了如何深入创建模块。
如何做到这一点...
-
在你的模块目录中创建一个
src文件夹。在这个目录中,创建一个Form目录,它将包含定义你的表单的类。 -
接下来,在你的模块的
src/Form目录中创建一个名为ExampleForm.php的文件。
Drupal 利用 PSR4 来发现和自动加载类。为了简洁,这定义了每个文件应该有一个类,每个文件名应与类名匹配。文件夹结构也将模仿预期的命名空间。
- 我们将编辑
ExampleForm.php文件,并添加适当的 PHP 命名空间、使用的类以及类本身:
<?php
namespace Drupal\drupalform\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class ExampleForm extends FormBase {
}
namespace定义了模块的Form目录中的类。autoloader现在将查看drupalform模块路径,并从src/Form目录加载ExampleForm类。
使用语句允许我们在引用FormBase和,在接下来的步骤中,FormStateInterface时只使用类名。否则,我们被迫在每次使用每个类时使用完全限定的命名空间路径。
\Drupal\Core\Form\FormBase是一个抽象类,并要求我们实现四个剩余的接口方法:getFormId、buildForm、validateForm和submitForm。后两个方法将在接下来的食谱中介绍;然而,我们需要定义方法占位符:
class ExampleForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'drupalform_example_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Return array of Form API elements.
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Validation covered in later recipe, required to satisfy interface.
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Validation covered in later recipe, required to satisfy interface.
}
}
这段代码清除了前面步骤中的初始类定义。FormBase 提供了 utility 方法,并不满足 FormStateInterface 的接口要求。我们在这里定义这些,因为它们在每个表单定义中都是独特的。
getFormId 方法返回一个唯一的字符串来标识表单,例如,site_information。你可能会遇到一些表单在其表单 ID 的末尾附加 _form。这不是必需的,而只是 Drupal 早期版本中常见的命名约定。
buildForm 方法将在以下步骤中介绍。validateForm 和 submitForm 方法都在表单 API 过程中调用,将在后面的食谱中介绍。
buildForm方法将被调用以返回渲染给最终用户的表单 API 元素。我们将添加一个简单的文本字段来请求公司名称和一个提交按钮:
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['company_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Company name'),
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
];
return $form;
}
我们向 form 数组中添加了一个表单元素定义。表单元素通过指定元素类型和用作标签的标题来定义,标题使用 t 方法来确保它是可翻译的。
添加提交按钮是通过提供一个类型为提交的元素来完成的。
- 要访问表单,我们将在模块文件夹中创建
drupalform.routing.yml。将创建一个路由条目来指示 Drupal 使用\Drupal\Core\Form\FormBuilder创建和显示我们的表单:
drupalform.form:
path: '/drupal-example-form'
defaults:
_title: 'Example form'
_form: '\Drupal\drupalform\Form\ExampleForm'
requirements:
_access: 'TRUE'
在 Drupal 中,所有路由都有一个名称,这个例子将其定义为 drupalform.form。路由定义了一个路径属性并覆盖了默认变量。这个路由定义改变了路由的标题,将其指定为表单,并给出了此表单类的完全限定命名空间路径。
路由需要传递一个具有特定说明的 requirements 属性,否则路由将被拒绝访问。
-
前往扩展页面并安装我们创建的 Drupal 表单示例模块。
-
前往
/drupal-example-form,现在表单应该可见,如下面的截图所示:

它是如何工作的...
这个食谱创建了一个路由来显示表单。通过在我们的路由条目默认部分传递 _form 变量,我们告诉路由控制器如何渲染我们的路由内容。包含命名空间的完全限定类名被传递到表单构建器中的方法。根据食谱,路由控制器将调用 \Drupal::formBuilder()->getForm (\Drupal\drupalform\Form\ExampleForm)。同时,这也可以手动调用以在其他地方嵌入表单。
一个实现 \Drupal\Core\Form\FormBuilderInterface 的表单构建实例将调用 buildForm 并启动渲染过程来处理表单。buildForm 方法预期返回一个表单元素和其他 API 选项的数组。这将发送到渲染系统以输出表单作为 HTML。
还有更多...
许多组件构成了通过 Drupal 的表单 API 创建的表单。我们将深入探讨其中的一些。
表单元素定义
表单是一组表单元素,这些元素是 Drupal 8 中的插件类型。插件是 Drupal 8 中的可互换功能的小块。插件和插件开发在第七章,使用插件轻松连接中有所介绍。
这里有一些最常用的元素属性,可以使用:
-
weight: 这用于改变表单元素在表单中的位置。默认情况下,元素将按照它们被添加到表单数组中的顺序显示。定义一个权重允许开发者控制元素的位置。 -
default_value: 这使开发者能够预先填充元素,例如,在构建具有现有数据的配置表单或编辑实体时。 -
placeholder: 这在 Drupal 8 中是新的。Drupal 8 提供了新的 HTML5 支持,此属性将在 HTML 输入上设置占位符属性。
要查看可用表单元素及其属性的完整参考,请访问 Drupal.org API 文档页面api.drupal.org/api/drupal/elements/。
表单状态
\Drupal\Core\Form\FormStateInterface 对象表示表单及其数据的当前状态。表单状态包含用户提交给表单的数据以及构建状态信息。表单提交后的重定向通过处理。
表单状态,同样。你将在验证和提交过程中更多地与表单状态交互。
表单缓存
Drupal 使用表单缓存表。它保存了由表单构建标识符识别的构建表。这允许 Drupal 在 AJAX 请求期间验证表单,并在需要时轻松构建它们。保持表单缓存在持久存储中很重要;否则,可能会有不良后果,例如丢失表单数据或使表单无效。
参见
-
Drupal 8 中的表单 API
www.drupal.org/node/2117411 -
参阅第四章,扩展 Drupal
-
参阅第七章,使用插件轻松连接,了解更多关于派生内容的信息。
使用新的 HTML5 元素
随着 Drupal 8 的发布,Drupal 终于进入了 HTML5 的领域。表单 API 现在允许直接使用 HTML5 输入元素。以下是一些元素类型:
-
电话 -
电子邮件 -
数字 -
日期 -
网址 -
搜索 -
范围
这使得 Drupal 中的表单可以利用原生设备输入方法和原生验证支持。
准备工作
这个菜谱将指导您如何在 Drupal 表单中添加元素。您需要通过模块实现自定义表单,例如本章中创建表单菜谱中创建的表单。
如何操作...
- 要使用电话输入,您需要在
buildForm方法中添加一个新的tel类型的form元素定义:
$form['phone'] = [
'#type' => 'tel',
'#title' => $this->t('Phone'),
];
- 要使用电子邮件输入,您需要在
buildForm方法中添加一个新的email类型的form元素定义。它将在表单 API 中验证电子邮件地址的格式:
$form['email'] = [
'#type' => 'email',
'#title' => $this->t('Email'),
];
- 要使用数字输入,您需要在
buildForm方法中添加一个新的数字类型的form元素定义。它将验证数字的范围和格式:
$form['integer'] = [
'#type' => 'number',
'#title' => $this->t('Some integer'),
// The increment or decrement amount
'#step' => 1,
// Miminum allowed value
'#min' => 0,
// Maxmimum allowed value
'#max' => 100,
];
- 要使用日期输入,您需要在
buildForm方法中添加一个新的date类型的form元素定义。您还可以传递#date_date_format选项来更改输入使用的格式:
$form['date'] = [
'#type' => 'date',
'#title' => $this->t('Date'),
'#date_date_format' => 'Y-m-d',
];
- 要使用 URL 输入,您需要在
buildForm方法中添加一个新的url类型的form元素定义。该元素有一个验证器来检查 URL 的格式:
$form['website'] = [
'#type' => 'url',
'#title' => $this->t('Website'),
];
- 要使用搜索输入,您需要在
buildForm方法中添加一个新的search类型的form元素定义。您可以指定搜索字段将查询以自动完成选项的路由名称:
$form['search'] = [
'#type' => 'search',
'#title' => $this->t('Search'),
'#autocomplete_route_name' => FALSE,
];
- 要使用
range输入,您需要在buildForm方法中添加一个新的range类型的form元素定义。它是数字元素的扩展,接受min、max和step属性来控制范围输入的值:
$form['range'] = [
'#type' => 'range',
'#title' => $this->t('Range'),
'#min' => 0,
'#max' => 100,
'#step' => 1,
];
它是如何工作的...
每个类型都引用了\Drupal\Core\Render\Element\FormElement的扩展类。它提供了元素的定义和附加功能。每个元素在其类中定义了一个prerender方法,该方法定义了input类型属性以及其他附加属性。
每个输入定义其主题为input__TYPE,允许您将input.html.twig基本模板复制到input.TYPE.html.twig以进行模板化。模板随后解析属性并渲染 HTML。
一些元素,如电子邮件,为元素提供了验证器。电子邮件元素定义了validateEmail方法。以下是从\Drupal\Core\Render\Element\Email::validateEmail的代码示例:
/**
* Form element validation handler for #type 'email'.
*
* Note that #maxlength and #required is validated by _form_validate() already.
*/
public static function validateEmail(&$element, FormStateInterface $form_state, &$complete_form) {
$value = trim($element['#value']);
$form_state->setValueForElement($element, $value);
if ($value !== '' && !\Drupal::service('email.validator')->isValid($value)) {
$form_state->setError($element, t('The email address %mail is not valid.', array('%mail' => $value)));
}
}
此代码将在表单提交时执行,并验证提供者的电子邮件。它通过获取当前值并删除任何空白字符,并使用表单状态对象来更新值来实现这一点。调用 email.validator 服务来验证电子邮件。如果此方法返回 false,则调用表单状态来标记元素为有错误的元素。如果元素有错误,表单构建器将阻止表单提交,将用户返回到表单以修复值。
还有更多...
元素通过 Drupal 的插件系统提供,并在接下来的章节中详细探讨。
特定元素属性
元素可以有自己的独特属性以及单独的验证方法。您可以通过 Drupal.org API 文档页面在 api.drupal.org/api/drupal/elements/ 中参考可用的元素。然而,也可以检查这些类,并阅读定义方法来了解每个元素的属性。这些类位于 /core/lib/Drupal/Core/Render/Element 下的 \Drupal\Core\Render\Element 命名空间中:

创建新元素
在 Form API 中使用的每个元素都扩展了 \Drupal\Core\Render\Element\FormElement 类,这是一个插件。模块可以通过向它们的 Plugins/Element 命名空间添加类来提供新的元素类型。有关如何实现插件的更多信息,请参阅第 7 章**,使用插件轻松连接*。
参见
-
Drupal 8 的表单 API 在
www.drupal.org/node/2117411 -
参考第 7 章,使用插件轻松连接
验证表单数据
所有表单都必须实现 \Drupal\Core\Form\FormInterface 接口。该接口定义了一个 validation 方法。validateForm 方法在表单提交后调用,并提供了一种验证数据和在需要时停止数据处理的方式。表单状态对象提供了标记特定字段为错误的方法,为用户提供了一个工具来提醒用户指定问题输入。
在这个菜谱中,我们将验证提交字段的长度。
准备工作
此菜谱将使用在第一个 创建表单 菜谱中创建的模块和自定义表单。
如何实现...
-
在模块的
src/Form目录中打开并编辑\Drupal\drupalform\Form\ExampleForm类。 -
在验证
company_name值之前,我们需要使用\Drupal\Core\Form\FormStateInterface对象中的isValueEmpty()方法来检查值是否为空:
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('company_name')) {
// Value is set, perform validation.
}
}
-
\Drupal\Form\FormStateInterface::isValueEmpty方法接受表单元素的键名;例如,从buildForm方法中的$form['company_name']通过isValueEmpty方法中的company_name进行引用。 -
接下来,我们将检查值的长度是否大于五:
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('company_name')) {
if (strlen($form_state->getValue('company_name')) <= 5) {
// Set validation error.
}
}
}
getValue接受表单元素的键并返回值。由于我们已经验证了值不为空,我们可以检索该值。
如果您对 Drupal 的先前版本有任何经验,请注意,表单状态现在是一个对象而不是数组。
- 如果逻辑检查发现长度为五个或更短的值,它将抛出表单错误以防止提交:
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('company_name')) {
if (strlen($form_state->getValue('company_name')) <= 5) {
$form_state->setErrorByName('company_name', t('Company name is less than 5 characters'));
}
}
}
我们可以在strlen逻辑检查中放置setErrorByName方法。如果字符串少于五个字符,将在元素上设置错误。第一个参数是元素的键,第二个参数是要向用户显示的消息。
- 当表单提交时,公司名称文本字段将包含超过五个字符或为空才能提交。

它是如何工作的...
在表单构建服务调用表单对象的submitForm方法之前,它将调用对象的validateForm方法。在验证方法中,可以使用表单状态来检查值和执行逻辑检查。如果项目被认为无效并在元素上设置错误,则表单无法提交,并将显示错误给用户。
当向元素添加错误时,表单上的错误总数会增加。如果表单有任何错误,表单构建服务将不会执行提交方法。
此过程通过\Drupal\Core\Form\FormValidator类执行,该类通过表单构建服务运行。
更多...
表单验证可以通过多个处理程序和元素级别进行。以下章节将介绍这些内容。
多个验证处理程序
表单可以有多个验证处理程序。默认情况下,所有表单都至少有一个验证器,即其自己的validateForm方法。还可以添加更多。然而,默认情况下,表单将仅执行::validateForm和所有元素验证器。这允许您在类或表单上调用方法。
如果一个类提供了它希望执行的method1和method2,则可以在buildForm方法中添加以下代码:
$form_state->setValidateHandlers([
['::validateForm'],
['::method1'],
[$this, 'method2'],
]);
这将设置验证器数组以执行默认的validateForm方法和两个附加方法。您可以使用两个冒号(::)和方法名称在当前类中引用方法。或者,您可以使用一个包含类实例和要调用的方法的数组。
访问多维数组值
表单支持在表单数组中嵌套表单元素。默认的\Drupal\Core\Form\FormStateInterface实现,\Drupal\Core\Form\FormState,支持访问多维数组值。您可以通过传递表示表单数组中父数组结构的数组,而不是传递字符串。
如果元素定义为 in $form['company']['company_name'],则我们将 ['company', 'company_name'] 传递给表单状态的方法。
元素验证方法
表单元素可以有自己的验证器。表单状态将聚合所有元素验证方法并将它们传递给表单验证服务。这将与表单的验证一起运行。
有一个 limit_validation_errors 选项,可以设置为允许传递选定的无效错误。此选项允许你绕过表单中特定元素的验证。如果表单有两个提交按钮,并且每个按钮都打算验证和提交特定数据,这很有用。此属性定义在提交按钮中,也称为表单状态中的 触发 元素。它是一个由表单元素键组成的数组值。
处理提交的表单数据
表单的目的是收集数据并对提交的数据做些处理。所有表单都需要实现 \Drupal\Core\Form\FormInterface 接口。该接口定义了一个提交方法。一旦表单 API 调用了类的验证方法,就可以运行提交方法。
此配方将基于本章 创建表单 配方中创建的自定义模块和表单。我们将表单转换为 \Drupal\Core\Form\ConfigBaseForm,这样我们就可以保存我们的配置并重用 Drupal 核心提供的代码。
准备工作
在此配方中,我们将使用在第一个 创建表单 配方中创建的模块和自定义表单。
如何做到这一点...
-
在你的模块目录中,创建一个
config目录,然后在其中创建一个名为install的目录。 -
创建一个名为
drupalform.schema.yml的文件。此文件将告诉 Drupal 我们想要保存的配置项。 -
将以下配置模式定义添加到
drupalform.schema.yml文件中:
drupalform.company:
type: config_object
label: 'Drupal form settings'
mapping:
company_name:
type: string
label: 'A company name'
这告诉 Drupal 我们有一个名为 drupalform.company 的配置,并且它有一个有效的选项 company_name。我们将在第九章,配置管理 - 在 Drupal 8 中部署中更详细地介绍这一点。
- 接下来,编辑模块的
src/Form/ExampleForm.php文件。将FormBase使用语句替换为使用ConfigFormBase类:
<?php
namespace Drupal\drupalform\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
- 将
ExampleForm类更新为扩展ConfigFormBase,以利用其现有方法和提供的代码:
<?php
namespace Drupal\drupalform\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
class ExampleForm extends ConfigFormBase {
...
}
这允许我们重用 ConfigFormBase 类中的方法并减少我们自己的实现。
- 为了使
ExampleForm实现ConfigFormBase,需要实现getEditableConfigNames方法以满足\Drupal\Core\Form\ConfigBaseTrait特性。此方法可以添加到类的任何位置:
<?php
namespace Drupal\drupalform\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
class ExampleForm extends ConfigFormBase {
...
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['drupalform.company'];
}
...
}
此函数定义了配置名称,这些名称可以通过表单进行编辑。这使 drupalform.company 对象下的所有属性在通过 ConfigFormBaseTrait 提供的 config 方法访问时都可编辑。
- 我们将移除提交表单元素(
$form['submit'])并更新buildForm方法,使其从父方法返回数据而不是从$form本身返回。我们还需要向company_name添加#default_value选项,以便在下次加载我们的表单时使用现有值:
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['company_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Company name'),
'#default_value' => $this->config('drupalform.company')->get('company_name'),
];
return parent::buildForm($form, $form_state);
}
ConfigFormBase类实现了buildForm方法以提供可重用的提交按钮。它还统一了 Drupal 配置表单的展示:

ConfigFormBase提供了一个配置工厂方法。我们将向我们的元素添加一个default_value属性,以包含当前保存的项目:
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['company_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Company name'),
'#default_value' => $this->config('drupalform.company')->get('name'),
];
return parent::buildForm($form, $form_state);
}
#default_value键被添加到元素的定义中。它调用由ConfigFormBaseTrait提供的config方法来加载我们的配置组并访问特定的配置值。
- 最后一步是在
submitForm方法中保存配置。向你的类中添加以下方法:
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$this->config('drupalform.company')->set('name', $form_state->getValue('company_name'));
}
通过指定我们的配置组来调用config方法。然后我们将使用set方法来定义名称作为公司名称文本字段的值。
- 完成后,你的表单类应类似于以下内容:
<?php
namespace Drupal\drupalform\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
class ExampleForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['drupalform.company'];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'drupalform_example_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['company_name'] = array(
'#type' => 'textfield',
'#title' => t('Company name'),
'#default_value' => $this->config('drupalform.company')->get('name'),
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('company_name')) {
if (strlen($form_state->getValue('company_name')) <= 5) {
$form_state->setErrorByName('company_name', t('Company name is less than 5 characters'));
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$this->config('drupalform.company')->set('name', $form_state->getValue('company_name'));
}
}
- 当你编辑你的表单并点击提交按钮时,你在公司名称字段中输入的值现在将被保存在配置中。
它是如何工作的...
ConfigFormBase利用ConfigFormBaseTrait提供对配置工厂的便捷访问。该类对buildForm的实现还向表单添加了submit按钮和主题样式。提交处理程序显示配置保存消息,但依赖于实现模块来保存配置。
该表单在drupalform.company命名空间下保存其数据。公司名称值存储为name,可以通过drupalform.company.name访问。请注意,配置名称不必与表单元素的键匹配。
更多内容...
在下一节中,我们将介绍如何处理多个提交回调。
多个提交处理程序
表单可以有多个提交处理程序。默认情况下,所有表单都实现了一个提交处理程序,即其自己的submitForm方法。表单将自动执行::submitForm并触发元素上定义的任何其他方法。还可以添加更多内容。然而,这允许你调用其他类或其他表单上的static方法。
如果一个类提供了method1和method2,它希望同时执行,可以在buildForm方法中添加以下代码:
$form_state->setSubmitHandlers([
['::submitForm'],
['::method1'],
[$this, 'method2']
]);
这将设置提交处理程序数组以执行默认的submitForm方法以及两个附加方法。你可以使用两个冒号(::)和方法名称来引用当前类中的方法。或者,你可以使用一个包含类实例和要调用的方法的数组。
参见
- 参阅第九章,配置管理-在 Drupal 8 中部署
修改其他表单
Drupal 的表单 API 不仅提供创建表单的方式。还有通过自定义模块来修改表单的方法,该模块允许你操作核心和贡献的表单。使用这种技术,可以添加新元素,更改默认值,甚至可以将元素隐藏以简化用户体验。
表单的修改不是在自定义类中发生的;这是一个在模块文件中定义的钩子。在这个配方中,我们将使用hook_form_FORM_ID_alter()钩子向网站的配置表单添加电话字段。
准备工作
这个配方假设你已经有一个自定义模块来添加代码。
如何操作...
-
在你的 Drupal 站点的
modules文件夹中,创建一个名为mymodule的文件夹。 -
在
mymodule文件夹中,创建一个mymodule.info.yml文件,包含以下代码:
name: My module
description: Custom module that uses a form alter
type: module
core: 8.x
- 接下来,在你的模块目录中创建一个名为
mymodule.module的文件:
<?php
/**
* @file
* Custom module that alters forms.
*/
- 添加
mymodule_form_system_site_information_settings_alter()钩子。可以通过查看表单的类并审查getFormId方法来找到表单 ID:
/**
* Implements hook_form_FORM_ID_alter().
*/
function mymodule_form_system_site_information_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
// Code to alter form or form state here
}
Drupal 将调用此钩子并传递当前表单数组和其表单状态对象。表单数组是通过引用传递的,允许我们的钩子修改数组而不返回任何值。这就是为什么$form参数前面有反引号(&)。在 PHP 中,所有对象都是通过引用传递的,这就是为什么我们在$form_state前面没有反引号(&)。
当在普通文件中调用类时,例如模块文件,你需要使用完全限定的类名,或者在文件开头添加一个 use 语句。在这个例子中,我们可以添加\Drupal\Core\Form\FormStateInterface。
- 接下来,我们将添加我们的
telephone字段到表单中,以便它可以显示和保存:
/**
* Implements hook_form_FORM_ID_alter().
*/
function mymodule_form_system_site_information_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$form['site_phone'] = [
'#type' => 'tel',
'#title' => t('Site phone'),
'#default_value' => Drupal::config('system.site')->get('phone'),
];
}
我们从system.site检索当前电话值,以便如果已经设置,则可以对其进行修改。
-
前往扩展页面并安装我们创建的模块“我的模块”。
-
注意配置下的基本站点设置表单,并测试设置站点电话号码:
![图片]()
-
我们需要添加一个提交处理程序来保存我们新字段的配置。我们需要向表单添加一个提交处理程序和一个提交处理程序回调:
/**
* Implements hook_form_FORM_ID_alter().
*/
function mymodule_form_system_site_information_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$form['site_phone'] = [
'#type' => 'tel',
'#title' => t('Site phone'),
'#default_value' => Drupal::config('system.site')->get('phone'),
];
$form['#submit'][] = 'mymodule_system_site_information_phone_submit';
}
/**
* Form callback to save site_phone
* @param array $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
*/
function mymodule_system_site_information_phone_submit(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$config = Drupal::configFactory()->getEditable('system.site');
$config
->set('phone', $form_state->getValue('site_phone'))
->save();
}
The $form['#submit'] modification adds our callback to the form's submit handlers. This allows our module to interact with the form once it has been submitted.
The mymodule_system_site_information_phone_submit callback is passed the form array and form state. We load the current configuration factory to receive the configuration that can be edited. We then load system.site and save phone based on the value from the form state.
- 提交表单,并验证数据是否已保存。
它是如何工作的...
\Drupal\system\Form\SiteInformationForm类扩展了\Drupal\Core\Form\ConfigFormBase以处理将表单元素作为单个配置值写入。然而,它不会自动将值写入表单状态。在这个配方中,我们需要添加一个提交处理程序,通过在mymodule.module文件中的过程函数手动保存我们添加的字段。
表单数组是通过引用传递的,允许在钩子中修改原始数据。这使我们能够添加元素,甚至修改现有项目,如标题或描述。
还有更多...
我们将讨论如何使用表单修改向其他表单添加额外的处理程序。
添加额外的验证处理程序
使用表单修改钩子,我们可以向表单添加额外的验证器。正确的方法是加载当前的验证器,将新的一个添加到数组中,并在表单状态中重置验证器:
$validators = $form_state->getValidateHandlers();
$validators[] = 'mymodule_form_validate';
$form_state->setValidateHandlers($validators);
首先,我们将从表单状态中接收所有当前设置的验证器,作为变量 $validators。然后,我们将一个新的回调函数添加到数组的末尾。一旦 $validators 变量被修改,我们将通过执行 setValidateHandlers 方法来覆盖表单状态的验证器数组。
您还可以使用 PHP 数组操作函数以不同的执行顺序添加您的验证器。例如,array_unshift 将您的验证器放置在数组的开头,以便它首先运行。
添加额外的提交处理程序
使用表单修改钩子,我们可以向表单添加额外的提交处理程序。正确的方法是加载当前的提交处理程序,将新的一个添加到数组中,并在表单状态中重置验证器:
$submit_handlers = $form_state->getSubmitHandlers();
$submit_handlers[] = 'mymodule_form_submit';
$form_state->setSubmitHandlers($submit_handlers );
首先,我们将从表单状态中接收所有当前设置的提交处理程序,作为变量 $submit_handlers。然后,我们将一个新的回调函数添加到数组的末尾。
一旦 $submit_handlers 变量被修改,我们将通过执行 setSubmitHandlers 方法来覆盖表单状态的提交处理程序数组。
您还可以使用 PHP 数组操作函数以不同的执行顺序添加您的回调。例如,array_unshift 将您的回调放置在数组的开头,以便它首先运行。
第七章:插件即插即用
在本章中,我们将深入探讨 Drupal 8 中提供的新插件 API:
-
使用插件创建块
-
创建自定义字段类型
-
创建自定义字段小部件
-
创建自定义字段格式化器
-
创建自定义插件类型
简介
Drupal 8 引入了插件。插件在 Drupal 中驱动许多项目,例如块、字段类型和字段格式化器。插件和插件类型由模块提供。它们提供可交换和特定的功能。正如在第五章,前端为王中讨论的,断点也是插件。在本章中,我们将讨论插件在 Drupal 8 中的工作方式,并展示如何创建块、字段和自定义插件类型。
每个版本的 Drupal 都有子系统,它们提供了可插拔的组件和贡献模块。然而,这些子系统的实现和管理带来了问题。块、字段和图像样式各自有不同的系统需要学习和理解。插件 API 存在于 Drupal 8 中,以减轻这个问题并提供一个基础 API 来实现可插拔组件。这大大提高了与 Drupal 核心子系统一起工作的开发者体验。在本章中,我们将实现一个块插件。我们将使用插件 API 来提供自定义字段类型以及字段的部件和格式化器。最后一个食谱将向您展示如何创建和使用自定义插件类型。
使用插件创建块
在 Drupal 中,块是可以放置在主题提供的区域中的内容片段。块用于展示特定类型的内容,例如用户登录表单、文本片段等等。
块是注解插件。注解插件使用文档块来提供插件的详细信息。它们在模块的 Plugin 类命名空间中被发现。Plugin/Block 命名空间中的每个类都将被块模块的插件管理器发现。
在本食谱中,我们将定义一个块,该块将显示版权片段和当前年份,并将其放置在页脚区域:
准备工作
创建一个新模块,如本食谱中所示,并定义 info.yml 以便 Drupal 能够发现它。在整个食谱中,我们将把该模块称为 mymodule。请使用您模块的适当名称。
如何操作...
-
在您的模块中创建一个
src/Plugin/Block目录。这将翻译\Drupal\mymodule\Plugin\Block命名空间并允许块插件发现。 -
在新创建的文件夹中创建一个
Copyright.php文件,以便我们可以为我们的块定义Copyright类:

Copyright类将扩展\Drupal\Core\Block\BlockBase类:
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
class Copyright extends BlockBase {
}
我们将扩展实现 \Drupal\Core\Block\BlockPluginInterface 的 BlockBase 类,并为我们提供接口几乎所有方法的实现。
- 我们将提供块的标识符、管理标签和类别:
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* @Block(
* id = "copyright_block",
* admin_label = @Translation("Copyright"),
* category = @Translation("Custom")
* )
*/
class Copyright extends BlockBase {
}
类的注释文档块通过 @Block 识别插件类型。Drupal 将解析此信息并使用其中定义的属性启动插件。id 是内部机器名称,admin_label 在块列表页面上显示,而 category 则出现在块选择列表中。
- 我们需要提供一个
build方法以满足\Drupal\Core\Block\BlockPluginInterface接口。这个方法返回要显示的输出:
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* @Block(
* id = "copyright_block",
* admin_label = @Translation("Copyright"),
* category = @Translation("Custom")
* )
*/
class Copyright extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
$date = new \DateTime();
return [
'#markup' => t('Copyright @year© My Company', [
'@year' => $date->format('Y'),
]),
];
}
}
build 方法返回一个使用 Drupal 的 t 函数替换 @year 的渲染数组,该数组是格式化为完整年份的 \DateTime 对象的输出。
自 PHP 5.4 以来,如果您在 PHP 的配置中没有明确设置时区,将会显示警告。
-
如果您的模块尚未安装,请通过访问扩展页面来安装您的模块。如果您已经安装了您的模块,请转到性能页面并重新构建 Drupal 的缓存。
-
从管理菜单中的结构进入黑色布局页面。在页脚第四区域中,点击放置块。
-
审查块列表,并将自定义块添加到您的区域中,例如页脚区域。找到版权块,并点击放置块:

-
取消选择显示标题复选框,以便只渲染我们的块内容。点击保存块并接受所有其他默认设置。
-
查看您的 Drupal 网站,并验证版权声明将始终保持年份动态:

它是如何工作的...
插件系统通过插件定义和插件管理器来实现。\Drupal\Core\Block\BlockManager 类定义了需要位于 Plugin/Block 命名空间中的块插件。它还定义了需要实现的基接口,以及用于解析类的文档块的 Annotation 类。
当 Drupal 的缓存重新构建时,所有可用的命名空间都会被扫描以检查在给定的插件命名空间中是否存在类。通过注释处理定义,并将信息缓存起来。
然后从管理器检索块,对其进行操作,并调用其方法。在查看 块布局 页面以管理块时,会调用 \Drupal\Core\Block\BlockBase 类的 label 方法以显示可读名称。当一个块在渲染的页面上显示时,会调用 build 方法并将其传递给主题层以输出。
更多...
在创建块插件时,可以使用更多深入的项目。我们将在以下部分中介绍这些内容。
修改块
块可以通过三种不同的方式修改:修改插件定义、修改构建数组或修改视图数组。
一个模块可以在其 .module 文件中实现 hook_block_alter 并修改所有发现的块的注释定义。这将允许模块将默认的 user_login_block 从用户登录更改为 Login:
/**
* Implements hook_block_alter().
*/
function mymodule_block_alter(&$definitions) {
$definitions['user_login_block']['admin_label'] = t('Login');
}
一个模块可以实现 hook_block_build_alter 并修改块的构建信息。钩子通过构建数组和当前块的 \Drupal\Core\Block\BlockPluginInterface 实例传递。模块开发者可以使用此功能添加缓存上下文或更改元数据的缓存能力:
/**
* Implements hook_block_build_alter().
*/
function hook_block_build_alter(array &$build, \Drupal\Core\Block\BlockPluginInterface $block) {
// Add the 'url' cache the block per URL.
if ($block->getBaseId() == 'myblock') {
$build['#contexts'][] = 'url';
}
}
您可以通过更改食谱的块以输出时间戳来测试缓存元数据的修改。启用缓存后,您将看到值在相同的 URL 上持续存在,但每个页面的值将不同。
最后,一个模块可以实现 hook_block_view_alter 以修改要渲染的输出。模块可以添加要渲染的内容或删除内容。这可以用来删除 contextual_links 项,这允许在网站的首页上进行内联编辑:
/**
* Implements hook_block_view_alter().
*/
function hook_block_view_alter(array &$build, \Drupal\Core\Block\BlockPluginInterface $block) {
// Remove the contextual links on all blocks that provide them.
if (isset($build['#contextual_links'])) {
unset($build['#contextual_links']);
}
}
块设置表单
块可以提供一个 setting 表单。本菜谱为版权文本提供了文本 My Company。相反,这可以通过块设置表单中的文本字段来定义。
让我们重新审视包含我们块类的 Copyright.php 文件。我们将覆盖基类提供的方法。以下方法将被添加到本食谱中编写的类中。
一个块可以覆盖默认的 defaultConfiguration 方法,该方法返回一个设置键及其默认值的数组。然后 blockForm 方法可以覆盖 \Drupal\Core\Block\BlockBase 的空数组实现,以返回一个表单 API 数组来表示设置表单:
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'company_name' => '',
];
}
/**
* {@inheritdoc}
*/
public function blockForm($form, \Drupal\Core\Form\FormStateInterface $form_state) {
$form['company_name'] = [
'#type' => 'textfield',
'#title' => t('Company name'),
'#default_value' => $this->configuration['company_name'],
];
return $form;
}
然后,必须实现 blockSubmit 方法,该方法更新块的配置:
/**
* {@inheritdoc}
*/
public function blockSubmit($form, \Drupal\Core\Form\FormStateInterface $form_state) {
$this->configuration['company_name'] = $form_state->getValue('company_name');
}
最后,可以将 build 方法更新为使用新的配置项:
/**
* {@inheritdoc}
*/
public function build() {
$date = new \DateTime();
return [
'#markup' => t('Copyright @year© @company', [
'@year' => $date->format('Y'),
'@company' => $this->configuration['company_name'],
]),
];
}
您现在可以返回到 块布局 表单,并在版权块上单击配置。新的设置将在块实例的配置表单中可用。
定义块的访问权限
默认情况下,块会为所有用户渲染。默认访问方法可以被覆盖。这允许块只对认证用户或基于特定权限的用户显示:
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
$route_name = $this->routeMatch->getRouteName();
if ($account->isAnonymous() && !in_array($route_name,
array('user.login', 'user.logout'))) {
return AccessResult::allowed()
->addCacheContexts(['route.name',
'user.roles:anonymous']);
}
return AccessResult::forbidden();
}
上述代码来自 user_login_block。它允许在用户注销且不在登录或注销页面时访问块。访问基于当前路由名称和用户当前角色为匿名者进行缓存。如果没有传递这些,则返回的访问被禁止,并且块不会被构建。
其他模块可以实现 hook_block_access 以覆盖块的访问权限:
/**
* Implements hook_block_access().
*/
function mymodule_block_access(\Drupal\block\Entity\Block $block, $operation, \Drupal\Core\Session\AccountInterface $account) {
// Example code that would prevent displaying the Copyright' block in
// a region different than the footer.
if ($operation == 'view' && $block->getPluginId() == 'copyright') {
return \Drupal\Core\Access\AccessResult::forbiddenIf($block->getRegion() != 'footer');
}
// No opinion.
return \Drupal\Core\Access\AccessResult::neutral();
}
实现上述钩子的模块将阻止我们的版权块在没有放置在页脚区域时访问。
参见
-
参考本章的 创建自定义插件类型 菜谱
-
请参考基于注释的插件文档,链接为
www.drupal.org/docs/8/api/plugin-api/annotations-based-plugins -
关于
block.api.php的信息可在api.drupal.org/api/drupal/core%21modules%21block%21block.api.php/8找到
创建自定义字段类型
字段类型是通过插件系统定义的。每种字段类型都有自己的类和定义。可以通过自定义类来定义新的字段类型,该类将提供模式和属性信息。
在本例中,我们将创建一个简单的字段类型,称为真实姓名,用于存储姓氏和名字。
字段类型定义了通过 Field API 存储和处理数据的方式。字段小部件提供了在用户界面中编辑字段类型的方法。字段格式化器提供了向用户显示字段数据的方法。两者都是插件,将在后续食谱中介绍。
准备工作
创建一个新模块,类似于本食谱中展示的模块,并定义一个info.yml文件,以便 Drupal 能够发现它。在整个食谱中,我们将把这个模块称为mymodule。请使用你模块的适当名称。
如何操作...
-
我们需要在模块的基本位置创建
src/Plugin/Field/FieldType目录。Field模块在Plugin\Field\FieldType命名空间中查找字段类型。 -
我们将在新创建的目录中创建一个
RealName.php文件,以便我们可以定义RealName类。这将为我们提供用于姓氏和名字的realname字段:

RealName类将扩展\Drupal\Core\Field\FieldItemBase类:
<?php
namespace Drupal\mymodule\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
class RealName extends FieldItemBase {
}
\Drupal\Core\Field\FieldItemBase满足继承接口定义的方法,除了schema和propertyDefinitions。
- 字段类型是注释插件。注释插件使用文档块来提供插件详情。我们将提供字段类型的标识符、标签、描述、类别和默认小部件和格式化器:
<?php
namespace Drupal\mymodule\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'realname' field type.
*
* @FieldType(
* id = "realname",
* label = @Translation("Real name"),
* description = @Translation("This field stores a first and last name."),
* category = @Translation("General"),
* default_widget = "string_textfield",
* default_formatter = "string"
* )
*/
class RealName extends FieldItemBase {
}
@FieldType告诉 Drupal 这是一个FieldType插件。以下属性被定义:
-
Id:这是插件的机器名 -
Label:这是字段的可读性名称 -
description:这是字段的可读性描述 -
category:这是字段在用户界面中显示的类别 -
default_widget:这是用于编辑的默认表单小部件 -
default_formatter:这是可以用来显示字段的默认格式化器
-
RealName类需要实现定义在\Drupal\Core\Field\FieldItemInterface。这返回一个数据库 API 模式信息的数组。请向你的类中添加以下方法:
/**
* {@inheritdoc}
*/
public static function schema(\Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'first_name' => [
'description' => 'First name.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
],
'last_name' => [
'description' => 'Last name.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
],
],
'indexes' => [
'first_name' => ['first_name'],
'last_name' => ['last_name'],
],
];
}
schema方法定义了字段数据表中的列。我们将定义一个列来存储first_name和last_name值。
- 我们还需要实现
propertySchema方法以满足\Drupal\Core\TypedData\ComplexDataDefinitionInterface。此方法返回schema方法中定义的值的类型化定义。将以下方法添加到您的类中:
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(\Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition) {
$properties['first_name'] = \Drupal\Core\TypedData\DataDefinition::create('string')
->setLabel(t('First name'));
$properties['last_name'] = \Drupal\Core\TypedData\DataDefinition::create('string')
->setLabel(t('Last name'));
return $properties;
}
此方法返回一个数组,其键与schema中提供的列名相同。它返回一个类型化数据定义来处理字段类型的值。
-
如果尚未安装,请通过访问扩展页面来安装您的模块。如果您已经安装了您的模块,请转到性能页面并重新构建 Drupal 的缓存。
-
字段现在将出现在字段类型管理屏幕上。要使用它,请转到结构,然后转到评论类型。现在您可以转到管理字段并点击添加字段来为您的评论添加真实姓名条目:

它是如何工作的...
Drupal 核心定义了一个 plugin.manager.field.field_type 服务。默认情况下,这是通过 \Drupal\Core\Field\FieldTypePluginManager 类来处理的。此插件管理器定义了应位于 Plugin/Field/FieldType 命名空间中的字段类型插件,并且该命名空间中的所有类都将被加载并假定是字段类型插件。
管理器的定义还设置了 \Drupal\Core\Field\FieldItemInterface 作为所有字段类型插件将实现的预期接口。这就是为什么大多数字段类型都扩展 \Drupal\Core\Field\FieldItemBase 以满足这些方法要求。
由于字段类型是注解插件,管理器提供了 \Drupal\Core\Field\Annotation\FieldType 作为满足注解定义的类。
当用户界面定义可用的字段时,将调用 plugin.manager.field.field_type 服务以检索可用字段类型的列表。
还有更多...
可以修改现有的字段类型以修改它们的定义,并且自定义字段类型可以实现一个方法来定义值是否为空。我们将在下一节中介绍这些内容。
修改字段类型
\Drupal\Core\Field\FieldTypePluginManager 类将 alter 方法定义为 field_info。实现 .module 文件中的 hook_field_info_alter 的模块有权修改由管理器发现的字段类型定义:
/**
* Implements hook_field_info_alter().
*/
function mymodule_field_info_alter(&$info) {
$info['email']['label'] = t('E-mail address');
}
前面的 alter 方法将在用户界面中选择字段时将电子邮件字段的易读标签更改为 电子邮件地址。
定义字段是否为空
\Drupal\Core\TypedDate\ComplexDataInterface 接口提供了一个 isEmpty 方法。此方法用于检查字段值是否为空,例如,在验证所需字段是否有数据时。\Drupal\Core\TypedData\Plugin\DataType\Map 类实现了此方法。默认情况下,此方法确保值不为空。
字段类型可以提供它们自己的实现以提供更健壮的验证。例如,字段可以验证可以输入第一个名称但不能输入最后一个名称,或者字段可以要求输入第一个和最后一个名称。
参见
- 参考本章的 使用插件创建块 配方
创建自定义字段小部件
字段小部件提供了编辑字段的表单界面。这些与表单 API 集成,以定义字段如何编辑以及数据在保存之前如何格式化。字段小部件通过表单显示界面进行选择和定制。
在本配方中,我们将创建一个用于本章中 创建自定义字段类型 配方中创建的字段的表单小部件。字段小部件将为输入第一个和最后一个名称项提供两个文本字段。
准备工作
创建一个新的模块,例如来自 创建自定义字段类型 的配方。在整个配方中,我们将把该模块称为 mymodule。使用您模块的适当名称。
如何操作...
-
我们需要在模块的基本位置创建
src/Plugin/Field/FieldWidget目录。Field模块在Plugin\Field\FieldWidget命名空间中查找字段小部件。 -
在新创建的目录中创建一个
RealNameDefaultWidget.php文件,以便我们可以定义RealNameDefaultWidget类。这将提供一个自定义表单元素来编辑我们字段的第一个和最后一个名称值:

RealNameDefaultWidget类将扩展\Drupal\Core\Field\WidgetBase类:
<?php
namespace Drupal\mymodule\Plugin\Field\FieldWidget;
use Drupal\Core\Field\WidgetBase;
class RealNameDefaultWidget extends WidgetBase {
}
- 我们将在插件的注解中提供字段小部件的标识符、标签和支持的字段类型:
<?php
namespace Drupal\mymodule\Plugin\Field\FieldWidget;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'realname_default' widget.
*
* @FieldWidget(
* id = "realname_default",
* label = @Translation("Real name"),
* field_types = {
* "realname"
* }
* )
*/
class RealNameDefaultWidget extends WidgetBase {
}
@FieldWidget 告诉 Drupal 这是一个字段小部件插件。它定义了 id 来表示机器名,可读名称为 label,以及小部件交互的字段类型。
- 我们需要实现
formElement方法以满足在扩展\Drupal\Core\Field\WidgetBase后剩余的interface方法。将以下方法添加到您的类中:
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['first_name'] = [
'#type' => 'textfield',
'#title' => t('First name'),
'#default_value' => '',
'#size' => 25,
'#required' => $element['#required'],
];
$element['last_name'] = [
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => '',
'#size' => 25,
'#required' => $element['#required'],
];
return $element;
}
formElement 方法返回一个表示要设置的表单 API 数组的数组,并编辑字段数据。
- 接下来,我们需要修改我们的原始
RealName字段类型插件类,以使用我们创建的默认小部件。修改src/Plugin/FieldType/RealName.php文件,并将default_widget注解属性更新为realname_default:
/**
* Plugin implementation of the 'realname' field type.
*
* @FieldType(
* id = "realname",
* label = @Translation("Real name"),
* description = @Translation("This field stores a first and last name."),
* category = @Translation("General"),
* default_widget = "realname_default",
* default_formatter = "string"
* )
*/
class RealName extends FieldItemBase {
-
重建 Drupal 的缓存,以便插件系统可以发现新的字段小部件。
-
添加一个
Real name字段并使用新的Real name小部件。例如,将其添加到评论类型中:

工作原理...
Drupal 核心定义了一个 plugin.manager.field.widget 服务。默认情况下,这是通过 \Drupal\Core\Field\FieldWidgetPluginManager 类处理的。这个插件管理器定义了应在 Plugin/Field/FieldWidget 命名空间中的字段小部件插件,并且这个命名空间中的所有类都将被加载并被假定是字段小部件插件。
管理器的定义也将 \Drupal\Core\Field\FieldWidgetInterface 设置为所有字段小部件插件应实现的预期接口。这就是为什么大多数字段类型都扩展 \Drupal\Core\Field\WidgetBase 来满足这些方法要求。
由于字段小部件是注解插件,管理器提供 \Drupal\Core\Field\Annotation\FieldWidget 作为满足注解定义的类。
实体表单显示系统使用 plugin.manager.field.widget 服务来加载字段定义并将从 formElement 方法返回的字段元素添加到实体表单中。
更多...
字段小部件有额外的方法来提供更多信息;它们将在下一节中介绍。
字段小部件设置和摘要
\Drupal\Core\Field\WidgetInterface 接口定义了三个可以被重写的方法,用于提供设置表单和当前设置的摘要:
-
defaultSettings:这个方法返回一个设置键和默认值的数组 -
settingsForm:这个方法返回一个用于设置表单的 Form API 数组 -
settingsSummary:这个方法允许返回并显示在字段管理显示表单上的字符串数组
可以使用小部件设置来更改用户看到的表单。可以创建一个设置,允许字段元素仅通过一个文本字段输入姓名的首字母或最后一个字母。
参见
- 本章的 创建自定义插件类型 食谱
创建自定义字段格式化器
字段格式化器定义了字段类型将被呈现的方式。这些格式化器返回由主题层处理的渲染数组信息。字段格式化器在显示模式接口上进行配置。
在本食谱中,我们将创建一个格式化器,用于本章中 创建自定义字段类型 食谱中创建的字段。字段格式化器将显示一些设置下的姓名的首字母和最后一个字母。
准备工作
创建一个新的模块,就像第一个食谱中已有的那样。在本食谱中,我们将把模块称为 mymodule。使用你模块的适当名称。
如何操作...
-
我们需要在模块的基础位置创建
src/Plugin/Field/FieldFormatter目录。Field模块在Plugin\Field\FieldFormatter命名空间中查找字段格式化器。 -
在新创建的目录中创建一个
RealNameFormatter.php文件,以便我们可以定义RealNameFormatter类。这将提供一个自定义表单元素来显示字段的值:

RealNameFormatter类将扩展\Drupal\Core\Field\FormatterBase类:
<?php
namespace Drupal\mymodule\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
class RealNameFormatter extends FormatterBase {
}
- 我们将提供字段小部件的标识符、标签和支持的字段类型:
<?php
namespace Drupal\mymodule\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'realname_one_line' formatter.
*
* @FieldFormatter(
* id = "realname_one_line",
* label = @Translation("Real name (one line)"),
* field_types = {
* "realname"
* }
* )
*/
class RealNameFormatter extends FormatterBase {
}
- 我们需要实现
viewElements方法以满足\Drupal\Core\Field\FormatterInferface接口。这用于渲染字段数据。将以下方法添加到您的类中:
/**
{@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$element = [];
foreach ($items as $delta => $item) {
$element[$delta] = [
'#markup' => $this->t('@first @last', [
'@first' => $item->first_name,
'@last' => $item->last_name,
]),
];
}
return $element;
}
- 接下来,我们需要修改我们原始的
RealName字段类型的plugin类,以使用我们创建的默认格式化器。打开src/Plugin/FieldType/RealName.php文件,并将default_formatter注解属性更新为realname_one_line:
/**
* Plugin implementation of the 'realname' field type.
*
* @FieldType(
* id = "realname",
* label = @Translation("Real name"),
* description = @Translation("This field stores a first and last name."),
* category = @Translation("General"),
* default_widget = " string_textfield ",
* default_formatter = "realname_one_line"
* )
*/
-
重建 Drupal 的缓存,以便插件系统可以发现新的字段小部件。
-
更新一个具有
Real name字段的实体视图模式,以使用真实姓名(单行)格式化器:

它是如何工作的...
Drupal 核心定义了一个 plugin.manager.field.formatter 服务。默认情况下,这是通过 \Drupal\Core\Field\FormatterPluginManager 类处理的。此插件管理器定义了应在 Plugin/Field/FieldFormatter 命名空间中的字段格式化器插件,并且该命名空间中的所有类都将被加载并假定是字段格式化器插件。
管理器的定义也将 \Drupal\Core\Field\FormatterInterface 设置为所有字段格式化器插件将实现的预期接口。这就是为什么大多数字段格式化器都扩展 \Drupal\Core\Field\FormatterBase 来满足这些方法要求。
由于字段格式化器是注解插件,管理器提供 \Drupal\Core\Field\Annotation\FieldFormatter 作为满足注解定义的类。
实体视图显示系统使用 plugin.manager.field.formatter 服务来加载字段定义并将从 viewElements 方法返回的字段渲染数组添加到实体视图渲染数组中。
还有更多...
字段格式化器有额外的方法来提供更多信息;它们将在下一节中介绍。
格式化器设置和摘要
\Drupal\Core\Field\FormatterInterface 接口定义了三个可以重写的方法,以提供设置表单和当前设置的摘要:
-
defaultSettings: 这返回一个设置键和默认值的数组 -
settingsForm: 这返回一个用于设置表单的表单 API 数组 -
settingsSummary: 这允许返回并显示在字段管理显示表单上的字符串数组
设置可以用来改变格式化器显示信息的方式。例如,可以实现这些方法来提供设置以隐藏或显示姓名的首字母或最后一个字母。
参见
- 参考本章的 创建自定义插件类型 菜谱。
创建自定义插件类型
插件系统提供了一种在 Drupal 中创建专用对象的方法,这些对象不需要实体系统的数据存储功能。
本食谱基于作者启动的将GeoIP API模块移植到 Drupal 8 的项目。GeoIP API模块提供了一种从网站访问者的 IP 地址获取国家的方法。
在本食谱中,我们将创建一个名为GeoLocator的新插件类型,该类型将返回给定 IP 地址的国家代码。我们将创建一个插件管理器、默认插件接口、插件注解定义,并提供一个默认插件,通过网站的 CDN 查找国家。
准备工作
在本食谱中,我们将使用geoip命名空间和模块名称。
如何操作...
-
所有插件都需要有一个作为插件管理器的服务。在你的模块的
src目录中创建一个新文件,命名为GeoLocatorManager.php。这将保存GeoLocatorManager类。 -
通过扩展
\Drupal\Core\Plugin\DefaultPluginManager类创建GeoLocatorManager类:
<?php
namespace Drupal\geoip;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class GeoLocatorManager extends DefaultPluginManager {
}
- 当创建一个新的插件类型时,建议插件管理器为新插件提供一组默认值,以防定义中缺少项目:
<?php
namespace Drupal\geoip;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class GeoLocatorManager extends DefaultPluginManager {
/**
* Default values for each plugin.
*
* @var array
*/
protected $defaults = [
'label' => '',
'description' => '',
'weight' => 0,
];
}
- 接下来,我们需要覆盖
\Drupal\Core\Plugin\DefaultPluginManager类的构造函数以定义模块处理程序和缓存后端:
<?php
namespace Drupal\geoip;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class GeoLocatorManager extends DefaultPluginManager {
/**
* Default values for each plugin.
*
* @var array
* /
protected $defaults = [
'label' => '',
'description' => '',
'weight' => 0,
];
/**
* Constructs a new GeoLocatorManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct(
'Plugin/GeoLocator',
$namespaces,
$module_handler,
'Drupal\geoip\Plugin\GeoLocator\GeoLocatorInterface',
'Drupal\geoip\Annotation\GeoLocator'
);
$this->setCacheBackend($cache_backend, 'geolocator_plugins');
}
}
我们覆盖构造函数,以便可以指定特定的缓存键。这允许插件定义被正确缓存和清除;否则,我们的插件管理器将不断从磁盘读取以查找插件。
- 下一步将在我们模块的根目录下创建一个
geoip.services.yml文件。这将描述我们的插件管理器给 Drupal,允许插件发现:
services:
plugin.manager.geolocator:
class: Drupal\geoip\GeoLocatorManager
parent: default_plugin_manager
Drupal 利用服务和依赖注入。通过将我们的类定义为服务,我们告诉应用程序容器如何初始化我们的类。我们可以使用parent定义来告诉容器使用与default_plugin_manager定义相同的参数。
- 所有基于注解的插件都必须提供一个类,该类作为注解定义。在
src/Annotation目录下创建GeoLocator.php文件以提供GeoLocator注解类:
<?php
namespace Drupal\geoip\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a GeoLocator annotation object.
*
* @Annotation
*/
class GeoLocator extends Plugin {
/**
* The human-readable name.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* A description of the plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description;
}
每个属性都是可以在插件注解中定义的项目。对于我们的插件,注解定义将始于@GeoLocator。
- 接下来,我们将定义在插件管理器中定义的插件接口。插件管理器将验证实现此接口的
GeoLocator插件。在我们的模块的src/Plugin/GeoLocator目录下创建一个GeoLocatorInterface.php文件以保存接口:
<?php
namespace Drupal\geoip\Plugin\GeoLocator;
/**
* Interface GeoLocatorInterface.
*/
interface GeoLocatorInterface {
/**
* Get the plugin's label.
*
* @return string
* The geolocator label
*/
public function label();
/**
* Get the plugin's description.
*
* @return string
* The geolocator description
*/
public function description();
/**
* Performs geolocation on an address.
*
* @param string $ip_address
* The IP address to geolocate.
*
* @return string|NULL
* The geolocated country code, or NULL if not found.
*/
public function geolocate($ip_address);
}
我们提供了一个接口,以确保在处理GeoLocator插件时,我们有这些预期的方法,并且无论每个方法背后的逻辑如何,都有输出。
- 接下来,我们将创建一个默认插件,如果可用,则从 CDN 头中返回国家代码。在
src/Plugin/GeoLocator目录下创建一个Cdn.php文件以创建我们的Cdn插件类:
<?php
namespace Drupal\geoip\Plugin\GeoLocator;
use Drupal\Core\Plugin\PluginBase;
/**
* CDN geolocation provider.
*
* @GeoLocator(
* id = "cdn",
* label = "CDN",
* description = "Checks for geolocation headers sent by CDN services",
* weight = -10
* )
*/
class Cdn extends PluginBase implements GeoLocatorInterface {
/**
* {@inheritdoc}
*/
public function label() {
return $this->pluginDefinition['label'];
}
/**
* {@inheritdoc}
*/
public function description() {
return $this->pluginDefinition['description'];
}
/**
* {@inheritdoc}
*/
public function geolocate($ip_address) {
// Check if CloudFlare headers present.
if (!empty($_SERVER['HTTP_CF_IPCOUNTRY'])) {
$country_code = $_SERVER['HTTP_CF_IPCOUNTRY'];
}
// Check if CloudFront headers present.
elseif (!empty($_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'])) {
$country_code = $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'];
}
else {
$country_code = NULL;
}
return $country_code;
}
}
GeoLocator插件类型现在已设置,并带有默认的基于 CDN 的插件。
它是如何工作的...
Drupal 8 实现了一个服务容器,这是一个从 Symfony 框架中采用的概念。为了实现一个插件,需要有一个可以发现和处理插件定义的管理器。这个管理器在模块的 services.yml 文件中定义为一个服务,并带有其所需的构造函数参数。这允许服务容器在需要时启动类。
在我们的示例中,GeoLocatorManager 插件管理器通过注解插件发现来发现 GeoLocator 插件定义。在第一次发现之后,所有已知的插件定义都将在 geolocator_plugins 缓存键下缓存。
插件管理器还提供了一个方法来返回这些定义或基于可用定义创建一个对象实例。对于 CDN 插件,这将是一个完整的实例化的 Cdn 类对象。
让我们考虑以下示例:
// Load the manager service.
$geolocator_manager = \Drupal::service('plugin.manager.geolocator');
// Create a class instance through the manager.
$cdn_instance = $unit_manager->createInstance('cdn');
// Get country code.
$country_code = $cdn_instance->geolocate('127.0.0.1');
还有更多...
创建自定义插件类型有许多附加项;我们将在以下章节中讨论其中的一些。
指定一个修改钩子
插件管理器有定义一个修改钩子的能力。以下代码行将被添加到 GeoLocatorManager 类的构造函数中,以提供 hook_geolocator_plugins_alter。这将被传递给模块处理服务以进行调用:
/**
* Constructs a new GeoLocatorManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct(
'Plugin/GeoLocator',
$namespaces,
$module_handler,
'Drupal\geoip\Plugin\GeoLocator\GeoLocatorInterface',
'Drupal\geoip\Annotation\GeoLocator'
);
$this->alterInfo('geolocator_info');
$this->setCacheBackend($cache_backend, 'geolocator_plugins');
}
实现 .module 文件中的 hook_geolocator_plugins_alter 的模块具有修改所有发现插件定义的能力。它们还具有删除定义的插件条目或修改为注解定义提供的任何信息的功能。
使用缓存后端
插件可以使用缓存后端来提高性能。这可以通过在管理器的构造函数中使用 setCacheBackend 方法来指定缓存后端来实现。以下代码行将允许 Unit 插件被缓存,并且只在缓存重建时被发现。
$cache_backend 变量传递给构造函数。第二个参数提供缓存键。缓存键将添加当前语言代码作为后缀。
有一个可选的第三个参数,它接受一个字符串数组,表示将导致插件定义被清除的缓存标签。这是一个高级功能,插件定义通常应通过管理器的 clearCachedDefinitions 方法来清除。缓存标签允许在相关缓存被清除时清除插件定义。
通过管理器访问插件
插件通过管理服务加载,应始终通过服务容器来访问。以下代码行将在您的模块的钩子或类中使用,以访问插件管理器:
$geolocator_manager = \Drupal::service('plugin.manager.geolocator');
插件管理器有各种方法来检索插件定义,如下所示:
-
getDefinitions:此方法将返回一个插件定义数组。它首先尝试检索缓存的定义(如果有),然后在返回之前设置已发现定义的缓存。 -
getDefinition: 这接受一个预期的插件 ID 并返回其定义。 -
createInstance: 这接受一个预期的插件 ID 并返回该插件的初始化类。 -
getInstance: 这接受一个充当插件定义的数组,并从定义中返回一个初始化类。
参见
- 请参阅
www.drupal.org/node/2133171中的服务和依赖注入
第八章:多语言和国际化
在本章中,我们将介绍以下菜谱,以确保您的网站是多语言的并且是国际化的:
-
翻译管理界面
-
翻译配置
-
翻译内容
-
创建多语言视图
简介
本章将介绍 Drupal 8 的多语言和国际化功能,这些功能自 Drupal 7 以来得到了极大的增强。之前的 Drupal 版本需要许多额外的模块来提供国际化工作,但现在大多数工作都由 Drupal 核心提供。
Drupal 核心提供了以下多语言模块:
-
语言:这为您提供了检测和支持多种语言的能力
-
界面翻译:这会将已安装的语言翻译成通过用户界面呈现的字符串
-
配置翻译:这允许您翻译配置实体,例如日期格式和视图
-
内容翻译:这带来了提供不同语言内容并根据用户当前语言显示内容的能力
每个模块在为您的 Drupal 网站创建多语言体验方面都发挥着特定的作用。在幕后,Drupal 支持所有实体和缓存上下文的语言代码。这些模块公开接口以实现和提供国际化体验。
翻译管理界面
界面翻译模块提供了一种翻译在 Drupal 用户界面中找到的字符串的方法。利用语言模块,界面翻译会自动从 Drupal 翻译服务器下载。默认情况下,界面语言通过语言代码作为路径前缀加载。使用默认的 语言 配置,路径将以默认语言为前缀。
界面翻译基于代码中提供的字符串,这些字符串通过内部翻译函数传递。
在本菜谱中,我们将启用西班牙语,导入语言文件,并审查翻译的界面字符串,以提供缺失或自定义翻译。
准备工作
Drupal 8 提供了翻译文件的自动化安装过程。为此,您的 Web 服务器必须能够与 localize.drupal.org/ 进行通信。如果您的 Web 服务器无法从翻译服务器自动下载文件,您可以参考手动安装说明,这些说明将在本菜谱的 还有更多... 部分中介绍。
如何操作...
-
前往扩展并安装界面翻译模块。如果尚未安装,它将提示您启用语言、文件和字段模块。
-
模块安装完成后,点击配置。在区域和语言部分下转到语言页面。
-
在语言概述表中点击添加语言:

-
添加语言页面提供了一个可供选择的所有可用语言的列表,界面可以翻译成这些语言。选择西班牙语,然后点击添加语言。
-
将运行一个批处理过程,安装翻译语言文件并将它们导入。
-
接口翻译列指定了具有匹配翻译的活跃可翻译界面字符串的百分比。点击链接可以查看用户界面翻译表单:

-
过滤可翻译字符串表单允许你搜索已翻译字符串或未翻译字符串。从搜索列表中选择“仅未翻译字符串”,然后点击过滤。
-
使用屏幕右侧的文本框,可以添加自定义翻译到“仅未翻译字符串”。为该条目输入翻译:

-
点击保存翻译以保存修改。
-
前往
/es/node/add,你会注意到基本页面内容类型的描述现在将与你的翻译相匹配。
它是如何工作的...
接口翻译模块提供了 \Drupal\locale\LocaleTranslation,它实现了 \Drupal\Core\StringTranslation\Translator\TranslatorInterface。这个类在 string_translation 服务下注册为可用的查找方法。
当调用 t 函数或 \Drupal\Core\StringTranslation\StringTranslationTrait::t 方法时,string_translation 服务会被调用以提供翻译后的字符串。string_translation 服务将遍历所有可用的翻译器,并在可能的情况下返回一个翻译后的字符串。
开发者需要注意,这是确保模块字符串通过翻译函数传递的关键原因。这允许你识别需要翻译的字符串。
接口翻译中提供的翻译器将尝试将提供的字符串与当前语言的已知翻译进行匹配。如果已保存翻译,则将返回该翻译。
更多内容...
在接下来的章节中,我们将探讨安装其他语言、检查翻译状态以及做更多的事情的方法。
手动安装语言文件
可以通过从 Drupal.org 翻译服务器下载并通过语言界面上传来手动安装翻译文件。你也可以使用导入界面上传自定义的 gettext 可移植对象(.po)文件。
Drupal 核心和大多数贡献项目在 Drupal 翻译网站上都有 .po 文件,localize.drupal.org。在网站上,点击下载以下载所有语言的 Drupal 核心文件的 .po 文件。此外,点击一个语言将提供跨项目的特定语言的更多翻译,如下所示:

你可以通过访问用户界面翻译表单并选择导入选项卡来导入.po文件。你需要选择.po文件和适当的语言。你可以将上传的文件视为自定义创建的翻译。如果你提供的是由 Drupal.org 未提供的自定义翻译文件,这建议这样做。如果你正在手动更新 Drupal.org 的翻译,请确保勾选覆盖现有非定制翻译的复选框。最后一个选项允许你在.po文件提供的情况下替换定制翻译。如果你已经翻译了可能现在由官方翻译文件提供的缺失字符串,这可能很有用。
检查翻译状态
随着你添加新的模块,可用的翻译将不断增加。界面翻译模块提供了一个可以从报告页面访问的翻译状态报告。这将检查项目的默认翻译服务器,并检查是否存在.po文件或它是否已更改。在自定义模块的情况下,你可以提供一个自定义翻译服务器,这在提供自定义模块的翻译中有所介绍。
如果有更新可用,你会收到通知。然后你可以自动导入翻译文件更新,或者下载并手动导入它们。
导出翻译
在用户界面翻译表单中,有一个导出选项卡。此表单将提供一个Gettext 便携对象(.po)文件。你可以导出在你当前 Drupal 网站上发现的所有未翻译的源文本。这将提供一个基础.po文件,供翻译者工作。
此外,你可以下载特定语言。特定语言的下载可以包括未定制的翻译、定制的翻译和缺失的翻译。下载定制的翻译可以帮助你为 Drupal 社区的多语言和国际化的努力做出贡献。
界面翻译权限
界面翻译模块提供了一个名为翻译界面文本的单个权限。这个权限授予用户与模块所有功能交互的权限。它带有安全警告标志,因为它允许具有此权限的用户自定义所有展示给他们的输出文本。
然而,它确实允许你为翻译者提供一个角色,并限制他们仅能访问翻译界面。
使用界面翻译来自定义默认的英文字符串
界面翻译模块在其典型的多语言用途之外也非常有用。你可以用它来自定义那些无法通过典型钩子方法修改的界面字符串,或者如果你不是开发者的话。
首先,您需要从语言屏幕编辑英语语言。勾选启用英语界面翻译的复选框并点击保存语言。现在您将能够自定义现有的界面字符串。
这仅建议用于无法通过正常用户界面或提供的 API 机制进行定制的界面区域。
界面文本语言检测
Language模块提供检测和选择规则。默认情况下,该模块将根据 URL 检测当前语言,语言代码作为当前路径的前缀。例如,/es/node将显示西班牙语的节点列表页面,如下面的截图所示:

您可以同时启用多个检测选项,并使用排序来决定哪个优先。这可以允许您首先使用 URL 中的语言代码,但如果它们缺失,则回退到用户浏览器指定的语言。
一些检测方法有设置。例如,URL 检测方法可以基于默认路径前缀或子域。
为自定义模块提供翻译
模块可以在它们的目录中提供自定义翻译或指向远程文件。这些定义添加到模块的info.yml文件中。首先,您需要指定interface translation project键,如果它与项目的机器名称不同。
然后,您需要通过interface translation server pattern键指定一个服务器模式。这可以是一个指向 Drupal 根目录的相对路径,例如modules/custom/mymodule/translation.po,或者一个远程文件 URL,例如http://example.com/files/translations/mymodule/translation.po。
分布式(或其他模块)可以实现hook_locale_translation_projects_alter来代表模块提供此信息或更改默认值。
服务器模式接受以下不同的标记:
-
%core:表示课程的版本(例如,8.x) -
%project:表示项目的名称 -
%version:表示当前版本字符串 -
%language:表示语言代码
更多关于界面翻译键和变量的信息可以在界面翻译模块基本文件夹中的local.api.php文档文件中找到。
参考信息
-
参考 Drupal 翻译服务器
localize.drupal.org/translate/drupal8 -
您可以使用位于
www.drupal.org/node/302194的本地化服务器进行贡献 -
参考位于
api.drupal.org/api/drupal/core%21modules%21locale%21locale.api.php/8的locale.api.php文档 -
参考 PO 和 POT 文件
www.drupal.org/node/1814954
翻译配置
配置翻译模块提供了一个界面,用于通过接口翻译和语言依赖来翻译配置。此模块允许我们翻译配置实体。翻译配置实体的能力增加了国际化的一层。
接口翻译允许我们翻译在 Drupal 网站代码库中提供的字符串。配置翻译允许我们翻译我们创建的可导入和可导出的配置项,例如网站标题或日期格式。
在这个菜谱中,我们将翻译日期格式配置实体。我们将为丹麦语提供本地化的日期格式,以提供更国际化的体验。
准备工作
您的 Drupal 网站需要启用两种语言才能使用配置翻译。从语言界面安装丹麦语。
如何操作...
-
前往扩展并安装配置翻译模块。如果尚未安装,它将提示您启用接口翻译、语言、文件和字段模块。
-
在模块安装后,转到配置。然后,转到区域和语言部分下的配置翻译页面。
-
在配置实体选项表中点击日期格式选项的列表:

-
我们将翻译默认的长日期格式以表示丹麦格式。点击“翻译”以翻译默认长日期格式行。
-
点击“添加”以创建丹麦语翻译:

-
对于丹麦语,我们将提供以下 PHP 日期格式:
l j. F, Y - H.i。这将显示星期几、月份中的日期、月份、完整年份以及时间的 24 小时制表示。 -
点击“保存翻译”。
-
当用户使用丹麦语浏览您的 Drupal 网站时,日期格式现在将根据他们的体验进行本地化。
它是如何工作的...
配置翻译模块需要接口翻译;然而,它的工作方式并不相同。该模块修改了所有扩展\Drupal\Core\Config\Entity\ConfigEntityInterface接口的实体类型。它在config_translation_list键下添加了一个新的处理器。这用于构建可用配置实体及其捆绑包的列表。
该模块修改 Drupal 的配置架构,并更新默认配置元素定义以使用\Drupal\config_translation\Form下的指定类。这允许\Drupal\config_translation\Form\ConfigTranslationFormBase及其子类正确保存翻译后的配置数据,然后可以通过配置翻译屏幕进行修改。
当配置保存时,它被识别为集合的一部分。该集合被标识为language.LANGCODE,所有翻译后的配置实体都通过此标识符保存和加载。以下是如何在数据库中存储配置项的示例:

在使用es语言代码浏览站点时,将加载适当的block.block.bartik_account_menu配置实体。如果您使用的是默认站点或没有语言代码,将使用空集合的配置实体。
更多内容...
配置实体和翻译它们的能力是 Drupal 8 多语言功能的重要组成部分。我们将在下一个配方中详细探讨它们。
修改配置翻译信息定义
模块可以调用hook_config_translation_info_alter钩子来修改发现的配置映射器。例如,节点模块就是这样做的,以修改node_type配置实体:
/**
* Implements hook_config_translation_info_alter().
*/
function node_config_translation_info_alter(&$info) {
$info['node_type']['class'] = 'Drupal\node\ConfigTranslation\NodeTypeMapper';
}
这将更新node_type定义,使用\Drupal\node\ConfigTranslation\NodeTypeMapper自定义映射类。此类将节点类型的标题添加为可配置的翻译项。
翻译视图
视图是配置实体。当配置翻译模块启用时,可以翻译视图。这将允许您翻译显示标题、暴露表单标签和其他项目。有关更多信息,请参阅本章中的创建多语言视图配方。
相关内容
- 请参阅第八章的创建多语言视图配方,多语言和国际化。
翻译内容
内容翻译模块提供了一种翻译内容实体(如节点和块)的方法。每个内容实体都需要启用翻译,这样您可以细粒度地决定哪些属性和字段需要翻译。
内容翻译是对现有实体的重复,但会使用适当的语言代码进行标记。当访客使用语言代码时,Drupal 会尝试使用该语言代码加载内容实体。如果不存在翻译,Drupal 将渲染默认未翻译的实体。
准备工作
您的 Drupal 站点需要启用两种语言才能使用内容翻译。从语言界面安装西班牙语。
如何操作...
-
前往“扩展”并安装内容翻译模块。如果尚未安装,它将提示您安装语言模块。
-
在模块安装后,前往配置,然后在区域和语言部分下转到内容语言和翻译页面。
-
在“内容”旁边的复选框中勾选,以显示当前内容类型的设置。
-
启用基本页面的内容翻译,并保留提供的默认设置,这些设置使每个字段都启用翻译。点击保存配置:

-
创建一个新的基本页面节点。我们将使用网站的默认语言创建此节点。
-
在查看新节点时,点击翻译标签。从西班牙语语言行,点击添加以创建节点的翻译版本:

- 内容将预先填充为默认语言的内容。用翻译文本替换标题和正文:

- 点击保存并保持已发布(此翻译)以保存新的翻译。
工作原理
内容翻译模块通过利用语言代码标志来工作。所有内容实体和字段定义都有一个语言代码键。内容实体有一个语言代码列,指定内容实体是哪种语言。字段定义也有一个语言代码列,用于识别内容实体的翻译。内容实体可以提供处理器定义来处理翻译,否则内容翻译模块将提供自己的。
每个实体和字段记录都保存了适当的语言代码以使用。当加载实体时,会考虑当前的语言代码以确保加载正确的实体。
更多...
有额外的操作来翻译内容;我们将在下一节中介绍它们。
标记翻译为过时
内容翻译模块提供了一个机制来标记翻译实体可能已过时。标记其他翻译为过时的标志提供了一个记录需要更新翻译的实体的方式:

此标志不会更改任何数据,而是一种审核工具。这使得翻译者能够轻松识别已更改并需要更新的内容。内容实体的翻译标签将突出显示所有仍标记为过时的翻译。随着它们的更改,编辑者可以取消选中标志。
翻译内容链接
通常,Drupal 菜单包含指向节点的链接。菜单链接默认不翻译,必须在内容翻译中启用自定义菜单链接选项。您需要从菜单管理界面手动翻译节点链接。
从节点创建和编辑表单启用菜单链接与翻译不兼容。如果您从翻译编辑菜单设置,它将编辑未翻译的菜单链接。
为实体定义翻译处理器
内容翻译模块需要实体定义来提供关于翻译处理器的信息。如果缺少此信息,它将提供自己的默认值。实体 API在第十章,实体 API中有介绍,但我们将快速讨论内容翻译模块如何与实体 API 交互。
内容实体定义可以提供 translation 处理器。如果没有提供,它将默认为 \Drupal\content_translation\ContentTranslationHandler。一个节点提供此定义并使用它将内容翻译信息放入垂直标签中。
content_translation_metadata 键定义了如何与翻译元数据信息交互,例如标记其他实体为过时。content_translation_deletion 键提供了一个表单类来处理实体翻译删除。
目前,截至 8.0.1 版本,没有核心模块提供覆盖默认的 content_translation_metadata 或 content_translation_deletion 的实现。
相关内容
- 参考 第十章,实体 API。
创建多语言视图
视图作为配置实体,可以进行翻译。然而,多语言视图的力量并不仅仅在于配置翻译。视图允许您构建对当前语言代码做出反应的过滤器。这确保了翻译成用户语言的内容被显示。
在这个菜谱中,我们将创建一个多语言视图,显示最近的文章块。如果没有内容,我们将显示翻译后的 无结果 消息。
准备工作
您的 Drupal 网站需要启用两种语言才能使用内容翻译。从语言界面安装西班牙语。为文章启用内容翻译。您还需要一些翻译内容。
如何操作...
-
从结构转到视图,并点击添加视图。
-
提供一个视图名称,例如
Recent articles,并将内容类型更改为文章。标记您想要创建一个块,然后点击保存并编辑。 -
添加新的过滤器标准。搜索翻译语言,并为内容类别添加过滤器。点击添加并配置过滤器标准。将过滤器设置为检查页面选择的界面文本语言。这将仅显示已翻译的内容或基本语言是当前语言:

-
点击无结果旁边的添加,搜索文本区域。勾选复选框,然后点击添加并配置无结果行为。提供一些示例文本,例如
目前没有最近的文章。 -
保存视图。
-
点击翻译视图标签。点击西班牙语行的添加以翻译该语言的视图。
-
展开主显示设置,然后展开最近的文章显示选项字段集。修改显示标题选项以提供翻译的标题:

- 将无结果行为展开以修改屏幕右侧的文本,使用屏幕左侧的文本框作为原始文本的来源:

-
点击保存翻译。
-
通过转到结构然后转到块布局来在您的 Drupal 网站上放置该块。
-
通过
/es访问网站并注意翻译后的视图块:

它是如何工作的...
视图提供了基于此元素的翻译语言过滤器。视图插件系统提供了一个机制来收集和显示所有可用的语言。这些语言将作为令牌内部保存,并在查询执行时替换为实际的语言代码。如果某个语言代码不再可用,则选择页面和视图的内容语言将回退到当前语言。
当您编辑 Drupal 核心或贡献模块提供的视图时,您会遇到此选项。尽管这不是用户界面中的选项,但将定义为***LANGUAGE_language_content***的语言过滤器添加为默认做法,这将强制视图为多语言。
过滤器告诉视图根据实体的语言代码及其字段进行查询。
视图是配置实体。配置翻译模块允许您翻译视图。您可以从配置区域的配置翻译主屏幕或通过编辑单个视图来翻译视图。
大多数翻译项都将位于主显示设置选项卡下,除非在特定显示中进行了覆盖。每种显示类型也将有自己的特定设置。
还有更多...
可以对视图进行更多翻译;我们将在下一节中讨论。
翻译暴露的表单项和过滤器
每个视图都可以从暴露表单部分翻译暴露的表单。这不会翻译表单上的标签,而是翻译表单元素。您可以翻译提交按钮文本、重置按钮标签、排序标签以及升序或降序。
您可以从过滤器部分翻译暴露过滤器的标签。每个暴露的过滤器将显示为一个可折叠的字段集,允许您配置管理标签和前端标签。

默认情况下,需要通过全局界面翻译上下文导入可用的翻译。
翻译显示和行格式项
一些显示格式有可翻译项。这些可以在每个显示模式的相应部分进行翻译。例如,以下项可以使用其显示格式进行翻译:
-
表格格式允许您翻译表格摘要 -
RSS 源格式允许您翻译源描述 -
页面格式允许您翻译页面的标题 -
块格式允许您翻译块的标题
翻译页面显示菜单项
自定义菜单链接可以通过内容翻译模块进行翻译。使用页面显示的视图不会创建自定义菜单链接实体。因此,必须通过视图本身进行翻译。视图模块将所有具有页面显示的视图直接注册到路由系统中,就像在模块的routing.yml文件中定义一样:

例如,列出所有用户的人员视图可以被翻译成具有更新的标签名称和链接描述。
另请参阅
- 请参阅第三章,通过视图显示内容。
第九章:配置管理 - 在 Drupal 8 中部署
在本章中,我们将探讨配置管理系统和配置更改的部署。以下是在本章中涵盖的食谱列表:
-
导入和导出配置
-
同步站点配置
-
使用命令行工作流程过程
-
更新和安装新的模块配置
简介
Drupal 8 提供了一个新的、统一的系统来管理配置。在 Drupal 8 中,所有配置都保存在与定义的配置模式相匹配的配置实体中。这个系统提供了一个在 Drupal 站点环境之间部署配置和更新站点配置的标准方式。
一旦配置被创建或导入,它就会进入一个不可变状态。如果一个模块尝试安装已存在的配置,它将抛出异常并被阻止。在典型用户界面之外,配置只能通过配置管理系统进行修改。
配置管理系统可以通过配置管理模块提供的用户界面或通过命令行界面工具进行操作。这些工具允许你遵循使用生产站点和开发站点的开发范例,其中更改是在开发站点上进行的,然后推送到生产环境。
在本章中,对于食谱的示例,你不需要创建两个不同的 Drupal 站点,而是可以利用 Drupal 的多站点功能。有关此功能的更多信息,请参阅第一章的“安装 Drupal”食谱,“使用 Drupal 8 启动”。请注意,如果你使用多站点,你需要将你的开发站点的数据库克隆到作为生产站点的站点中,以复制一个真实的发展和生产站点工作流程。
导入和导出配置
Drupal 8 中的配置管理为在多个环境中与网站工作时的常见问题提供了一个解决方案。无论工作流程模式是什么,配置最终都需要从一个地方移动到另一个地方,例如从生产环境到本地环境。当将开发工作推送到生产环境时,你需要有一种方法来放置配置。
Drupal 8 的用户界面提供了一种通过 YAML 格式导入和导出配置实体的方式。在本食谱中,我们将创建一个内容类型,导出其配置,然后将其导入到另一个 Drupal 站点。配置 YAML 导出将被导入到生产站点以更新其配置。
准备工作
你需要一个作为开发站点的基 Drupal 站点。另一个 Drupal 站点,它是开发站点的克隆,必须可用,作为生产 Drupal 站点。
如何操作...
-
要开始,在开发站点上创建一个新的内容类型。将内容类型命名为 Staff Page,然后点击保存和管理字段以保存内容类型。我们不会添加任何额外的字段。
-
一旦内容类型已保存,前往扩展并安装配置管理模块(如果尚未安装):

- 从您的 Drupal 站点的配置页面,前往开发组下的配置同步。此部分允许您导入和导出配置:

- 在页面顶部点击导出标签页。默认页面将是一个完整存档导出,包含您整个 Drupal 站点的配置。点击单项子标签以导出单个配置实体:

-
从配置类型下拉菜单中选择内容类型。然后,从配置名称下拉菜单中选择您的内容类型。其配置将填充到配置文本框中:
![图片]()
-
从文本框中复制 YAML 内容,以便您可以将其导入到您的其他 Drupal 站点。
-
在您的生产 Drupal 站点上,如果尚未安装,请像开发站点一样安装配置管理模块。
-
前往配置同步页面,点击导入标签页。
-
点击单项,从配置类型中选择内容类型:

-
将导出的配置 YAML 粘贴到文本框中,然后点击导入:
![图片]()
-
在确认表单上点击确认,以最终将您的自定义内容类型的导入到生产 Drupal 站点。
-
前往结构页面,然后是内容类型页面,以验证您的内容类型是否已导入。
如何工作...
在最基本层面上,配置只是键和值的映射,这可以表示为一个 PHP 数组,并转换为 YAML 格式。
配置管理使用配置实体的模式定义。模式定义提供了一个配置命名空间和可用的键和数据类型。模式定义为每个选项提供了类型化的数据定义,允许验证单个值和配置。
导出过程读取配置数据并将其转换为 YAML 格式。配置管理器随后以 YAML 的形式接收配置并将其转换回 PHP 数组。然后,数据在数据库中更新。
在导入配置时,Drupal 会检查配置 YAML 的uuid键的值,如果存在,则与任何具有相同通用唯一标识符(UUID)的当前配置进行比较。UUID 是在软件中用于在不同环境中识别对象的模式。这允许 Drupal 根据 UUID 关联数据,因为数据库标识符可能在不同环境中有所不同。如果配置项具有匹配的机器名称,但 UUID 不匹配,则会抛出错误。
还有更多...
我们将在稍后的章节中更深入地讨论在 Drupal 站点内导入和导出配置。
配置依赖项
配置实体在导出时定义依赖项。依赖定义确保配置实体的架构和其他模块功能可用。
当你审查field.storage.node.body.yml的配置导出时,它将node和text定义为dependencies:
dependencies:
module:
- node
- text
如果node或text模块未启用,导入将失败并抛出错误。
将模块配置安装保存到 YAML 文件
第六章中的“在安装或更新时提供配置”配方,使用表单 API 创建表单,讨论了如何使用模块在模块安装时提供配置。而不是手动编写安装配置YAML文件,可以使用配置管理模块来导出配置并将其保存到模块的config/install目录中。
通过用户界面导出的任何项目都可以使用。唯一的要求是您需要删除uuid键,因为它表示站点的 UUID 值,并在尝试安装时使配置无效。
配置架构
Drupal 8 中的配置管理系统利用配置架构来描述可以存在的配置。这为什么很重要?它允许 Drupal 在存储的配置值上正确实现类型数据并对其进行验证,为处理配置和配置项提供了一种标准化的方式。
当一个模块使用配置系统来存储数据时,它需要为它希望存储的每个配置定义提供一个架构。架构定义用于验证其值的类型数据定义。
以下代码定义了navbar_awesome模块的配置架构,该模块包含两个不同的布尔配置值:
navbar_awesome.toolbar:
type: config_object
label: 'Navbar Awesome toolbar settings'
mapping:
cdn:
type: boolean
label: 'Use the FontAwesome CDN library'
roboto:
type: boolean
label: 'Include Roboto from Google Fonts CDN'
这定义了navbar_awesome.toolbar配置命名空间;它属于navbar_awesome模块,并具有toolbar配置。然后我们需要两个表示类型数据值的cdn和roboto子值。该架构的配置 YAML 文件将在命名空间之后命名为navbar_awesome.toolbar.yml,并包含以下代码:
cdn: true
roboto: true
反过来,当这些值以 PHP 数组表示时,它们将看起来像这样:
[
'navbar_awesome' => [
'cdn' => TRUE,
'roboto' => TRUE,
]
]
配置工厂类随后提供基于对象的包装器,围绕这些配置定义,并提供对其值的模式验证。例如,如果您尝试将cdn值保存为字符串,将抛出一个验证异常。
参见
-
请参阅第四章,扩展 Drupal
-
在 Drupal.org 社区手册中的配置架构/元数据,请参阅
www.drupal.org/node/1905070
同步站点配置
管理 Drupal 网站的一个关键组件是配置完整性。维护这种完整性的一个关键部分是确保您在开发中做出的配置更改被推送到生产环境。通过用户界面手动导出和导入配置更改可能很繁琐,并且无法跟踪已导出或导入的内容。同时,手动编写模块钩子来操作配置可能很耗时。幸运的是,配置管理解决方案为您提供了导出和导入整个站点配置的能力。
站点导出只能导入到其自身的另一个副本。每个站点都必须有相同的 UUID,这是在安装期间设置的。这允许您导出本地开发环境的配置,并将其带到预发布或生产环境,而无需直接修改内容或数据库。
在这个菜谱中,我们将导出开发站点完整配置实体定义。然后我们将导出的配置导入到生产站点。这将模拟一个典型的部署,其中在开发中创建的更改已准备好在生产中发布。
准备工作
您需要一个基 Drupal 站点作为开发站点。另一个 Drupal 站点,它是开发站点数据库的副本,必须可用,作为生产 Drupal 站点。
如果尚未安装,您需要安装配置管理模块。
如何操作...
-
前往开发站点。修改站点以模拟创建需要部署到我们的其他 Drupal 站点的更改。
-
例如,让我们修改站点的名称;转到配置页面和基本站点设置表单。
-
从配置页面,转到配置同步。
-
导航到导出标签页;我们将到达完整存档页面。点击导出按钮开始导出和下载过程:

-
保存
gzip存档;这包含所有站点配置的 YAML 存档。 -
导航到您的其他 Drupal 站点,然后转到其配置同步页面。
-
点击“导入”标签页,然后点击“完整存档”标签页。使用配置存档文件输入,点击“选择文件”以选择您刚刚下载的 tarball。点击“上传”以开始导入过程。
-
您将被带到“同步”标签页以审查需要导入的更改:

-
点击“导入全部”以更新当前站点的配置为存档中的项目。
-
批量操作将随导入过程开始:

它是如何工作的...
配置同步表单提供了一种与您的 Drupal 站点的config数据库表进行接口的方式。当您转到导出页面并创建 tarball 时,Drupal 实际上会转储config表的内容。每一行代表一个配置实体,并将成为其自己的YAML文件。YAML文件的内容代表其数据库值。
当您导入 tarball 时,Drupal 会提取其内容。文件被放置在可用的CONFIG_SYNC_DIRECTORY目录中。同步页面解析配置实体 YAML,并提供与当前站点配置的差异检查。每个配置项都可以进行审查,然后可以导入所有项。您不能选择选择性地导入单个项。
更多...
我们现在将讨论站点配置同步所需的各项内容。
通用唯一标识符
当 Drupal 站点安装时,UUID 被设置。此 UUID 添加到导出的配置实体中,并由uuid键表示。Drupal 使用此键来识别配置的来源。Drupal 不会同步在其 YAML 定义中没有匹配 UUID 的配置。
您可以通过查看system.site配置对象来审查站点的当前 UUID 值。这也可以使用 Drush 或 Drupal Console 命令行工具完成。
使用 Drush,输入以下命令:
$ drush config-get system.site
使用 Drupal Console,输入以下命令:
$ drupal debug:config system.site
一个同步文件夹
Drupal 使用一个同步文件夹来保存要导入到当前站点的配置 YAML 文件。此文件夹由CONFIG_SYNC_DIRECTORY常量表示。如果您在站点的settings.php全局$config_directories变量中未定义此变量,则它将是站点文件目录中的一个随机命名的目录。
当 Drupal 8 进入其 beta 发布周期时,此文件夹被引用为预发布文件夹,并由CONFIG_STAGING_DIRECTORY引用。现在已弃用;然而,配置管理系统的内部支持将CONFIG_STAGING_DIRECTORY作为CONFIG_SYNC_DIRECTORY读取。这将在 Drupal 9 中删除。
同步表单将使用配置管理发现服务来查找需要从该文件夹导入的配置更改。
从新站点安装配置
Drupal 的配置管理系统不允许导入来自不同 Drupal 站点的配置实体。当安装 Drupal 站点时,system.site配置实体会保存当前站点实例的 UUID。只有该站点数据库的克隆版本才能接受从其导入的配置。
配置安装器配置文件是一个自定义发行版,它将允许您导入配置,即使配置的站点 UUID 不同。配置文件不会自行安装。当您使用配置文件时,它将提供一个界面来上传配置导出文件,然后将其导入,如下面的截图所示:

发行版可以在www.drupal.org/project/config_installer找到。
使用命令行工作流程
Drupal 8 的配置系统解决了在 Drupal 7 中导出和部署配置时遇到的问题。然而,同步配置的任务仍然是一个用户界面任务,需要操作包含 Drupal 8 站点配置导出的存档文件。
配置管理可以通过 Drush 在命令行上完成,无需安装。这减轻了登录到生产网站以导入更改的要求。它还打开了将配置放入版本控制的高级工作流程的能力。
在本菜谱中,我们将使用 Drush 将开发站点的配置导出到文件系统。然后,将导出的配置文件复制到生产站点的配置目录。使用 Drush,将配置导入生产环境以完成部署。
准备中...
您需要一个作为开发站点的基 Drupal 站点。另一个 Drupal 站点,它是开发站点的克隆版本,必须可用以作为生产 Drupal 站点。
本菜谱使用 Drush。如果您尚未安装 Drush,可以在docs.drush.org/en/master/install/找到说明。Drush 需要在您的两个 Drupal 站点的位置安装。
如何操作...
-
为了演示目的,将您的开发站点名称更改为
Drush Config Sync Demo!。这样,至少有一个配置更改需要导入到生产 Drupal 站点。 -
打开命令行终端,并将您的目录更改为开发 Drupal 站点的当前工作目录。
-
使用
drush config-export命令将配置导出到一个目录。该命令将默认为在您的 Drupal 8 站点中定义的sync配置目录。
如果您没有明确定义同步目录,Drupal 会自动在当前站点的上传文件目录中创建一个受保护的文件夹,并在目录名称上添加一个唯一的哈希后缀。
-
你将收到一条消息,表明配置已导出到目录中。
-
使用你选择的方法,将配置
sync文件夹的内容复制到其他匹配配置同步文件夹的 Drupal 站点。例如,Drupal 生成的默认文件夹可以是sites/default/files/config_XYZ/sync。 -
打开命令行终端,并将你的目录更改为你的生产 Drupal 站点的工作目录。
-
使用
drush config-import命令开始导入你的配置过程。 -
审查对配置实体键所做的更改,并输入
y以确认更改:

- 检查你的配置更改是否已导入。
它是如何工作的...
Drush命令行工具可以利用 Drupal 中的代码与之交互。config-export命令复制了配置管理模块完整站点导出提供的功能。然而,你不需要启用配置管理模块,该命令才能工作。该命令将提取可用的站点配置并将其写入目录,该目录未存档。
config-import命令会解析目录中的文件。它将尝试运行与配置管理模块的同步概览表类似的差异检查,然后导入所有更改。
还有更多...
在 Drupal 中,还有其他一些与配置管理系统协同工作的方法。我们将在下一节中探讨这些选项。
Drush config-pull
Drush 提供了一种简化配置在不同站点之间传输的方法。config-pull命令允许你指定两个 Drupal 站点,并在它们之间移动导出配置。你可以指定/sites目录下的子目录名称或 Drush 别名。
以下命令将复制开发站点的配置并将其导入到预发布服务器的站点:
drush config-pull @mysite.local @mysite.staging
此外,你可以指定--label选项。这代表$config_directories设置中的一个文件夹键。该选项默认自动设置为sync。或者,你可以使用--destination参数指定一个未在$config_directories设置中指定的任意文件夹。
使用 Drupal 控制台
Drush 自 Drupal 4.7 以来一直是 Drupal 社区的一部分,是一个自定义构建的命令行工具。Drupal 控制台是一个基于 Symfony 控制台的应用程序,用于与 Drupal 交互。Drupal 控制台项目提供了一种通过命令行进行配置管理的方法。
你可以在第十三章,Drupal CLI或www.drupalconsole.com/了解更多关于 Drupal 控制台的信息。
工作流程相同,只是命令的命名不同。配置导出命令是 config:export,并且它将自动导出到你的系统临时文件夹,直到传递一个目录。然后你可以使用 config:import 命令导入配置。
从命令行编辑配置
Drush 和 Drupal 控制台都支持通过命令行以 YAML 格式编辑配置的能力。这两个工具以相同的方式操作,并且具有相似的命令名称:
-
Drush:
config-edit [name] -
控制台:
config:edit [name]
差异在于,如果你没有传递一个名称,Drush 将列出所有可编辑的选项,而控制台允许你进行搜索。
当你编辑一个配置项时,你的默认基于终端的文本编辑器将打开。你将看到一个可以编辑的 YAML 文件。一旦你保存了更改,配置就会被保存在你的 Drupal 网站上:

导出单个配置项
Drush 和控制台都提供了自己的机制来导出单个配置实体:
-
Drush:
config-get [name] -
控制台:
config:debug [name]
Drush 将配置的输出打印到终端,而控制台默认的行为是将输出写入文件磁盘。例如,以下命令将以 YAML 格式输出 system.site 的值:
$ drush config-get system.site
$ drupal debug:config system.site

使用版本控制和命令行工作流程
将配置导出为 YAML 文件的好处是配置可以保持在版本控制中。Drupal 网站的 CONFIG_SYNC_DIRECTORY 目录可以提交到版本控制,以确保它在不同环境中传输并正确更新。部署工具随后可以使用 Drush 或控制台自动导入更改。
Drush 提供的 config-export 命令提供了 Git 集成:
drush config-export --add
添加 --add 选项将运行 git add -p 以进行交互式暂存更改的配置文件:
drush config-export --commit --message="Updating configuration "
--commit 和可选的 --message 选项将暂存所有配置文件更改,并使用你的消息提交:
drush config-export --push --message="Updating configuration "
最后,你也可以指定 --push 以提交更改并将其推送到远程仓库。
参考信息
-
请参阅第十三章,Drupal 命令行
-
请参阅 Drush 在
docs.drush.org/en/master/ -
请参阅 Drupal 控制台在
www.drupalconsole.com/
更新和安装新的模块配置
Drupal 8 中的模块在其config/install目录内提供配置 YAML 文件。由于站点控制配置,模块的config/install目录中的新配置不会自动安装。模块开发者必须编写更新函数,以便在添加时导入新的配置。虽然这是贡献模块应遵循的实践,但对于私人项目来说,这个过程可能有些繁琐。
幸运的是,Drupal 社区已经提出了一种解决方案,该解决方案提供了一种配置管理流程,允许更新模块提供的默认配置。配置更新管理器模块允许您从模块导入新的配置,或者在修改后将其还原到原始配置。实际上,该模块是讨论在第四章,“扩展 Drupal”中提到的功能模块的依赖项。
在本食谱中,我们将使用配置更新管理器来审查模块的配置差异,并还原已修改的配置。
如何操作...
- 使用以下 Composer 命令将配置更新管理器项目添加到您的 Drupal 安装中:
$ cd /path/to/drupal8
$ composer require drupal/config_update
- 前往“扩展”页面,安装配置更新报告模块,这是配置更新管理器的用户界面:

-
要访问配置报告,请转到开发部分下的工具栏中的“配置”,然后点击“配置同步”。
-
前往“更新报告”选项卡。这提供了生成配置报告的概述:按配置类型、模块、主题和配置文件。
-
审查系统模块的配置。从模块的下拉按钮中选择系统模块。
-
对于
system.site行,点击“显示差异”按钮链接。这将审查默认配置和当前站点配置之间的差异报告:

-
点击“返回到‘更新报告’页面”以返回到主报告,以便可以还原配置。
-
展开下拉按钮,以便您可以点击“还原到源”:

-
点击确认表单上的“还原”以完成操作。
-
配置已还原到原始安装状态。
它是如何工作的...
配置更新管理器提供两个模块:配置更新基础和配置更新报告。基础模块提供了一个底层 API,用于列出配置、还原配置以及运行带有结果的差异检查。它扩展了 Drupal 核心的配置操作。报告模块在基础模块之上提供了一个用户界面。功能模块使用基础模块来提供差异审查和配置的自动还原。
在回滚配置时,会收集原始值,然后用来覆盖系统中当前存在的配置。报告还允许导入添加到模块中的新配置。
更多内容...
还有其他贡献项目和方法,用于在模块中处理配置。
配置开发模块
对于模块开发者,有配置开发模块。配置开发模块提供了一种命令行方法来导入和导出配置。这对于贡献模块的开发者来说很有用。它简化了将配置导出到config/install目录以及更新配置的过程。
该模块在模块的info.yml文件中查找config_devel条目。以下是一个从 Drupal Commerce 模块的 Commerce Store 子模块中取出的示例:
config_devel:
install:
- commerce_store.commerce_store_type.online
- commerce_store.settings
- core.entity_view_display.commerce_store.online.default
- views.view.commerce_stores
- system.action.commerce_delete_store_action
使用 Drush,可以通过配置开发提供的命令来导出和导入数据。以下命令将导出列出的配置到config/install目录:
$ drush config-devel-export commerce_store
参见
-
参考配置更新管理器项目页面,
www.drupal.org/project/config_update -
参考基于命令行的配置开发项目,
www.drupal.org/project/config_devel
第十章:实体 API
在本章中,我们将探索 Entity API 以创建自定义实体,并了解它们是如何被处理和覆盖以下菜谱:
-
创建配置实体类型
-
创建内容实体类型
-
为内容实体类型创建捆绑包
-
为实体实现自定义访问控制
-
提供自定义存储处理程序
-
创建路由提供者
简介
在 Drupal 中,实体是具有特定结构的数据的表示。存在特定的实体类型,它们具有不同的捆绑包和附加到这些捆绑包的字段。捆绑包是实体的实现,可以附加字段。从编程的角度来看,可以将支持捆绑包的实体视为一个抽象类,每个捆绑包作为一个扩展该抽象类的类。字段被添加到捆绑包中。这也是术语的推理部分:实体类型可以包含一个 捆绑包 的字段。
实体是 Drupal 中定义的实体类型的实例。Drupal 8 提供了两种实体类型:configuration 和 content。配置实体不可字段化,代表网站内的配置。内容实体可字段化,并且可以有捆绑包。捆绑包通常通过配置实体进行控制。
在 Drupal 8 中,有一个 Entity API 模块。它是在 Drupal 7 中创建的,用于扩展实体子系统;其大部分功能现在都已集成到 Drupal 核心中。该模块的目标是通过在每个小版本发布周期(8.1.x、8.2.x 等)中将更多功能合并到 Drupal 核心中,来为开发者体验中的实体开发改进。每个菜谱中都会有一个 还有更多... 部分,介绍如何使用 Entity API 模块简化菜谱。
创建配置实体类型
Drupal 8 利用实体 API 为配置提供配置验证和扩展功能。使用底层实体结构,配置有一个适当的 创建、读取、更新 和 删除(CRUD)过程,可以对其进行管理。配置实体不可字段化。配置实体的所有属性都定义在其配置架构定义中。
最常见的配置实体与 Drupal 核心的 config_object 类型交互,如在第 第四章 “扩展 Drupal” 和 第九章 “配置管理 - 在 Drupal 8 中部署” 中所述,用于存储和管理网站的配置。配置实体还有其他用途,例如菜单、视图显示、表单显示和联系表单,这些都是配置实体。
在这个菜谱中,我们将创建一个新的配置实体类型,称为 SiteAnnouncement。这将提供一个简单的配置实体,允许您创建、编辑和删除可以在网站上显示的重要公告的简单消息。
准备工作
你需要一个自定义模块来放置代码以实现配置实体类型。让我们为你的类创建一个src目录。有关创建自定义模块的信息,请参阅第四章的创建模块配方,扩展 Drupal。
不要使用当前已安装的模块,否则 Drupal 将无法安装您的新实体类型。
如何做到这一点...
- 在我们的模块的基本目录中,让我们创建一个
config目录,并在其中创建一个名为schema的子目录。在子目录中,创建一个名为mymodule.schema.yml的文件,该文件将包含您的配置实体模式:

- 在我们的
mymodule.schema.yml中,为mymodule.announcement.*:添加一个定义以提供我们的标签和消息存储:
# Schema for the configuration files of the Site Announcement.
mymodule.announcement.*:
type: config_entity
label: 'Site announcement'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
message:
type: text
label: 'Text'
我们将定义配置实体命名空间为公告,我们将将其提供给 Drupal 的实体注解块。然后我们将告诉 Drupal 这是一个config_entity并提供模式标签。
使用映射数组,我们将提供构成我们的实体和将要存储的数据的属性。
- 在我们的模块的
src文件夹中创建一个Entity目录。首先,我们将通过创建一个SiteAnnouncementInterface.php文件来为我们的实体创建一个接口。SiteAnnouncementInterface接口将扩展\Drupal\Core\Config\Entity\ConfigEntityInterface:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
interface SiteAnnouncementInterface extends ConfigEntityInterface {
/**
* Gets the message value.
*
* @return string
*/
public function getMessage();
}
这将通过我们的实体来实现,并提供方法要求。为实体提供一个接口是最佳实践。这允许你在其他开发者扩展你的实体或在进行高级测试并需要模拟一个对象时提供所需的方法。我们还提供了一个返回我们自定义属性的方法。
- 让我们在
src/Entity目录中创建SiteAnnouncement.php。此文件将包含SiteAnnouncement类,该类扩展\Drupal\Core\Config\Entity\ConfigEntityBase并实现我们的实体接口:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
class SiteAnnouncement extends ConfigEntityBase implements SiteAnnouncementInterface {
/**
* The announcement's message.
*
* @var string
*/
protected $message;
/**
* {@inheritdoc}
*/
public function getMessage() {
return $this->message;
}
}
在前面的代码中,我们添加了在模式中定义的message属性作为类属性。我们在实体接口中定义的方法用于返回该值并与我们的配置实体交互。
- 实体使用注解文档块。我们将从提供实体 ID、标签、配置前缀和配置导出键名称开始我们的注解块:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
/**
* @ConfigEntityType(
* id ="announcement",
* label = @Translation("Site Announcement"),
* config_prefix = "announcement",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "message",
* }
* )
*/
class SiteAnnouncement extends ConfigEntityBase implements SiteAnnouncementInterface {
/**
* The announcement's message.
*
* @var string
*/
protected $message;
/**
* {@inheritdoc}
*/
public function getMessage() {
return $this->message;
}
}
注解文档块告诉 Drupal 这是一个ConfigEntityType插件的实例。id是实体类型的内部机器名标识符,而label是其可读版本。config_prefix与我们使用mymodule.announcement定义的模式相匹配。实体键定义告诉 Drupal 代表我们的标识符和标签的属性。
当指定config_export时,我们正在告诉配置管理系统在导出我们的实体时哪些属性是可导出的。
- 接下来,我们将向我们的实体注释添加
handlers。我们将定义一个将显示可用实体条目和表单的类,以处理我们的实体:
/**
* @ConfigEntityType(
* id ="announcement",
* label = @Translation("Site Announcement"),
* handlers = {
* "list_builder" = "Drupal\mymodule\SiteAnnouncementListBuilder",
* "form" = {
* "default" = "Drupal\mymodule\SiteAnnouncementForm",
* "add" = "Drupal\mymodule\SiteAnnouncementForm",
* "edit" = "Drupal\mymodule\SiteAnnouncementForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* }
* },
* config_prefix = "announcement",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "message",
* }
* )
*/
handlers数组指定了提供与我们的实体交互功能的类。list_builder类将被创建以显示我们的实体表。form数组提供了用于创建、编辑或删除我们的配置实体的表单的类。
- 最后,为了我们的实体注释,我们需要为我们的
delete、edit和collection(列表)页面定义路由。Drupal 将根据我们的注释自动构建路由:
/**
* @ConfigEntityType(
* id ="announcement",
* label = @Translation("Site Announcement"),
* handlers = {
* "list_builder" = "Drupal\mymodule\SiteAnnouncementListBuilder",
* "form" = {
* "default" = "Drupal\mymodule\SiteAnnouncementForm",
* "add" = "Drupal\mymodule\SiteAnnouncementForm",
* "edit" = "Drupal\mymodule\SiteAnnouncementForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* }
* },
* config_prefix = "announcement",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* links = {
* "delete-form" = "/admin/config/system/site-announcements/manage/{announcement}/delete",
* "edit-form" = "/admin/config/system/site-announcements/manage/{announcement}",
* "collection" = "/admin/config/system/site-announcements",
* },
* config_export = {
* "id",
* "label",
* "message",
* }
* )
*/
存在着一个为实体提供路由服务的路由服务,它将自动根据此注释为 Drupal 提供具有适当控制器的路由。
-
通过在模块的
src目录中创建一个SiteAnnouncementListBuilder.php文件并扩展\Drupal\Core\Config\Entity\ConfigEntityListBuilder来创建在list_builder处理程序中定义的SiteAnnouncementListBuilder类:\Drupal\Core\Entity\EntityForm:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\mymodule\Entity\SiteAnnouncementInterface;
class SiteAnnouncementListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = t('Label');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(SiteAnnouncementInterface $entity) {
$row['label'] = $entity->label();
return $row + parent::buildRow($entity);
}
}
在我们的列表构建器处理程序中,我们重写buildHeader和builderRow方法,以便我们可以将配置实体属性添加到表中。
- 现在,我们需要创建一个实体表单,如我们的表单处理程序数组中定义的那样,以处理我们的添加和编辑功能。在
src目录中创建SiteAnnouncementForm.php以提供扩展\Drupal\Core\Entity\EntityForm类的SiteAnnouncementForm类:
<?php
namespace Drupal\mymodule;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
class SiteAnnouncementForm extends EntityForm {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['label'] = [
'#type' => 'textfield',
'#title' => t('Label'),
'#required' => TRUE,
'#default_value' => $entity->label(),
];
$form['message'] = [
'#type' => 'textarea',
'#title' => t('Message'),
'#required' => TRUE,
'#default_value' => $entity->getMessage(),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$entity = $this->entity;
$is_new = !$entity->getOriginalId();
if ($is_new) {
// Configuration entities need an ID manually set.
$machine_name = \Drupal::transliteration()
->transliterate($entity->label(), LanguageInterface::LANGCODE_DEFAULT, '_');
$entity->set('id', Unicode::strtolower($machine_name));
drupal_set_message(t('The %label announcement has been created.', array('%label' => $entity->label())));
}
else {
drupal_set_message(t('Updated the %label announcement.', array('%label' => $entity->label())));
}
$entity->save();
// Redirect to edit form so we can populate colors.
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
}
}
我们重写form方法以向我们的label和message属性添加表单 API 元素。我们还重写save方法以提供关于所做的更改的用户消息。我们利用实体的toUrl方法将其重定向到collection(列表)页面。我们使用转写服务根据标签生成我们实体的标识符的机器名。
- 接下来,我们将在模块目录中创建一个
mymodule.links.action.yml文件。这将允许我们在路由上定义操作链接。我们将在实体的集合路由上添加一个添加公告链接到我们的实体添加表单:
announcement.add:
route_name: entity.announcement.add_form
title: 'Add announcement'
appears_on:
- entity.announcement.collection
这将指示 Drupal 在appears_on值中指定的路由上渲染entity.announcement.add_form链接。
- 为了使我们的网站公告可以从主管理页面访问,我们需要在模块目录中创建一个
mymodule.links.menu.yml文件:
mymodule.site_announcements:
title: 'Site announcements'
parent: system.admin_config_system
description: 'Manage site announcements.'
route_name: entity.announcement.collection
- 我们模块的结构应该看起来像以下截图:

- 安装模块并检查配置页面。现在,您可以从网站公告链接管理
Site Announcement条目。
它是如何工作的...
当创建配置架构定义时,用于配置命名空间的第一个属性之一是type。此值可以是config_object或config_entity。当类型为config_entity时,定义将用于创建数据库表,而不是为config表结构化序列化数据。
实体由 Drupal 的插件系统提供支持,这意味着存在一个插件管理器。默认的\Drupal\Core\Entity\EntityTypeManager提供实体的发现和处理。实体类型的插件类的ConfigEntityType类将强制在entity_keys定义中设置uuid和langcode。配置实体的存储处理程序默认为\Drupal\Core\Config\Entity\ConfigEntityStorage。ConfigEntityStorage类与配置管理系统交互,以加载、保存和删除自定义配置实体。
更多内容...
Drupal 8 引入了一种类型化数据系统,配置实体和字段都使用该系统。
可用于模式定义的数据类型
Drupal 核心提供自己的配置信息。在core/config/schema位置有一个core.data_types.schema.yml文件。这些是核心提供的基本数据类型,可以在创建配置模式时使用。该文件包含数据类型的 YAML 定义以及表示它们的类:
boolean:
label: 'Boolean'
class: '\Drupal\Core\TypedData\Plugin\DataType\BooleanData'
email:
label: 'Email'
class: '\Drupal\Core\TypedData\Plugin\DataType\Email'
string:
label: 'String'
class: '\Drupal\Core\TypedData\Plugin\DataType\StringData'
当配置模式定义指定一个具有电子邮件类型的属性时,该值将由\Drupal\Core\TypedData\Plugin\DataType\Email类处理。数据类型是一种插件形式,每个插件的注释指定了验证约束。这是围绕 Symfony Validator 组件构建的。
参考以下内容
-
参考第第六章**,使用 Form API 创建表单
-
参考第第四章,扩展 Drupal*
-
参考第第九章,配置管理 - 在 Drupal 8 中部署
-
参考配置模式/元数据在
www.drupal.org/node/1905070
创建内容实体类型
内容实体提供基础字段定义和可配置字段,通过字段模块实现。内容实体还支持修订和翻译。内容实体有显示模式,包括表单和视图,用于控制字段的编辑和显示。当一个实体没有指定包时,会自动有一个与实体同名的包实例。
在本食谱中,我们将创建一个自定义内容实体,不指定任何包。我们将创建一个Message实体,它可以作为通用消息的内容实体。
准备工作
您需要一个自定义模块来放置代码以实现配置实体类型。为您的类创建一个src目录。有关创建自定义模块的信息,请参考第第四章,扩展 Drupal中的创建模块食谱。
不要使用当前已安装的模块,否则 Drupal 不会安装您的新实体类型。
如何操作...
- 让我们在模块的
src文件夹中创建一个Entity目录。首先,我们将通过创建一个MessageInterface.php文件来为我们的实体创建一个接口:

MessageInterface将扩展\Drupal\Core\Entity\ContentEntityInterface:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
interface MessageInterface extends ContentEntityInterface {
/**
* Gets the message value.
*
* @return string
*/
public function getMessage();
}
这将由我们的实体实现,并提供方法要求。为实体提供一个接口是最佳实践。这允许你在其他开发者扩展你的实体或在进行高级测试并需要模拟对象时提供所需的方法。我们还提供了一个方法来返回我们的主要基础字段定义(待定义)。
- 然后,让我们在
src目录中的Entity目录下创建Message.php。此文件将包含Message类,它扩展自\Drupal\Core\Entity\ContentEntityBase并实现了我们实体的接口:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityBase;
class Message extends ContentEntityBase implements MessageInterface {
}
- 我们需要在我们的类上创建一个注解文档块,以提供有关我们实体的信息,例如其
id、label和entity键:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityBase;
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* base_table = "message",
* fieldable = TRUE,
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* )
*/
class Message extends ContentEntityBase implements MessageInterface {
}
id是实体类型的内部机器名称标识符,而label是其可读版本。实体键定义告诉 Drupal 哪些属性代表我们的标识符和标签。
base_table定义了实体将存储的数据库表,而fieldable允许通过 Field UI 模块配置自定义字段。
- 接下来,我们将向我们的实体添加
handlers。我们将使用 Drupal 提供的默认处理器:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* },
* base_table = "message",
* fieldable = TRUE,
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* )
*/
handlers数组指定了提供与我们的实体交互功能的类。将创建一个列表构建器类来显示我们的实体表。表单数组提供了用于创建、编辑或删除我们的内容实体时使用的表单的类。
- 可以添加一个额外的
handler,即route_provider,它可以动态生成我们的规范(查看)、edit、add、delete和collection(列表)路由:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* base_table = "message",
* fieldable = TRUE,
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/messages/{message}",
* "edit-form" = "/messages/{message}/edit",
* "delete-form" = "/messages/{message}/delete",
* "collection" = "/admin/content/messages"
* },
* )
*/
为实体存在一个路由服务,它将自动为 Drupal 提供基于此注解的正确控制器路由。
- 然后,我们在实体的注解中定义一个管理权限属性,系统默认会检查所有创建、更新和删除操作:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* admin_permission = "administer message",
* base_table = "message",
* fieldable = TRUE,
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/messages/{message}",
* "add-form" = "/messages/add",
* "edit-form" = "/messages/{message}/edit",
* "delete-form" = "/messages/{message}/delete",
* "collection" = "/admin/content/messages"
* },
* )
*/
- 我们需要实现
baseFieldDefinitions方法以满足FieldableEntityInterface接口,这将为我们提供字段定义到实体的基础表中。将以下方法添加到你的类中:
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['title'] = BaseFieldDefinition::create('string')
->setLabel(t('Title'))
->setRequired(TRUE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -5,
))
->setDisplayConfigurable('form', TRUE);
$fields['content'] = BaseFieldDefinition::create('text_long')
->setLabel(t('Content'))
->setDescription(t('Content of the message'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'text_default',
'weight' => 0,
))
->setDisplayConfigurable('view', TRUE)
->setDisplayOptions('form', array(
'type' => 'text_textfield',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
return $fields;
}
FieldableEntityInterface接口是通过ContentEntityBase类使用ContentEntityInterface实现的。该方法需要返回一个BaseFieldDefinitions数组,用于类型化数据定义。父类为我们实体注解中的大多数entity_keys值提供了字段定义。我们必须提供标签字段以及我们实现中的任何特定字段。
content基础字段定义将保存消息的实际文本。
- 接下来,我们将在我们的类中实现
getMessage方法以满足我们的接口,并提供一种检索消息文本值的方式:
/**
* {@inheritdoc}
*/
public function getMessage() {
return $this->get('content')->value;
}
此方法提供了一个围绕定义的基本字段值的包装,并返回它。
- 通过创建一个
MessageListBuilder.php文件并扩展\Drupal\Core\Entity\EntityListBuilder来定义我们列表构建器处理程序中的MessageListBuilder类。我们需要覆盖默认实现以显示我们的基本字段定义:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
class MessageListBuilder extends EntityListBuilder {
public function buildHeader() {
$header['title'] = t('Title');
return $header + parent::buildHeader();
}
public function buildRow(EntityInterface $entity) {
$row['title'] = $entity->label();
return $row + parent::buildRow($entity);
}
}
在我们的列表构建器处理程序中,我们覆盖了 buildHeader 和 builderRow 方法,以便我们可以将配置实体属性添加到表中。
- 在我们继续之前,我们必须在模块的根目录中创建一个
mymodule.permissions.yml文件。我们需要提供administer message的权限定义,正如我们在注解中所提供的:
administer message:
title: 'Administer messages'
- 我们模块的结构应该类似于以下截图:

- 安装模块。转到
/messages/add创建我们的第一个自定义内容实体条目,然后在/admin/content/messages上查看:

它是如何工作的...
内容实体是 EntityType 插件的一种版本。当你定义内容实体类型时,注解块以 @ContentEntityType 开始。这个声明以及其中的属性代表了初始化 \Drupal\Core\Entity\ContentEntityType 类实例的定义,就像所有其他的插件注解一样。ContentEntityType 插件类实现了一个构造函数来提供默认的 storage 和 view_builder 处理程序,这迫使我们实现 list_builder 和 form 处理程序数组。
实体类型的插件管理器位于 entity_type.manager 服务名称下,默认通过 \Drupal\Core\Entity\EntityTypeManager 提供。然而,虽然注解定义了插件信息,但扩展 ContentEntityBase 的 Message 类提供了操作它所表示数据的方式。
还有更多...
我们将讨论如何为你的实体添加额外的功能,并使用实体模块来简化开发过程。
使用 AdminHtmlRouteProvider 提供者
我们的 Message 实体类型实现了 DefaultHtmlRouteProvider 类。还有一个 \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider 类。这个类覆盖了 getEditFormRoute 和 getDeleteFormRoute 方法,并用 _admin_route 标记它们。这将导致这些表单在管理主题中渲染。
将集合路由作为本地任务标签
在这个菜谱中,我们指定了消息集合路由为 /admin/content/messages。如果不将此路由作为 /admin/content 路由下的本地任务实现,它将不会作为标签显示。这可以通过为模块创建一个 links.task.yml 文件来完成。
在 mymodule.links.task.yml 中,添加以下 YAML 内容:
entity.message.collection_tab:
route_name: entity.message.collection
base_route: system.admin_content
title: 'Messages'
这指示 Drupal 使用在routing.yml文件中定义的entity.message.collection路由,作为system.admin_content路由的子路由:

参见
- 参见第四章,扩展 Drupal
为内容实体类型创建一个捆绑包
捆绑包允许你拥有内容实体不同变体的内容。所有捆绑包共享相同的基字段定义,但不配置字段。这允许每个捆绑包拥有自己的自定义字段。显示模式也依赖于特定的捆绑包。这允许每个捆绑包为表单模式和查看模式拥有自己的配置。
使用前一个菜谱中的自定义实体,我们将添加一个配置实体作为捆绑包。这将允许你为多个自定义字段配置有不同的消息类型。
准备工作
我们需要一个自定义模块来放置代码以实现配置实体类型。为你的类创建一个src目录。我们需要实现一个自定义内容实体类型,例如本章创建内容实体类型菜谱中的那个。
如何做到这一点...
- 由于内容实体捆绑包是配置实体,我们需要定义我们的配置实体模式。创建一个
config/schema目录和mymodule.schema.yml文件,该文件将包含配置实体的模式:
mymodule.message_type.*:
type: config_entity
label: 'Message type settings'
mapping:
id:
type: string
label: 'Machine-readable name'
uuid:
type: string
label: 'UUID'
label:
type: label
label: 'Label'
langcode:
type: string
label: 'Default language'
我们将定义配置实体的配置前缀为message_type,并将其提供给 Drupal 在实体的注释块中。我们将告诉 Drupal 这是一个config_entity,并为模式提供标签。
通过映射数组,我们提供构成我们的实体的属性和将要存储的数据。
- 在我们模块的
src/Entity目录中,让我们通过创建一个MessageTypeInterface.php文件为我们的捆绑包创建一个接口。MessageTypeInterface将扩展\Drupal\Core\Config\Entity\ConfigEntityInterface:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
interface MessageTypeInterface extends ConfigEntityInterface {
// Empty for future enhancements.
}
这将由我们的实体实现,并提供方法要求。为实体提供一个接口是最佳实践。这允许你在其他开发者扩展你的实体或在进行高级测试并需要模拟对象时提供所需的方法。
我们将实现一个非常基础的捆绑包。即使在未来的增强和测试中的模拟能力,提供接口也是明智的。
- 在
src/Entity中创建一个MessageType.php文件。这将持有MessageType类,该类将扩展\Drupal\Core\Config\Entity\ConfigEntityBundleBase并实现我们捆绑包的接口:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
class MessageType extends ConfigEntityBundleBase implements MessageTypeInterface {
}
在大多数用例中,捆绑实体类可以是一个空类,不提供任何属性或方法。如果捆绑包在其模式定义中提供了额外的属性,它们也会在这里提供,就像任何其他配置实体一样。
- 实体需要被注释。为
id、label、entity键和config_export键创建一个基本注释:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
/**
* Defines the message type entity class.
*
* @ConfigEntityType(
* id = "message_type",
* label = @Translation("Message type"),
* config_prefix = "message_type",
* bundle_of = "message",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* },
* )
*/
class MessageType extends ConfigEntityBundleBase implements MessageTypeInterface {
}
注释文档块告诉 Drupal 这是一个ConfigEntityType插件的实例。id是实体类型的内部机器名称标识符,而label是其可读版本。config_prefix与我们使用mymodule.message_type定义的模式相匹配。实体键定义告诉 Drupal 哪些属性代表我们的标识符和标签。
当指定config_export时,我们正在告诉配置管理系统在导出我们的实体时要导出哪些属性。
- 然后我们将添加处理程序,这些处理程序将与我们的实体交互:
/**
* Defines the message type entity class.
*
* @ConfigEntityType(
* id = "message_type",
* label = @Translation("Message type"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageTypeListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* },
* },
* config_prefix = "message_type",
* bundle_of = "message",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* },
* )
*/
handlers数组指定了提供与我们的实体交互功能的类。列表构建器类将被创建以显示我们的实体表。表单数组提供了用于创建、编辑或删除我们的配置实体的表单的类。
- 可以添加另一个处理程序,即
route_provider,以动态生成我们的规范(视图)、编辑、删除和集合(列表)路由:
/**
* Defines the message type entity class.
*
* @ConfigEntityType(
* id = "message_type",
* label = @Translation("Message type"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageTypeListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* config_prefix = "message_type",
* bundle_of = "message",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* },
* links = {
* "add-form" = "/admin/structure/message-types/add",
* "delete-form" = "/admin/structure/message-types/{message_type}/delete",
* "edit-form" = "/admin/structure/message-types/{message_type}",
* "admin-form" = "/admin/structure/message-types/{message_type}",
* "collection" = "/admin/structure/message-types"
* }
* )
*/
存在着一个用于实体的路由服务,它将自动根据此注释为 Drupal 提供带有适当控制器的路由。添加表单路由尚未支持,需要手动添加。
- 我们需要修改我们的内容实体以使用我们定义的捆绑配置实体。编辑
src/Entity/Message.php文件并调整实体注释:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {...},
* base_table = "message",
* fieldable = TRUE,
* bundle_entity_type = "message_type",
* field_ui_base_route = "entity.message_type.edit_form",
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "bundle" = "type",
* "uuid" = "uuid"
* },
* links = {...},
* )
*/
bundle_entity_type键指定了用作捆绑的实体类型。插件将其验证为实际的实体类型,并标记为配置依赖项。通过field_ui_base_route键指向捆绑的主要编辑表单,它将在捆绑中生成管理字段、管理表单显示和管理显示选项卡。最后,bundle实体键指示 Drupal 使用字段定义来识别实体捆绑,该捆绑将在下一步创建。
添加了bundle实体键后,ContentEntityBase类将自动将一个名为type的实体引用基础字段添加到我们的实体中,该字段引用捆绑配置实体类型。
- 在我们的
list_builder处理程序中创建MessageTypeListBuilder类,在MessageTypeListBuilder.php文件中,并扩展\Drupal\Core\Config\Entity\ConfigEntityListBuilder。我们需要覆盖默认实现以显示我们的配置实体属性:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
class MessageTypeListBuilder extends EntityListBuilder {
public function buildHeader() {
$header['label'] = t('Label');
return $header + parent::buildHeader();
}
public function buildRow(EntityInterface $entity) {
$row['label'] = $entity->label();
return $row + parent::buildRow($entity);
}
}
- 在我们的列表构建器处理程序中,我们将覆盖
buildHeader和builderRow方法,以便我们可以将配置实体属性添加到表中:

- 我们模块的结构应该类似于以下截图:

它是如何工作的...
捆绑包主要通过Field和Field UI模块在配置的字段级别得到最广泛的应用。当你创建一个新的字段时,它有一个用于其全局设置的基存储项。一旦字段被添加到捆绑包中,就会创建一个新的字段配置并将其分配给捆绑包。字段可以为特定捆绑包设置自己的设置,以及表单和视图显示配置。
内容实体捆绑包的工作方式与任何其他配置实体实现一样,但它们扩展了 Field API 在内容实体类型中的可用性。
更多内容...
我们将讨论如何向我们的实体捆绑包添加更多功能,并使用实体模块简化开发者的工作流程。
提供添加新捆绑包的动作链接
Drupal 中有一些称为动作链接的特殊链接。它们出现在页面顶部,通常用于创建links.action.yml文件以允许创建项目的链接。
在你的mymodule.links.action.yml中,每个动作链接定义了它将链接到的路由、标题以及它出现在哪些路由上:
message_type_add:
route_name: entity.message_type.add_form
title: 'Add message type'
appears_on:
- entity.message_type.collection
appears_on 键接受多个值,这将允许此路由链接出现在多个页面上:

参考以下内容
-
请参考第四章,扩展 Drupal
-
请参考第九章,配置管理 - 在 Drupal 8 中部署
-
请参考第十章中的创建配置实体类型配方,实体 API
为实体实现自定义访问控制
所有实体都有一组处理程序,用于控制特定的功能。一个处理程序处理访问控制。当未指定访问处理程序时,基础 \Drupal\Core\Entity\EntityType 模块将实现 \Drupal\Core\Entity\EntityAccessControlHandler 作为访问处理程序。默认情况下,这将检查是否有模块实现了 hook_entity_create_access 或 hook_entity_type_create_access 并使用它们的意见。否则,如果实现了实体类型的默认管理权限,则默认为该权限。
在此配方中,我们将为我们的实体提供一个管理权限,并实现通过实体 API 模块提供的访问处理程序和权限提供程序。我们将基于一个名为消息的实体来实现。
此配方特别使用实体 API 模块的功能,因为它已经过测试和彻底审查,并减少了样板代码。理想情况下,这将作为 Drupal 核心即将推出的一个小版本的一部分。
准备工作
我们需要一个自定义模块来放置代码以实现配置实体类型。让我们为我们的 PSR-4 风格类创建一个src目录。我们需要实现一个自定义内容实体类型,例如本章中创建内容实体类型配方中的那个。
如何做到这一点...
- 首先,我们需要为实体定义一个管理权限。这是通过确保实体注释文档块中存在
admin_permission键来完成的:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {...},
* base_table = "message",
* fieldable = TRUE,
* admin_permission = "administer messages",
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {...},
* )
*/
核心提供的实体访问处理器将检查实体是否实现了此选项。如果提供了,它将作为访问检查的基础。
- 接下来,我们将想要指定按捆绑包粒度指定的权限:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {...},
* base_table = "message",
* fieldable = TRUE,
* admin_permission = "administer messages",
* permission_granularity = "bundle",
* bundle_entity_type = "message_type",
* field_ui_base_route = "entity.message_type.edit_form",
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "bundle" = "type",
* "uuid" = "uuid"
* },
* links = {...},
* )
*/
permission_granularity键将告诉系统应该生成哪些权限以及如何检查访问。这样,一个用户可以创建公告消息但不能创建公告板消息。
- 然后,我们定义
permission_provider处理器,它将生成我们的权限:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageListBuilder",
* "permission_provider" = "\Drupal\entity\EntityPermissionProvider",
* "form" = {...},
* "route_provider" = {...},
* },
* base_table = "message",
* fieldable = TRUE,
* admin_permission = "administer messages",
* permission_granularity = "bundle",
* bundle_entity_type = "message_type",
* field_ui_base_route = "entity.message_type.edit_form",
* entity_keys = {...},
* links = {...},
* )
*/
- 我们对实体注释的最终调整是更改默认的访问处理器:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageListBuilder",
* "access" = "\Drupal\entity\EntityAccessControlHandler",
* "permission_provider" = "\Drupal\entity\EntityPermissionProvider",
* "form" = {...},
* "route_provider" = {...},
* },
* base_table = "message",
* fieldable = TRUE,
* admin_permission = "administer messages",
* permission_granularity = "bundle",
* bundle_entity_type = "message_type",
* field_ui_base_route = "entity.message_type.edit_form",
* entity_keys = {...},
* links = {...},
* )
*/
-
重建 Drupal 的缓存,或者如果尚未安装,则安装该模块。
-
在权限概述页面上验证权限是否可用:

它是如何工作的...
实体由 Drupal 的插件系统提供支持,这意味着存在一个插件管理器。默认的\Drupal\Core\Entity\EntityTypeManager提供实体的发现和处理。ContentEntityType和ConfigEntityType实体类型和类都扩展了基\Drupal\Core\Entity\EntityType类。
EntityType类构造函数提供了一个默认的access处理器,如果未通过\Drupal\Core\Entity\EntityAccessControlHandler类提供,则提供。每个提供实体类型的核心模块都实现此接口以覆盖至少checkAccess和checkCreateAccess。同时,实体 API 访问处理器扩展此接口以支持捆绑包粒度权限和基于所有者的权限,如果实体以可重用的方式实现了EntityOwnerInterface。
\Drupal\Core\Access\AccessibleInterface定义了一个access方法,所有实体都继承了这个接口。在\Drupal\Core\Entity\Entity中的默认实现将如果操作是create则调用checkCreateAccess,否则调用访问控制器的通用access方法,这将调用实体访问钩子和类的checkAccess方法。
当 Drupal 生成可用权限时,实体 API 模块会找到定义了permission_provider处理器的实体定义,然后调用该类来生成权限。
还有更多...
我们将讨论如何实现实体的自定义访问控制,并使用实体来简化访问控制。
控制实体字段的访问
核心实体访问控制处理器中的checkFieldAccess方法可以被覆盖,以在修改实体时控制对特定实体字段的访问。如果没有被子类覆盖,\Drupal\Core\Entity\EntityAccessControlHandler::checkFieldAccess将始终返回允许的访问结果。该方法接收以下参数:
-
查看和编辑操作
-
当前字段的定义
-
检查访问的用户会话
-
字段项值的一个可能列表
实体类型可以实现自己的访问控制处理程序并覆盖此方法,以提供对其基本字段修改的细粒度控制。一个很好的例子是 User 模块及其 \Drupal\user\UserAccessControlHandler。
用户实体有一个 pass 字段,用于用户的当前密码。还有一个 created 字段,记录用户何时被添加到网站。
对于 pass 字段,如果操作是 view,则返回 denied,但如果操作是 edit,则允许访问:
case 'pass':
// Allow editing the password, but not viewing it.
return ($operation == 'edit') ? AccessResult::allowed() : AccessResult::forbidden();
created 字段使用相反的逻辑。当用户登录时,可以查看网站但不能编辑:
case 'created':
// Allow viewing the created date, but not editing it.
return ($operation == 'view') ? AccessResult::allowed() : AccessResult::forbidden();
参见
- 参考第 第四章,扩展 Drupal
提供自定义存储处理程序
存储处理程序控制实体的加载、保存和删除。\Drupal\Core\Entity\ContentEntityType 为所有内容实体类型提供基本实体类型定义。如果没有指定,则默认存储处理程序是 \Drupal\Core\Entity\Sql\SqlContentEntityStorage。这个类可以扩展以实现替代的 load 方法或保存时的调整。
在这个菜谱中,我们将实现一个方法,支持通过特定的属性来加载实体,而不是必须编写特定的 loadByProperties 方法调用。
准备工作
您需要一个自定义模块来放置代码以实现配置实体类型。为您的 PSR-4 风格类创建一个 src 目录。需要实现一个自定义内容实体类型,例如本章 创建内容实体类型 菜谱中的那个。
如何做...
- 在模块的
src目录中创建一个MessageStorage类。这个类将扩展默认的\Drupal\Core\Entity\Sql\SqlContentEntityStorage类:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
/**
* Defines the entity storage for messages.
*/
class MessageStorage extends SqlContentEntityStorage {
}
通过扩展我们实体类型的默认存储类,我们可以简单地添加与我们的需求相关的新方法,而不是实现额外的业务逻辑。
- 创建一个
loadMultipleByType方法;使用这个方法,我们将提供一个简单的方式来加载特定捆绑包的所有消息:
/**
* Load multiple messages by bundle type.
*
* @param string $message_type
* The message type.
*
* @return array|\Drupal\Core\Entity\EntityInterface[]
* An array of loaded message entities.
*/
public function loadMultipleByType($message_type) {
return $this->loadByProperties([
'type' => $message_type,
]);
}
我们传递 type 属性,这样我们就可以根据消息捆绑包来查询它,并返回所有匹配的消息实体。
- 更新实体的注解块以包含新的存储处理程序定义:
handlers = {
"list_builder" = "Drupal\mymodule\MessageListBuilder",
"access" = "\Drupal\entity\EntityAccessControlHandler",
"permission_provider" = "\Drupal\entity\EntityPermissionProvider",
"storage" = "Drupal\mymodule\MessageStorage",
"form" = {
"default" = "Drupal\Core\Entity\EntityForm",
"add" = "Drupal\Core\Entity\EntityForm",
"edit" = "Drupal\Core\Entity\EntityForm",
"delete" = "Drupal\Core\Entity\EntityDeleteForm"
},
"route_provider" = {
"html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
},
},
- 您现在可以使用以下代码以编程方式与您的消息实体交互:
// Get the entity type manager from the container.
\Drupal::entityTypeManager()
// Access the storage handler.
->getStorage('message')
// Invoke the new method on custom storage class.
->loadMultipleByType('message');
它是如何工作的...
在定义内容实体类型时,注解块以 @ContentEntityType 开始。这个声明以及其中的属性代表了初始化实体实例的定义
\Drupal\Core\Entity\ContentEntityType 类就像所有其他插件注解一样。
在类构造函数中,有一个合并操作,如果未提供,将为 storage 处理程序提供默认处理程序。这始终默认为 \Drupal\Core\Entity\Sql\SqlContentEntityStorage,因为它提供了帮助其父类 ContentEntityStorageBase 与基于 SQL 的存储交互的方法和逻辑。
配置实体也可以有它们的默认 \Drupal\Core\Config\Entity\ConfigEntityStorage。然而,对于配置实体,配置管理使用 \Drupal\Core\Config\StorageInterface 实现进行存储,而不是扩展 ConfigEntityStorage 的类,这个逻辑位于配置工厂服务中。
扩展 SqlContentEntityStorage 重新使用默认 Drupal 实现所需的方法,并提供了一种创建与加载、保存等交互的自定义方法的简单方法。
更多...
我们将讨论自定义存储处理程序以及不同存储后端的利用。
为实体使用不同的存储后端
Drupal 提供了支持不同数据库存储后端的机制,这些后端不是 Drupal 核心提供的,例如 MongoDB。尽管在撰写本书时它对 Drupal 8 来说还不稳定,但有一个 MongoDB 模块提供了存储交互。
该模块提供了 \Drupal\mongodb\Entity\ContentEntityStorage,它扩展了 \Drupal\Core\Entity\ContentEntityStorageBase。这个类覆盖了用于创建、保存和删除的方法,将它们写入 MongoDB 集合。
项目可以在 www.drupal.org/project/mongodb 找到。
虽然为内容实体及其字段提供自定义存储后端还有更多步骤,但这作为如何选择将自定义实体放置在不同存储后端的示例。
参见
-
参阅 第四章,扩展 Drupal
-
参阅 第七章,使用插件即插即用
创建路由提供者
实体可以实现一个路由提供者,为实体的规范(查看)、编辑、删除和集合(列表)路由创建路由定义。截至 Drupal 8.3.0,所有通常所需的路由都已生成(在 8.0.0 中并非如此)。提供者接受特定链接定义的路径,并将其转换为路由和可访问路径。
在这个菜谱中,我们将扩展默认的 \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider 并覆盖规范路由以与编辑路由相同,因为我们假设消息总是嵌入的。
这与在 8.4 中修复的错误有关,内容翻译模块通过假设所有实体都有一个规范链接来导致错误,而它们可能只支持编辑--请参阅 www.drupal.org/node/2479377。
准备工作
你需要一个自定义模块来放置代码以实现配置实体类型。为你的类创建一个src目录。需要实现一个自定义内容实体类型,例如本章中创建内容实体类型配方中的那个。
如何实现...
-
在
src目录中创建一个MessageHtmlRouteProvider类,该类扩展了\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
/**
* Provides HTML routes for the message entity type.
*/
class MessageHtmlRouteProvider extends DefaultHtmlRouteProvider {
}
- 覆盖提供的
getCanonicalRoute方法并返回getEditFormRoute的值:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
/**
* Provides HTML routes for the message entity type.
*/
class MessageHtmlRouteProvider extends DefaultHtmlRouteProvider {
/**
* {@inheritdoc}
*/
protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
// Messages use the edit-form route as the canonical route.
// @todo Remove this when #2479377 gets fixed.
return $this->getEditFormRoute($entity_type);
}
}
-
重建 Drupal 的缓存以使更改生效并重新构建路由。
-
导航到
/message/{message}现在将加载编辑表单,就像/message/{message}/edit一样。
它是如何工作的...
实体由 Drupal 中的插件系统提供支持,这意味着存在一个插件管理器。默认的\Drupal\Core\Entity\EntityTypeManager提供了实体的发现和处理。\Drupal\Core\Entity\EntityTypeManagerInterface指定了一个getRouteProviders方法,该方法预期返回一个字符串数组,提供\Drupal\Core\Entity\Routing\EntityRouteProviderInterface接口实现的完全限定类名。
在core.services.yml中定义了一个事件订阅者,名为entity_route_subscriber。这个服务订阅了动态路由事件。当发生这种情况时,它使用实体类型管理器来检索所有实体类型实现,这些实现提供路由订阅者。然后,它将接收到的所有\Symfony\Component\Routing\RouteCollection实例聚合起来,并将它们合并到系统的主路由集合中。
更多内容...
Drupal 8 引入了路由类型,并为我们的实体提供了添加路由的功能。
实体 API 模块提供了额外的提供者
实体模块提供了两个针对支持修订和批量删除表单选项的实体的新路由提供者。
如果你有一个实现了RevisionLogInterface接口的实体,修订路由提供者会生成用于管理修订的用户界面。然后,你为router_providers数组添加一个revision条目,指向新的路由提供者:
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* "revision" = "Drupal\entity\Routing\RevisionRouteProvider",
* },
然后,你只需在你的实体links定义中定义额外的项:
* links = {
* "revision" = "/messages/{message}/revisions/{message_revision}/view",
* "revision-revert-form" = "/messages/{message_enhanced}/revisions/{message_revision}/revert",
* "version-history" = "/messages/{message}/revisions",
* "canonical" = "/messages/{message}",
* "edit-form" = "/messages/{message}/edit",
* "delete-form" = "/messages/{message}/delete",
* "collection" = "/admin/content/messages"
* }
这减少了实现Entity所需的样板代码量。对于实现示例,请参考实体 API 测试模块entity_module_test中的EnhancedEntity类。
参见
-
参考第4 章,扩展 Drupal
-
参考 Drupal 8 的路由系统,见
www.drupal.org/developing/api/8/routing
第十一章:离开 Drupalicon 岛
在本章中,我们将详细说明如何使用第三方库,例如 JavaScript、CSS 和 PHP:
-
实现和使用第三方 JavaScript 库
-
实现和使用第三方 CSS 库
-
实现和使用第三方 PHP 库
简介
Drupal 8 带着一种自豪地在外地建造的态度。已经做出了努力,使用更多由整个 PHP 社区和其他社区创建的组件。Drupal 8 是用 Symfony 构建的。它包括 Twig 作为其模板系统,提供的 WYSIWYG 编辑器作为其 CKEditor,以及 PHPUnit 用于测试。
Drupal 8 是如何推广使用其他地方制作的库的?Drupal 8 中的新资产管理系统使得使用前端库变得更加容易。Drupal 实现了来自PHP 框架互操作性小组(PHP-FIG)的 PSR-0 和 PSR-4,PHP 标准建议(PSRs)是用于增加 PHP 应用程序之间互操作性的建议标准。这简化了集成第三方 PHP 库的过程。
这两个领域将在 Drupal 8 的每个小版本中不断改进。这些领域将在本章中提到。
实现和使用第三方 JavaScript 库
在过去,Drupal 只包含 jQuery 和几个 Drupal 核心用于 JavaScript API 的 jQuery 插件。这种情况在 Drupal 8 中发生了变化。Underscore.js和Backbone.js现在包含在 Drupal 中,为开发者带来了两个流行的 JavaScript 框架。
然而,有许多 JavaScript 框架可以使用。在第5 章“前端为王”中,我们介绍了资产管理系统和库。在这个配方中,我们将创建一个模块,提供Angular.js作为库和自定义 Angular 应用程序;演示可在 AngularJS 主页上查看。
准备工作
在这个例子中,我们将使用 Bower 来管理我们的第三方Angular.js库组件。如果您不熟悉 Bower,它只是一个前端组件的包管理器。您可以选择不使用 Bower,只需手动下载并放置所需的文件。
如果您没有 Bower,您可以按照从bower.io安装 Bower 的说明进行操作:
bower.io/#install-bower。如果您不想安装 Bower,我们将提供手动下载库的链接。
拥有 AngularJS 的背景不是必需的,但很有益。这个配方实现了来自库主页的示例。
如何操作...
- 创建一个名为
mymodule的自定义模块,该模块将提供 AngularJS 库及其实现:
name: My Module!
type: module
description: Provides an AngularJS app.
core: 8.x
- 运行
bower init命令在我们的模块目录中创建一个 Bower 项目。我们将为提示问题使用大多数默认值:
$ bower init
? name mymodule
? description Example module with AngularJS
? main file
? what types of modules does this package expose?
? keywords
? authors Matt Glaman <nmd.matt@gmail.com>
? license GPL
? homepage
? set currently installed components as dependencies? Yes
? would you like to mark this package as private which prevents it from being accidentally published to the registry? No
{
name: 'mymodule',
authors: [
'Matt Glaman <nmd.matt@gmail.com>'
],
description: 'Example module with AngularJS',
main: '',
moduleType: [],
license: 'GPL',
homepage: '',
ignore: [
'**/.*',
'node_modules',
'bower_components',
'test',
'tests'
]
}
? Looks good? Yes
- 接下来,我们将使用
bower install安装 AngularJS 库:
$ bower install --save angular
bower angular#* cached git://github.com/angular/bower-angular.git#1.5.0
bower angular#* validate 1.5.0 against git://github.com/angular/bower-angular.git#*
bower angular#¹.5.0 install angular#1.5.0
angular#1.5.0 bower_components/angular
--save选项将确保包的依赖被保存在创建的bower.json中。如果您没有 Bower,可以从angularjs.org/下载 AngularJS 并将其放置在bower_components文件夹中。
- 创建
mymodule.libraries.yml。我们将定义 AngularJS 为其自己的库:
angular:
js:
'bower_components/angular/angular.js: {}
css:
component:
'bower_components/angular/angular-csp.css': {}
当angular库被附加时,它将添加 AngularJS 库文件并附加 CSS 样式表。
- 接下来,创建一个
mymodule.module文件。我们将使用主题层的预处理器函数将ng-app属性添加到根 HTML 元素:
<?php
/**
* Implements hook_preprocess_html().
*/
function mymodule_preprocess_html(&$variables) {
$variables['html_attributes']['ng-app'] = '';
}
AngularJS 使用ng-app属性作为引导 AngularJS 应用的指令。它标记了应用的根。
-
我们将使用自定义区块来实现 AngularJS 示例所需的 HTML。创建一个
src/Plugin/Block目录和一个AngularBlock.php文件。 -
扩展
BlockBase类并实现build方法以返回我们的 Angular 应用的 HTML:
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a block for AngularJS example.
*
* @Block(
* id = "mymodule_angular_block",
* admin_label = @Translation("AngularJS Block")
* )
*/
class AngularBlock extends BlockBase {
public function build() {
return [
'input' => [
'#type' => 'textfield',
'#title' => $this->t('Name'),
'#placeholder' => $this->t('Enter a name here'),
'#attributes' => [
'ng-model' => 'yourName',
],
],
'name' => [
'#markup' => '<hr><h1>Hello {{yourName}}!</h1>',
],
'#attached' => [
'library' => [
'mymodule/angular',
],
],
];
}
}
我们返回一个包含input、name和我们的library附件的渲染数组。input数组返回文本字段的表单 API 渲染信息。name返回一个常规标记,将 Angular 的变化绑定到yourName作用域变量。
-
安装您的自定义模块,或者如果模块已经安装,重新构建 Drupal 的缓存。
-
从结构页面进入区块布局表单,并将您的区块放置在区域中,例如侧边栏的第一个区域。
-
查看您的 Drupal 站点并与由 AngularJS 驱动的区块进行交互:

它是如何工作的...
Drupal 8 的新资产管理系统提供了与 JavaScript 框架集成的简便性。Bower的使用是可选的,但通常是一种用于管理前端依赖的首选方法。使用 Bower,我们可以将bower_components放置在一个ignore文件中,以防止第三方库进入版本控制。
还有更多...
Drupal 8 使用 Composer 处理 PHP 依赖项,但前端库的最佳实践仍在整理中。
处理外部库的最佳实践
在我们的配方中,我们通过模块内的代码本地副本添加了第三方库。然而,这种方法使得在另一个模块中重用相同的库变得困难。其他模块必须声明对提供库的模块的依赖,或者定义自己的副本,页面上将加载两个版本的 AngularJS。
目前,社区在 Drupal 核心问题队列中正在讨论如何最好地解决这个问题--请参阅www.drupal.org/node/2605130。
一种最佳实践是在 Drupal docroot 中放置一个libraries目录(与modules和themes并列)。一个例子可以在 DropzoneJS 集成模块中找到:
dropzonejs:
title: 'Dropzonejs'
website: http://www.dropzonejs.com
version: 4.0.1
license:
name: MIT
url: https://github.com/enyo/dropzone/blob/master/LICENSE
gpl-compatible: true
js:
/libraries/dropzone/dist/min/dropzone.min.js: {}
css:
component:
/libraries/dropzone/dist/min/dropzone.min.css: {}
这种模式将允许任何模块通过此路径加载库。它的作者建议你有一个定义库和简单集成的基础模块,并始终将其作为一个依赖项。
参见
-
在[添加 Backbone.js 和 Underscore.js 的核心问题]中参考
-
参考第五章的使用新的资产管理系统食谱,前端获胜
-
参考第四章的创建模块食谱,扩展 Drupal
实现和使用第三方 CSS 库
Drupal 提供了许多东西。然而,它不提供任何类型的 CSS 组件库。在第五章的使用新的资产管理系统食谱中,前端获胜,我们添加了FontAwesome作为库。CSS 框架实现了强大的用户界面设计组件,如果你使用包含所有捆绑内容的编译版本,它们可以相当大。资产管理系统可以用来定义每个组件为其自己的库,仅传递为强大的前端性能所需的精确文件。
在这个食谱中,我们将实现 Semantic UI 框架,使用仅 CSS 的发行版,它为每个单独的组件提供 CSS 文件。我们将注册form、button、label和input组件作为库。然后我们的自定义主题将修改 Drupal 的buttons、labels和inputs元素,使其具有 Semantic UI 类并加载适当的库。
准备工作
在这个例子中,我们将使用 Bower 来管理我们的第三方组件。如果你不熟悉 Bower,它是一个用于前端组件的包管理器。除了使用 Bower,你也可以手动下载并放置所需的文件。
如何做到...
-
对于这个食谱,使用 Classy 作为基础主题创建一个名为
mytheme的新自定义主题。这样,你可以重用一些现有的样式。如果你不熟悉创建基础主题,请参考第五章的基于 Classy 创建自定义主题食谱,前端获胜。 -
使用你的终端,导航到你的主题目录。运行
bower init来创建一个bower项目:
$ bower init
? name mytheme
? description Example theme with Semantic UI
? main file
? what types of modules does this package expose?
? keywords
? authors Matt Glaman <nmd.matt@gmail.com>
? license GPL
? homepage
? set currently installed components as dependencies? Yes
? would you like to mark this package as private which prevents it from being accidentally published to the registry? No
{
name: 'mytheme',
authors: [
'Matt Glaman <nmd.matt@gmail.com>'
],
description: 'Example theme with Semantic UI,
main: '',
moduleType: [],
license: 'GPL',
homepage: '',
ignore: [
'**/.*',
'node_modules',
'bower_components',
'test',
'tests'
]
}
? Looks good? Yes
- 接下来,使用
bower install保存 Semantic UI 库:
$ bower install --save semantic-ui
bower semantic-ui#* not-cached git://github.com/Semantic-Org/Semantic-UI.git#*
bower semantic-ui#* resolve git://github.com/Semantic-Org/Semantic-UI.git#*
bower semantic-ui#* download https://github.com/Semantic-Org/Semantic-UI/archive/2.1.8.tar.gz
bower semantic-ui#* extract archive.tar.gz
bower semantic-ui#* resolved git://github.com/Semantic-Org/Semantic-UI.git#2.1.8
bower jquery#>=1.8 not-cached git://github.com/jquery/jquery-dist.git#>=1.8
bower jquery#>=1.8 resolve git://github.com/jquery/jquery-dist.git#>=1.8
bower jquery#>=1.8 download https://github.com/jquery/jquery-dist/archive/2.2.0.tar.gz
bower jquery#>=1.8 extract archive.tar.gz
bower jquery#>=1.8 resolved git://github.com/jquery/jquery-dist.git#2.2.0
bower semantic#².1.8 install semantic#2.1.8
bower jquery#>=1.8 install jquery#2.2.0
--save选项将确保包的依赖被保存在创建的bower.json中。如果你没有 Bower,你可以从github.com/semantic-org/semantic-ui/下载 Semantic UI 并将其放置在bower_components文件夹中。
-
在你的主题基础目录中创建
mytheme.libraries.yml。这将包含你的主要 Semantic UI 定义以及特定的组件库定义。 -
然后你将为
form组件添加一个新的库:
semantic_ui.form:
js:
bower_components/semantic/dist/components/form.js: {}
css:
component:
bower_components/semantic/dist/components/form.css: {}
Semantic UI 的 form 组件包含一个样式表和 JavaScript 文件。您的库确保在库附加时两者都被加载。
button、input和label组件没有 JavaScript 文件。为每个组件添加一个库:
semantic_ui.button:
css:
component:
bower_components/semantic/dist/components/button.css: {}
semantic_ui.input:
css:
component:
bower_components/semantic/dist/components/input.css: {}
semantic_ui.label:
css:
component:
bower_components/semantic/dist/components/label.css: {}
-
现在库已经定义,您可以在添加 Semantic UI 类时使用
attach_libraryTwig 函数将您的库添加到适当的模板中。 -
将 Classy 主题的
templates文件夹中的form.html.twig文件复制到您的主题模板文件夹中。然后,附加mytheme/semantic_ui.form并添加ui和form类:
{{ attach_library('mytheme/semantic_ui.form') }}
<form{{ attributes.addClass(['ui', 'form']) }}>
{{ children }}
</form>
attach_library 函数将附加指定的库。使用 Twig 的 addClass 方法添加 ui 和 form 类。Semantic UI 要求所有元素都具有匹配的 ui 类。
- 然后,将 Classy 主题中的
input.html.twig文件复制到您的主题的template文件夹中。然后,附加mytheme/semantic_ui.input并添加ui和input类:
{{ attach_library('mytheme/semantic_ui.input') }}
<input{{ attributes.addClass(['ui', 'input']) }} />{{ children }}
- 将您刚刚创建的
input.html.twig文件复制并使用它来制作input-submit.html.twig。此模板文件将用于submit和其他按钮:
{{ attach_library('mytheme/semantic_ui.button') }}
<input{{ attributes.addClass(['ui', 'button', 'primary']) }} />{{ children }}
- 最后,将 Classy 中的
form-element-label.html.twig文件复制到您的主题中,并添加标签库以及 Classy 定义的默认类:
{{ attach_library('mytheme/semantic_ui.label') }}
{%
set classes = [
title_display == 'after' ? 'option',
title_display == 'invisible' ? 'visually-hidden',
required ? 'js-form-required',
required ? 'form-required',
'ui',
'label',
]
%}
{% if title is not empty or required -%}
<label{{ attributes.addClass(classes) }}>{{ title }}</label>
{%- endif %}
- 查看表单并检查它是否已被 Semantic UI CSS 框架样式化:

它是如何工作的...
Drupal 8 的新模板系统 Twig 和资产管理系统的简单性提供了与 CSS 框架集成的便利。Bower 的使用是可选的,但通常它是管理前端依赖的首选方法,并且可以用来将第三方库排除在版本控制之外。
虽然将每个组件作为单独的库添加并仅在特定需要时附加可能是一项任务,但它确保了最优的资产交付。启用 CSS 和 JavaScript 聚合后,每个页面将只包含所需的最小资源。当整个 Semantic UI 压缩版仍然有 524 KB 时,这是一个优势。
参考信息
-
请参考 Semantic UI 在
semantic-ui.com/ -
请参考 基于 Classy 创建自定义主题 的 第五章 菜谱 第五章,前端为王
-
请参考 使用新的资产管理系统 的 第五章 菜谱 第五章,前端为王
-
请参考 第五章 的 Twig 模板 菜谱 第五章,前端为王
实现和使用第三方 PHP 库
Drupal 8 使用 Composer 来处理包依赖和基于 PSR 标准的 autoloading 类。这使得我们比在 Drupal 的先前版本中更容易使用任何可用的 PHP 库。
在这个菜谱中,我们将添加IpRestrict Stack Middleware库,以添加基于允许 IP 地址的白名单访问 Drupal 网站的功能。
准备工作
您需要安装 Composer 才能使用 Composer 管理器工作流程。您可以参考getcomposer.org/doc/00-intro.md中的入门文档。我们将添加alsar/stack-ip-restrict库作为我们的 Drupal 安装的依赖项。
如何操作...
-
使用您的终端,导航到您的 Drupal 网站根目录。
-
使用 Composer 的
require命令添加库:
composer require alsar/stack-ip-restrict
-
编译器将随后将库添加到
composer.json文件中,并安装库及其所有依赖项。它的命名空间现在已被注册。 -
现在,您需要实现一个模块,将库注册为中间件服务。我们将该模块命名为
ip_restrict。将以下代码添加到ip_restrict.info.yml文件中:
name: IP Restrict
type: module
description: Restricts access to the Drupal site based on allowed IP addresses
core: 8.x
- 创建
ip_restrict.services.yml。这将注册库到 Drupal 的服务容器中:
parameters:
ip_restrict:
enabled: true
ipAddresses: ['127.0.0.1', 'fe80::1', '::1']
services:
ip_restrict.middleware:
class: Alsar\Stack\IpRestrict
arguments: ['%ip_restrict%']
tags:
- { name: http_middleware }
parameters部分定义了配置值,这些值可以在网站的services.yml文件中覆盖。services部分定义了服务的机器名称、类文件、其构造函数参数以及任何标签。
- 接下来,您需要实现编译器传递注入。这将允许我们在编译容器定义时修改我们的服务。创建一个
src/Compiler目录并创建IpRestrictPass.php。
在创建编译器传递类时,类和文件名必须以特定的方式格式化。它是模块名称的驼峰式版本,后面跟着Pass。
IpRestrictPass.php将提供IpRestrictPass类,该类实现了\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface接口:
<?php
namespace Drupal\ip_restrict\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds the IP Restrict middleware if enabled.
*/
class IpRestrictPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (FALSE === $container->hasDefinition('ip_restrict.middleware')) {
return;
}
$ip_restrict_config = $container->getParameter('ip_restrict');
if (!$ip_restrict_config['enabled']) {
$container->removeDefinition('ip_restrict.middleware');
}
}
}
在我们的编译器传递中,我们检查enabled参数,如果已禁用我们的中间件(以便它不会限制允许的 IP 地址),则将其移除。
- 启用模块。堆栈中间件服务将被注册,现在支持从本地 IP 地址限制访问。
它是如何工作的...
Drupal 8 利用了 Symfony 组件。其中之一是服务容器及其已注册的服务。在容器构建过程中,有一个编译器传递过程,允许修改容器的服务。
首先,我们需要在模块的services.yml文件中注册服务。核心提供的\Drupal\Core\DependencyInjection\Compiler\StackedKernelPass类将自动加载所有带有http_middleware标签的服务,例如我们的ip_restrict.middleware服务。
我们的arguments定义加载在parameters.ip_restrict中定义的项,这些项用于类的构造函数。
使用我们提供的 IpRestrictPass 类,我们也在利用容器的编译周期。我们将查看 ip_restrict 部分的参数值,以检查它们是否已启用。如果启用设置设置为 false,我们将从容器中移除我们的服务。
参见
-
参阅 Drupal 8 文档中的服务和依赖注入
www.drupal.org/docs/8/api/services-and-dependency-injection/services-and-dependency-injection-in-drupal-8 -
请参阅 Symfony 服务容器文档
-
请参阅 Symfony 依赖注入组件文档
symfony.com/doc/current/components/dependency_injection/introduction.html
第十二章:Web 服务
Drupal 8 随带 RESTful Web 服务器功能,以实现与您的应用程序交互的 Web 服务。本章将向您展示如何启用这些功能并构建您的 API,涵盖以下主题:
-
启用 RESTful 接口
-
使用 POST 创建数据
-
使用 PATCH 更新数据
-
使用 Views 提供自定义数据源
-
身份验证
-
使用 JSON API
简介
Drupal 8 提供了几个模块,这些模块使您能够将其转变为 Web 服务提供者。序列化模块提供了一种将数据序列化到或从 JSON 和 XML 等格式反序列化的方法。然后,RESTful Web 服务模块通过 Web API 暴露实体和其他 API。通过 RESTful 资源端点执行的操作使用与在非 API 格式中相同的创建、编辑、删除和查看权限。
HAL 模块使用 Hypertext Application Language(HAL)格式序列化实体。HAL 是一个用于在 API 中超链接资源之间的 Internet Draft 标准约定。当使用 POST 和 PATCH 方法时,需要 HAL+JSON。对于身份验证,HTTP Basic Authentication 模块通过 HTTP 头提供简单的身份验证。
有一个社区主导的努力,使用 JSON API 模块在 Drupal 中实现 JSON API 规范,如本章“使用 JSON API”菜谱中所述。像 HAL 一样,它不仅提供了关于数据应该如何表示的规范,还提供了关于如何通过请求参数进行排序和过滤的规范。
本章介绍了如何使用 RESTful Web 服务模块及其支持模块开发由 Drupal 8 驱动的 RESTful API。我们将介绍如何使用 GET、POST 和 PATCH HTTP 方法来操纵网站上的内容。此外,我们还将介绍如何使用视图提供列出端点的自定义内容。最后,我们将介绍如何处理 API 的自定义身份验证。
在一篇名为“推迟 PUT”的文章中,Web 服务倡议背后的团队选择不实现 PUT,仅支持 PATCH。更多信息,请参阅原文档groups.drupal.org/node/284948。
然而,API 对贡献模块开放,以添加对核心资源或其自己的 PUT 支持。
启用 RESTful 接口
RESTful Web 服务模块提供了暴露 RESTful API 端点的路由。它使用序列化模块来处理响应的规范化以及从请求中数据的反规范化。端点支持特定的格式和身份验证提供者。在安装后,RESTful Web 服务模块不提供任何默认配置的端点。
有一个注意事项:RESTful Web 服务不提供用户界面来配置可用的端点。通过手动编辑配置或 REST UI 模块可以启用资源端点。我们将在这个菜谱中使用 REST UI 模块。
在这个菜谱中,我们将安装 RESTful Web 服务并启用适当的权限,以便通过 REST 检索节点并接收格式化的 JSON。
我们将在后面的菜谱中介绍如何使用 GET、POST、PATCH 和 DELETE。本菜谱涵盖了安装和配置基础模块以启用 Web 服务。
准备工作
如果您正在运行 PHP 5.6,可能需要进行配置更改:always_populate_raw_post_data设置。如果您尝试启用 RESTful Web 服务模块而不更改默认设置,则在安装时将看到以下错误消息:
在 PHP 5.6 版本中,always_populate_raw_post_data PHP 设置应设置为-1。请查阅 PHP 手册了解如何纠正此设置。(当前使用的always_populate_raw_post_data PHP 设置版本未设置为-1。)
如何操作...
- 首先,我们必须将 REST UI 模块添加到我们的 Drupal 站点,这样我们就可以轻松地配置我们的端点:
cd /path/to/drupal8
composer require drupal/restui
- 从管理工具栏转到扩展,并安装以下 Web 服务模块:序列化、RESTful Web 服务以及 REST UI:

-
前往配置,然后在 Web 服务下点击 REST 以配置可用的端点。
-
点击内容行的“启用”按钮:

- 端点启用后,必须进行配置。勾选 GET 方法复选框以允许 GET 请求。然后,勾选 json 复选框以便数据可以以 JSON 格式返回。所有端点都需要选择一个认证提供者。勾选 cookie 复选框,然后保存:

-
启用了任何 RESTful 资源端点都将使用已经为实体类型配置的相同创建、更新、删除和查看权限。为了允许匿名用户通过 GET 访问内容,请确保匿名用户有查看已发布内容权限。
-
使用命令行上的 cURL,现在可以使用 RESTful 端点检索内容。您必须传递
?_format=json以确保返回正确的格式:
curl http://127.0.0.1:8888/node/1?_format=json
{"nid":[{"value":1}],"uuid":[{"value":"9a473f09-fa61-42c9-b4ad-f24b857d04f6"}],"vid":[{"value":51}],"langcode":[{"value":"en"}],"type":[{"target_id":"page","target_type":"node_type","target_uuid":"8a8ad160-69dc-453f-bc11-86775040465e"}],"status":[{"value":true}],"title":[{"value":"Example node"}],"uid":[{"target_id":0,"target_type":"user","target_uuid":"e31b3de2-2195-48c6-9a5e-ab0553461c93","url":"\/user\/0"}],"created":[{"value":1500650071}],"changed":[{"value":1500650374}],"promote":[{"value":true}],"sticky":[{"value":false}],"revision_timestamp":[{"value":1500650374}],"revision_uid":[{"target_id":1,"target_type":"user","target_uuid":"2d7ee3ef-6f8a-4feb-a99a-4af8cfd24402","url":"\/user\/1"}],"revision_log":[],"revision_translation_affected":[{"value":true}],"default_langcode":[{"value":true}],"path":[],"body":[{"value":"Defui dolor elit jus luptatum. Ad augue causa hos loquor luctus minim singularis sino utinam. ","format":"plain_text","summary":""}]}
RESTful Web 服务模块将为每个用户可以查看的字段返回整个实体对象。
它是如何工作的...
RESTful Web 服务模块通过实现一个事件订阅者服务rest.resource_routes来工作,该服务根据其RestResource插件的实现向 Drupal 添加路由。每个插件根据为资源启用的 HTTP 方法返回可用的路由。
当构建路由时,\Drupal\rest\Routing\ResourceRoutes类使用RestResource插件管理器检索所有可用的定义。加载并检查端点配置对象。如果资源插件提供了一个在配置定义中启用的 HTTP 方法,它将开始构建一个新的路由。验证是对定义的支持格式和支持认证定义进行的。如果基本验证通过,新的路由将被添加到RouteCollection并返回。
如果你提供了一个不可用的supported_formats或supported_auth值,端点仍然会被创建。然而,如果你尝试使用无效插件的路由,将会出现错误。在使用 REST UI 模块时,这种情况不会发生,但需要手动提供和管理配置。
基类为资源插件提供的默认路由,\Drupal\rest\Plugin\ResourceBase类,将\Drupal\rest\RequestHandler::handle设置为路由的控制器和方法。此方法将传递的 _format 参数与配置的插件进行比较。如果格式有效,数据将被传递到适当的序列化器。然后,序列化数据将带有适当的内容头返回到请求中。
还有更多...
RESTful Web Services 模块提供了一个强大的 API,有一些额外的项目需要注意。我们将在下一个菜谱中探讨这些内容。
使用 _format 代替 Accept 头
在 Drupal 8 的生命周期早期,直到 8.0.0-beta12,Drupal 支持使用Accept头而不是 _format 参数。不幸的是,存在外部缓存的问题。Drupal 在相同的路径上提供 HTML 和其他格式,只使用不同的Accept头。CDN 和反向代理不会仅基于此头无效化缓存。防止这些外部缓存(如 Varnish)缓存中毒的唯一解决方案是确保实现Vary: Accept头。然而,关于 CDN 和实现差异的问题太多,因此引入了 _format 参数,而不是将扩展(.json和.xml)附加到路径上。
关于问题的详细情况可以在以下核心问题中找到:
-
请参考
www.drupal.org/node/2364011中关于在 URL 上使用内容协商时外部缓存混淆响应格式的说明。 -
检查如何实现基于查询参数的内容协商,作为扩展的替代方案,请参阅
www.drupal.org/node/2481453。
RestResource 插件通过 RESTful Web Services 公开数据
RESTful Web Services 模块定义了一个 RestResource 插件。此插件用于定义资源端点。它们在模块的 Plugin/rest/resource 命名空间中找到,并需要实现 \Drupal\rest\Plugin\ResourceInterface 接口。Drupal 8 提供了两个 RestResource 插件的实现。第一个是由 RESTful Web Services 模块提供的 EntityResource 类。它实现了一个驱动类,允许它表示每种实体类型。第二个是提供自己的 RestResource 插件的 Database Logging 模块。它允许您通过 ID 检索记录的消息。\Drupal\rest\Plugin\ResourceBase 类提供了一个抽象基类,可以扩展用于 RestResource 插件实现。如果子类提供了一个与可用 HTTP 方法匹配的方法,它将支持这些方法。例如,如果一个类只有一个 GET 方法,您只能通过 HTTP GET 请求与该端点交互。另一方面,您可以提供一个跟踪方法,允许端点支持 HTTP TRACE 请求。
Drupal 8 提供了两个 RestResource 插件的实现。第一个是由 RESTful Web Services 模块提供的 EntityResource 类。它实现了一个 deriver 类,允许它表示每种实体类型。第二个是提供自己的 RestResource 插件的 Database Logging 模块。它允许您通过 ID 检索记录的消息。
限制您的 API 速率
许多 API 实现了速率限制,以防止滥用公共 API。当您公开暴露 API 时,您将需要控制击中服务的流量量,并防止滥用者减慢或停止您的服务。
速率限制器 模块实现了多种控制对您的公共 API 访问的方式。有一个选项可以控制特定请求的速率限制,基于 IP 地址的限制,以及 IP 白名单。
您可以在 www.drupal.org/project/rate_limiter 找到 Rate Limiter 模块。
使用 HAL 格式
当安装 HAL 模块时,它可以格式化返回的实体,以提供到相关实体的链接,例如用户、修订版或任何其他实体引用字段。当 HAL 模块安装后,您可以将其添加为支持的格式,然后使用 _format=hal_json 进行请求。来自菜谱的响应将返回一个 _links 参数:
"_links" : {
"http://127.0.0.1:8888/rest/relation/node/page/revision_uid" : [
{
"href" : "http://127.0.0.1:8888/user/1?_format=hal_json"
}
],
"self" : {
"href" : "http://127.0.0.1:8888/node/1?_format=hal_json"
},
"http://127.0.0.1:8888/rest/relation/node/page/uid" : [
{
"lang" : "en",
"href" : "http://127.0.0.1:8888/user/0?_format=hal_json"
}
],
"type" : {
"href" : "http://127.0.0.1:8888/rest/type/node/page"
}
},
参见
-
请参阅 Drupal.org 上的 RESTful Web Services 模块文档,
www.drupal.org/documentation/modules/rest -
请参阅变更记录:基于接受头的路由被查询参数替换,
www.drupal.org/node/2501221 -
参阅第七章,插件即插即用
-
请参阅 速率限制器模块:
www.drupal.org/project/rate_limiter -
请参阅 REST UI 模块:
www.drupal.org/project/restui
使用 POST 创建数据
当与 RESTful Web 服务一起工作时,使用 HTTP POST 方法来创建新实体。我们将使用 HTTP Basic Authentication 来验证用户并创建新节点。
在本配方中,我们将使用公开的节点端点通过 RESTful Web 服务模块创建新的文章内容。我们将使用 json 格式。在 更多内容... 部分中,我们将讨论如何使用 HAL 模块来处理 hal_json 格式。
准备工作
您将使用标准安装提供的 Article 内容类型。按照前面的配方,启用 RESTful 接口,您应该已经使用 Composer 将 REST UI 模块添加到您的 Drupal 安装中。这可以通过以下命令完成:
cd /path/to/drupal8
composer require drupal/restui
在本配方中,Drupal 8 安装可通过 http://127.0.0.1:8888 访问。使用您 Drupal 8 网站的适当 URL。
如何操作...
- 从管理工具栏转到“扩展”,并安装以下 Web 服务模块:序列化、RESTful Web 服务、REST UI 和 HTTP Basic Authentication:

-
前往“配置”,然后在“Web 服务”下点击 REST 来配置可用的端点。
-
点击内容行的“启用”按钮:

- 启用端点后,必须进行配置。勾选 GET 和 POST 方法复选框以允许 GET 和 POST 请求。然后,勾选 json 复选框以便数据可以以 JSON 格式返回。勾选 basic_auth 复选框,然后保存:

- 我们创建 JSON 负载以匹配 Drupal 预期的字段结构:
{
"type": "article",
"status": {"value": true},
"title": {"value": "Testing via REST!"},
"body": {"value": "This article was created using a RESTful endpoint"}
}
- 在我们发送 JSON 负载之前,我们需要检索 CSRF 令牌。我们通过向
/session/token发送GET请求来完成此操作。我们将在 POST 请求头中使用返回的值:
curl -X GET http://127.0.0.1:8888/session/token
- 我们可以通过向
/entity/node?_format=json端点路径发送包含我们正文负载的请求来创建我们的节点。确保您传递有效的用户登录信息,其中admin:admin被使用:
curl -X POST \
'http://127.0.0.1:8888/entity/node?_format=json' \
-u admin:admin \
-H 'content-type: application/json' \
-H 'x-csrf-token: K5UW756_nWJxjX8Lt5NXXrE0xYSAqCn8MPKLbgE6Gps' \
-d '{
"type": "article",
"status": {"value": true},
"title": {"value": "Testing via REST!"},
"body": {"value": "This article was created using a RESTful endpoint"}
}
'
-
成功的请求将返回
201状态码和创建的节点的完整值,包括其标识符。 -
通过访问
/node/{nid},使用请求响应中的节点 ID 来查看您的 Drupal 网站,并验证节点是否已创建:

它是如何工作的...
当使用内容实体和 POST 方法时,端点与用于 GET 请求的端点不同。\\Drupal\\rest\\Plugin\\rest\\resource\\EntityResource 类扩展了 \\Drupal\\rest\\Plugin\\ResourceBase 基类,该类提供了一个路由方法。如果一个资源插件提供了一个 https://www.drupal.org/link-relations/create 链接模板,那么将使用该路径作为 POST 路径。
EntityResource 类将 /entity/{entity_type} 定义为创建链接模板。然后它覆盖 getBaseRoute 方法以确保 entity_type 参数从定义中正确填充。
EntityResource 类将为请求运行一系列条件。首先,它将通过检查实体是否为 null 来验证 POST 请求。然后,如果当前用户也有权编辑提供的所有字段,则当前用户有权创建实体类型。最后,它检查是否传递了标识符。最后一个条件很重要,因为更新只能通过 PATCH 请求进行。
如果实体经过验证,它将被保存。在成功保存后,将返回一个空的 HTTP 201 响应。
还有更多...
使用 POST 请求需要一些特定的格式,这些格式将在下一个菜谱中解释。
使用 HAL 和理解 _links 需求
当使用 HAL 模块和 hal_json 格式时,你必须为实体提供关系。这是通过请求中的 _links 参数完成的。这是为了确保实体被正确创建,并具有它所需的任何关系,例如内容实体包的实体类型。另一个例子是在 RESTful 接口上创建评论。你需要为拥有评论的用户提供一个 _links 条目。
rest.link_manager 服务使用 rest.link_manager.type 和 rest.link_manager.relation,并负责返回类型和关系的 URI。默认情况下,一个包将有一个类似于 /rest/type/{entity_type}/{bundle} 的路径,其关系将类似于 /rest/relation/{entity_type}/{bundle}/{field_name}。
以用户引用为例,我们必须填充一个 uid 字段,如下所示:
{
"_links": {
"type": {
"href": "http://127.0.0.1:8888/rest/type/node/page"
},
"http://127.0.0.1:8888/rest/relation/node/article/uid": [
{
"href": "http://127.0.0.1:8888/user/1?_format=hal_json",
"lang": "en"
}
]
}
}
不幸的是,文档很少,了解需要哪些 _links 的最佳方式是执行 GET 请求并研究从 HAL JSON 返回的 _links。
处理图像
大多数 RESTful API 利用文件的 base64 编码来支持 POST 操作上传图像。不幸的是,这不在 Drupal 核心中得到支持。尽管有一个 serializer.normalizer.file_entity.hal 服务将文件实体序列化为 HAL JSON,但它目前截至 8.3 版本还没有工作,但希望计划在 8.4 版本中实现。
\\Drupal\\hal\\Normalizer\\FileEntityNormalizer 类支持反序列化;然而,它不处理 base64 并期望二进制数据。
对于这个问题,有一个 Drupal 核心问题,可在 www.drupal.org/node/1927648 找到。
使用跨站请求伪造令牌
当使用 POST 请求工作时,如果你使用会话 cookie 进行身份验证,你需要传递一个 跨站请求伪造(CSRF)令牌。当使用会话 cookie 时,需要 X-CSRF-Token 标头以防止意外的 API 请求。
如果你使用 cookie 提供者进行身份验证,你需要从 /session/token 路径请求 CSRF 令牌:
curl -X GET http://127.0.0.1:8888/session/token
参见
-
参考如何序列化文件内容(
base64)以支持 REST GET/POST/PATCH
使用 PATCH 更新数据
当使用 RESTful Web 服务时,使用 HTTP PATCH 方法来更新实体。我们将使用 HTTP Basic Authentication 来验证我们的用户并更新一个节点。
在这个菜谱中,我们将使用暴露的节点端点通过 RESTful Web 服务模块创建新的文章内容。
准备工作
我们将使用标准安装提供的 Article 内容类型。按照 启用 RESTful 接口 菜谱,你应该已经使用 Composer 将 REST UI 模块添加到你的 Drupal 安装中。这可以通过以下命令完成:
cd /path/to/drupal8
composer require drupal/restui
如何操作...
- 从管理工具栏转到扩展,并安装以下 Web 服务模块:序列化、RESTful Web 服务、REST UI 和 HTTP 基本身份验证:

-
前往配置并点击 Web 服务下的 REST 进行配置可用的端点。
-
点击内容行的“启用”按钮:

- 端点启用后,必须进行配置。检查 GET、POST 和 PATCH 方法复选框以允许 GET、POST 和 PATCH 请求。然后,检查 json 复选框,以便可以以 JSON 格式发送数据。检查 basic_auth 复选框,然后保存:

- 在你的 Drupal 网站上创建一个示例
Article节点,你将通过 REST 端点对其进行修改。确保你记下它的路径。你将在稍后的请求中使用相同的路径(例如,/node/4)。这也会显示节点的 ID:

- 然后,开始构建你的 JSON 负载。你必须提供现有节点标识符 (
nid) 的值和内容类型 (type) 的值。确保你提供的nid值与你的当前节点相匹配:
{
"nid" : {
"value" : 4
},
"body" : {
"value" : "This article was updated using the RESTful API endpoint!"
},
"type" : "article"
}
- 在发送 JSON 负载之前,你需要检索 CSRF 令牌。你可以通过针对
/session/token执行 GET 请求来完成此操作。然后,在 POST 请求头中使用返回的值:
curl -X GET http://127.0.0.1:8888/session/token
- 您可以将包含您的正文有效负载的请求发送到
/node/4?_format=json,其中/node/4匹配您想要编辑的节点路径,通过 HTTP PATCH 请求创建我们的节点:
curl -X PATCH \
'http://127.0.0.1:8888/node/4?_format=json' \
-u admin:admin \
-H 'x-csrf-token: MAjbBsIUmzrwHQGNlXxvGMZQJzQCDZbmtecstzbk5UQ' \
-d '{
"type": "article",
"nid": {"value": 4},
"body": {"value": "This article was updated using the RESTful API endpoint!!"}
}
'
-
成功的请求将返回
200头代码和更新节点的完整值。 -
通过访问
/node/{nid}来查看您的 Drupal 网站,并验证节点是否已创建,使用请求响应中的节点 ID:

它是如何工作的…
当与内容实体和 PATCH 方法一起工作时,端点与 GET 方法路径相同。检查当前用户的访问权限,以查看他们是否有权更新实体类型以及请求正文中提供的每个提交字段。
提供的每个字段都会在实体上更新,然后进行验证。如果实体经过验证,它将被保存。在成功保存后,将返回包含整个更新实体内容的 HTTP 200 响应。
使用视图提供自定义数据源
RESTful Web 服务模块提供了视图插件,允许您在视图上公开数据以供您的 RESTful API 使用。这允许您创建一个具有路径并使用序列化插件输出数据的视图。您可以使用此功能输出实体,例如 JSON、HAL JSON 或 XML,并且它可以与适当的头信息一起发送。
在这个菜谱中,我们将创建一个视图,输出 Drupal 网站的用户,提供他们的用户名、电子邮件和图片(如果提供)。
如何做到这一点…
- 从管理工具栏转到扩展,并安装以下 Web 服务模块:序列化和 RESTful Web 服务:

-
前往结构,然后转到视图。点击添加视图。将视图命名为 API Users 并使其显示用户。
-
检查提供 REST 导出复选框,并输入
api/users路径。这是请求将在这里进行的地方:

-
点击保存并编辑。
-
将行插件的格式从实体更改为字段,以便我们可以控制特定的输出。
-
确保您的视图包含以下用户实体字段:名称、电子邮件和图片。
-
将用户:名称字段更改为纯文本格式化程序,并且不要将其链接到用户,以便响应不包含任何 HTML。
-
将用户:图片字段更改为使用 URL 到图片格式化程序,以便只返回 URL 而不是 HTML。
-
保存您的视图。
-
通过访问
/api/users访问您的视图;您将收到包含用户信息的 JSON 响应:
[
{
"name": "spuvest",
"mail": "spuvest@example.com",
"user_picture": "\/sites\/default\/files\/pictures\/2017-07\/generateImage_xIQkfx.jpg"
},
{
"name": "crepathuslus",
"mail": "crepathuslus@example.com",
"user_picture": "\/sites\/default\/files\/pictures\/2017-07\/generateImage_eauTko.gif"
},
{
"name": "veradabufrup",
"mail": "veradabufrup@example.com",
"user_picture": "\/sites\/default\/files\/pictures\/2017-07\/generateImage_HsEjKW.png"
}
]
它是如何工作的
RESTful Web 服务模块提供了显示、行和格式插件,允许您将内容实体导出为序列化格式。REST 导出显示插件允许您指定访问 RESTful 端点的路径,并为请求的格式正确分配 Content-Type 头。
序列化样式是作为 REST 导出显示的唯一支持样式插件提供的。此样式插件仅支持标识为数据显示类型的行插件。它期望从行插件接收原始数据,以便它可以传递给适当的序列化器。
然后,你有使用数据实体或数据字段行插件的选择。它们不是从它们的渲染方法返回渲染数组,而是返回将被序列化为正确格式的原始数据。
使用行插件返回原始格式数据,以及通过样式插件序列化的数据,显示插件将返回通过序列化模块转换为正确格式的响应。
还有更多...
视图提供了一种提供特定 RESTful 端点的方式。我们将在下一个菜谱中探索一些额外的功能。
控制 JSON 输出中的键名
数据字段行插件允许你配置字段别名。当数据返回到视图时,它将具有 Drupal 的机器名称。这意味着自定义字段看起来可能像field_my_field,这可能对消费者来说没有意义。
点击“字段”旁边的设置,你可以在模态表单中设置别名:

当你提供一个别名时,字段将匹配。例如,user_picture可以改为avatar,邮件键可以改为email:
[{
"name": "veradabufrup",
"email": "veradabufrup@example.com",
"avatar": "\/sites\/default\/files\/pictures\/2017-07\/generateImage_HsEjKW.png"
}]
控制 RESTful 视图的访问
当你使用视图创建 RESTful 端点时,你不会使用 RESTful Web 服务模块创建的相同权限。你需要在视图中定义路由权限,这样你可以指定特定的角色或权限来请求。
EntityResource插件提供的默认 GET 方法不提供列出实体的方式,并允许通过 ID 检索任何实体。使用视图,你可以提供一个实体列表,限制它们到特定的捆绑包。
使用视图,你甚至可以提供一个新端点来检索特定实体。使用上下文过滤器,你可以添加路由参数和过滤器来限制和验证实体 ID。例如,你可能想通过 API 公开文章内容,但不公开页面。
认证
使用 RESTful Web 服务模块,我们为端点定义特定的支持认证提供者。Drupal 核心提供了一个 cookie 提供者,它通过有效的 cookie 进行认证,例如你的常规登录体验。然后,还有 HTTP 基本认证模块来支持 HTTP 认证头。
有提供更健壮认证方法的替代方案。使用基于 cookie 的认证,你需要使用 CSRF 令牌来防止未经授权的第三方加载未请求的页面。当你使用 HTTP 认证时,你是在请求头中发送每个请求的密码。
OAuth是一个流行且开放的授权框架。它是一种使用令牌而不是密码的正确认证方法。在本食谱中,我们将实现简单 OAuth 模块以提供 GET 和 POST 请求的 OAuth 2.0 认证。
准备工作
如果您不熟悉 OAuth 或 OAuth 2.0,它是一个授权标准。OAuth 的实现围绕在 HTTP 头中发送的令牌的使用。有关更多信息,请参阅 OAuth 主页oauth.net/。
通过遵循启用 RESTful 接口食谱,您应该已经使用 Composer 将REST UI模块添加到您的 Drupal 安装中。这可以通过以下命令完成:
cd /path/to/drupal8
composer require drupal/restui
如何操作
- 首先,我们必须将简单 OAuth 模块添加到我们的 Drupal 站点:
cd /path/to/drupal8
composer require drupal/simple_oauth
- 从管理工具栏转到扩展并安装以下网络服务模块:序列化、RESTful 网络服务、REST UI 和简单 OAuth:

-
前往配置,点击网络服务下的 REST 进行配置可用的端点。
-
点击内容行的启用按钮:

-
端点启用后,必须进行配置。勾选 GET 和 POST 方法复选框以允许 GET 和 POST 请求。然后,勾选 json 复选框以便数据可以以 JSON 格式返回。勾选 oauth2 复选框然后保存。
-
在我们配置简单 OAuth 模块之前,我们必须生成一对密钥来加密 OAuth 令牌。在 Drupal 可访问但通过 Web 服务器不可用的路径中生成这些密钥:
openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout > public.key
- 生成密钥后,转到配置页面,然后转到简单 OAuth。输入刚刚生成的私有和公共密钥的路径,然后点击保存配置:

-
从简单的 OAuth 配置表单中,点击添加客户端。为客户端提供一个标签并选择管理员范围。点击保存以创建客户端。
-
接下来,我们将通过
/oauth/token端点生成令牌。您需要您刚刚创建的客户端的 ID。我们必须传递grant_type、client_id、username和password。grant_type是password,client_id是创建的客户端的 ID,然后是您希望使用的账户的用户名和密码:
curl -X POST \
http://127.0.0.1:8888/oauth/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'grant_type=password&client_id=3ec55f70-18cd-422f-9abd-2223f6ca3636&username=admin&password=admin'
在撰写本书时,端点不接受 JSON 正文,而只接受表单编码的值。
-
响应将包含一个
access_token属性。这将在进行 API 请求时用作您的令牌。 -
使用
Authorization: Bearer [token]头请求 REST 中的节点:
curl -X GET \
'http://127.0.0.1:8888/node/1?_format=json' \
-H 'accept: application/json' \
-H 'authorization: Bearer JT9zgBgMEDlk2QIF0ecpZEOcsYC7-x649Bovo83HXQM'
工作原理
简单 OAuth 模块是使用League\OAuth2 PHP 库构建的,这是一个 OAuth2 实现的社区事实库。
在典型的身份验证请求中,有一个身份验证管理器,它使用 authentication_collector 服务来收集所有标记的身份验证提供者服务器。根据提供者设置的优先级,每个服务被调用以检查它是否适用于当前请求。然后,每个适用的身份验证提供者被调用以查看身份验证是否无效。
对于 RESTful 网络服务模块,过程更为明确。在端点的 supported_auth 定义中标识的提供者是唯一经过 applies 和 authenticates 过程的服务。
还有更多...
我们将在下一节中探索更多关于与身份验证提供者和 RESTful 网络服务模块一起工作的信息。
身份验证提供者服务
当与 RESTful 网络服务模块端点一起工作时,supported_auth 值引用了带有 authentication_provider 标签的服务。默认情况下,Drupal 支持 cookie 身份验证。以下代码由 basic_auth 模块提供,以支持 HTTP 头部身份验证:
services:
basic_auth.authentication.basic_auth:
class: Drupal\basic_auth\Authentication\Provider\BasicAuth
arguments: ['@config.factory', '@user.auth', '@flood', '@entity.manager']
tags:
- { name: authentication_provider, provider_id: 'basic_auth', priority: 100 }
可以通过在模块的 Authentication\Provider 命名空间中创建一个类并实现 \Drupal\Core\Authentication\AuthenticationProviderInterface 接口来创建身份验证提供者。然后,将类作为服务注册到模块的 services.yml 文件中。
页面缓存请求策略和已验证的 Web 服务请求
当处理期望有已验证用户的数据时,身份验证服务提供者还应提供页面缓存服务处理程序。标记为 page_cache_request_policy 的服务有权检查内容是否已缓存。这防止了授权请求被缓存。
以下代码摘自 basic_auth 模块:
basic_auth.page_cache_request_policy.disallow_basic_auth_requests:
class: Drupal\basic_auth\PageCache\DisallowBasicAuthRequests
public: false
tags:
- { name: page_cache_request_policy }
\Drupal\basic_auth\PageCache\DisallowBasicAuthRequests 类实现了 \Drupal\Core\PageCache\RequestPolicyInterface 接口。检查方法允许页面缓存策略明确拒绝或保持中立,以确定页面是否可以缓存。basic_auth 模块检查默认身份验证头是否存在。对于 simple_oauth 模块,它检查是否存在有效的令牌。
如果您正在实现自己的身份验证服务,这是一个重要的安全措施。
可以通过在模块的 PageCache 命名空间中创建一个类并实现 \Drupal\Core\PageCache\ResponsePolicyInterface 接口来实现页面缓存策略服务。然后,我们需要将类作为服务注册到模块的 services.yml 文件中。
IP 身份验证提供者
一些实现服务器到服务器通信的 API 将使用 IP 地址白名单进行身份验证。对于此用例,我们有 IP 消费者身份验证模块。白名单 IP 地址由一个保存配置值的表单控制。
如果 IP 地址被列入白名单,用户将被认证为匿名用户。虽然这可能在 POST、PATCH 和 DELETE 请求中不被推荐,但它可以提供一个简单的方法来控制私有网络中的特定 GET 端点。
您可以从其项目页面下载 IP 消费者身份验证:www.drupal.org/project/ip_consumer_auth。
相关内容
-
请参考 OAuth 社区网站:
oauth.net/ -
请参考支持 OAuth 1.0 的 OAuth 模块:
www.drupal.org/project/oauth -
请参考支持 OAuth 2.0 的简单 OAuth 模块:
www.drupal.org/project/simple_oauth -
请参考 IP 消费者身份验证模块:
www.drupal.org/project/ip_consumer_auth
使用 JSON API
在为前端消费者开发后端 API 时,关于命名约定和返回值结构经常有许多争议。这时出现了 {json:api},这是一个开源规范,旨在标准化和简化 API 的构建,这些 API 消费和返回 JSON 有效负载。规范和文档可以在 jsonapi.org/ 找到。
对于 Drupal,有一个由社区主导的努力,旨在提供强大的 JSON API 规范实现,将 Drupal 转换为一个高效的 API 服务器。本食谱将安装 JSON API 模块并展示如何启用资源。
就像 Drupal 核心提供的 RESTful Web 服务模块一样,JSON API 模块不提供用户界面。它还自动使所有内容可通过 API 获取(假设用户已配置权限以访问端点。)JSON API Extra 模块改变了这一点,这将在本食谱的 还有更多... 部分中介绍。
JSON API 模块可以在 www.drupal.org/project/jsonapi 找到。
准备工作
使用标准 Drupal 安装提供的 Article 内容类型创建示例内容。这将使测试 GET 方法变得容易得多。
在进行请求时,所有端点路径都以前缀 jsonapi 开头。
如何操作
- 首先,我们必须将 JSON API 模块添加到我们的 Drupal 网站中:
cd /path/to/drupal8
composer require drupal/jsonapi
- 安装 JSON API 和序列化模块。一旦模块安装完成,API 端点将处于活动状态:

-
执行请求时,必须传递一个值为
application/vnd.api+json的 Accept 头部。 -
对于 JSON API 规范,每个资源都必须有一个唯一的类型名称,JSON API 从实体类型及其包中推导出此名称。检索文章节点的端点将是:
http://127.0.0.1:8888/jsonapi/node/article
- 整个请求可以使用以下命令执行:
curl -X GET \
http://127.0.0.1:8888/jsonapi/node/article \
-H 'accept: application/vnd.api+json'
- 响应将类似于以下内容。内容值将在属性属性中:
{
"data": [
{
"type": "node--article",
"id": "c897acba-eb81-454a-94ed-13107fd205cf",
"attributes": {...},
"relationships": {...},
"links": {
"self": "http://127.0.0.1:8888/jsonapi/node/article/c897acba-eb81-454a-94ed-13107fd205cf"
}
}
],
"links": {
"self": "http://127.0.0.1:8888/jsonapi/node/article",
}
}
它是如何工作的...
JSON API 模块实现了 {json:api} 规范。类似于 Drupal 核心提供的 RESTful Web 服务模块,它通过各种端点公开数据。它建立在 Drupal 现有的路由系统之上,以处理非 HTML 格式。主要区别在于它遵循一个社区驱动的规范,该规范决定了数据应该如何格式化、链接、过滤、排序等。
还有更多...
接下来,我们将介绍过滤、分页、排序和 JSON API Extras 模块。
分页、过滤和排序请求
菜谱中的请求将返回系统中所有可用的文章节点。这些可以分页、过滤和排序。这些操作都是通过查询参数完成的,其中包含一个值数组。
分页是通过附加一个 page 查询参数来完成的。要限制请求为 10 个节点,我们会在后面附加 ?page[limit]=10。要访问下一组结果,我们也会传递 page[offset]=10。
以下是一个返回结果的第一页和第二页的示例:
http://127.0.0.1:8888/jsonapi/node/article?page[limit]=10
http://127.0.0.1:8888/jsonapi/node/article?page[offset]=10&page[limit]=10
每个请求都包含一个链接属性;当使用分页结果时,它也将包含下一页和上一页的链接。
通过附加一个 filter 查询参数来进行过滤。以下是一个请求所有被提升到首页的节点的示例:
http://127.0.0.1:8888/jsonapi/node/article?filter[promoted][path]=promote&filter[promoted][value]=1&filter[promoted][operator]==
每个过滤器都由一个名称定义--在前面的示例中,它是 promoted。然后过滤器接受 path,这是要过滤的字段。value 和 operator 决定了如何过滤。
排序是最简单的操作。添加一个排序查询参数。字段名称值是要排序的字段,如果要按降序排序,则在字段名称前添加一个分钟符号。以下示例分别展示了如何按 nid 升序和降序排序:
http://127.0.0.1:8888/jsonapi/node/article?sort=nid
http://127.0.0.1:8888/jsonapi/node/article?sort=-nid
安装 JSON API Extras 模块
JSON API Extras 模块提供了一个用户界面进行额外的自定义。应像所有其他模块一样,使用 Composer 将 JSON API Extras 模块添加到您的 Drupal 安装中:
cd /path/to/drupal8
composer require drupal/jsonapi_extras
一旦在 Drupal 中安装了该模块,您将能够启用或禁用端点、更改资源名称、更改资源路径、禁用字段、别名字段名称以及增强字段输出。
更改 API 路径前缀
使用 extras 模块,可以将 API 路径前缀从 jsonapi 更改为 api 或任何其他前缀。
从管理工具栏导航到配置。在 Web 服务部分下,单击 JSON API Overwrites 以自定义 JSON API 实现。设置选项卡允许修改 API 路径前缀:

禁用和增强返回的实体字段
JSON API Extras 模块允许覆盖 JSON API 模块自动公开的端点。这允许禁用返回字段。它还允许使用增强器来简化字段属性的架构。
从管理工具栏中,转到配置。在 Web 服务部分下,点击 JSON API 覆盖以自定义 JSON API 实现。
要禁用端点,请点击任何端点的“覆盖”。勾选“禁用”复选框以关闭该特定端点:

要禁用、别名或使用增强器,请点击任何端点的“覆盖”。复选框将允许您防止字段在 API 中使用。增强器允许您在返回或用于 POST/PATCH 请求时简化字段:

在本例中,created 和 changed 字段将不再返回 Unix 时间戳,而是返回 RFC ISO8601 格式的时间戳。promote 和 sticky 字段将直接返回它们的值,而不是嵌套在 value 属性下。最后,将不会返回任何修订信息字段。
Contenta CMS
Contenta CMS 是一个解耦的、基于 API 的 Drupal 分发,使用 JSON API 构建。该项目是通过推动 JSON API 模块向前发展的同一社区倡议构建的。项目主页可在 www.contentacms.org/ 找到。
它提供了许多预配置选项,包括对默认端点的自定义。它还提供了简单的 OAuth,以设置与前端消费者和 API 后端的解耦身份验证。
在提供分发的同时,社区贡献者还开发了各种前端消费者作为示例:
-
Vue/Nuxt:
github.com/contentacms/contenta_vue_nuxt -
Ember.js:
github.com/contentacms/contenta_ember
参考以下内容
-
JSON API 项目页面位于
www.drupal.org/project/jsonapi -
JSON API Extras 页面位于
www.drupal.org/project/jsonapi_extras -
JSON API 模块文档位于
www.drupal.org/docs/8/modules/json-api/json-api -
JSON API 模块视频教程位于
www.youtube.com/playlist?list=PLZOQ_ZMpYrZsyO-3IstImK1okrpfAjuMZ -
{json:api}规范文档位于jsonapi.org/
第十三章:Drupal CLI
Drupal 8 有两个命令行工具:Drupal Console 和 Drush。在本章中,我们将通过以下菜谱来讨论它们如何通过简化与 Drupal 的工作来使工作变得更轻松:
-
在 Drupal Console 或 Drush 中重建缓存
-
使用 Drush 与数据库交互
-
使用 Drupal Console 调试 Drupal
-
通过 Drupal Console 生成代码框架
-
创建 Drush 命令
-
创建 Drupal Console 命令
简介
在本书的前几章中,有一些菜谱提供了使用命令行工具简化 Drupal 工作的方法。有两个贡献项目为 Drupal 提供了命令行界面体验。
首先,是 Drush。Drush 首次为 Drupal 4.7 创建,并已成为用于日常 Drupal 操作的必备工具。然而,随着 Drupal 8 及其与 Symfony 的集成,出现了 Drupal Console。Drupal Console 是基于 Symfony Console 的应用程序,这使得它能够重用更多组件,并更容易与贡献模块集成。
本章包含了一些菜谱,将突出使用 Drush 或 Console 可以简化的操作。在本章结束时,您将能够通过命令行与您的 Drupal 网站进行交互。
在撰写本书时,Drush 仍然是 Drupal 8 的首选主要工具;然而,Drupal Console 正在赢得更多的市场份额。Drupal Console 正在快速发展。由于这种快速发展,命令将仍然存在,但输出可能不同。
Drush 和 Drupal Console 都支持全局安装,但这两个项目都在迁移到使用 Composer 的按项目安装。要开始,请参考以下每个工具的安装指南以获取最新的安装信息:
-
Drupal Console:
docs.drupalconsole.com/en/getting/project.html
在 Drupal Console 或 Drush 中重建缓存
Drupal 利用缓存来存储插件定义、路由等。当您添加新的插件定义或新的路由时,您需要重建 Drupal 的缓存,以便它被识别。
通过命令行重建缓存比使用用户界面更高效,因为它不会使用 web 服务器资源来执行缓存重建。
在这个菜谱中,我们将向您展示如何使用 Drush 和 Drupal Console 清除 Drupal 中的各种缓存区。了解如何清除特定的缓存区非常重要,这样在可能的情况下,您就不需要重建一切。
如何操作...
-
打开终端并导航到已安装的 Drupal 目录。
-
我们在 Drush 中使用
cache-rebuild命令来重建 Drupal 的所有缓存,包括路由:
$ drush cache-rebuild
Cache rebuild complete.
- 在 Drupal Console 中,我们使用
cache:rebuild命令来清除特定的缓存区。输入使用自动完成来帮助指定要清除的特定缓存区。在这个例子中,我们清除渲染缓存:
$ drupal cache:rebuild all
Select cache. [all]:
> render
Rebuilding cache(s), wait a moment please.
[OK] Done clearing cache(s).
- 如果我们只需要在 Drupal 中重建我们的路由,我们可以在控制台中使用
router:rebuild命令。这将保持渲染、发现和其他缓存,但会暴露新的路由:
$ drupal router:rebuild
Rebuilding routes, wait a moment please
[OK] Done rebuilding route(s).
- Drush 提供了
twig-compile来重建模板。传递详细选项将显示正在编译的模板:
$ drush twig-compile --verbose
它是如何工作的…
Drush 和 Drupal Console 都会从 Drupal 安装中加载文件并引导应用程序。这使得命令可以调用 Drupal 中找到的函数和方法。
对于 Drush 8.x,Drush 没有实现依赖注入容器,仍然需要依赖于 Drupal 的过程函数。
然而,Drupal Console 利用依赖注入容器,允许它重用 Drupal 的容器和服务。
创建 Drush 命令 和 创建 Drupal Console 命令 的配方将更详细地描述这些差异。
参见
-
Drush 文档位于
docs.drush.org/en/master/ -
Drupal Console 文档位于
docs.drupalconsole.com/ -
Drush 命令速查表位于
drushcommands.com/ -
Drupal Console 命令速查表位于
drupalconsole.com/cheatsheet/
使用 Drush 与数据库交互
当与任何使用数据库的应用程序一起工作时,有时您需要导出数据库并将其导入其他地方。通常,您会这样做以将生产站点上的内容本地化。这样,您可以创建一个可以导出并推送到生产的配置,如在第 第九章 中讨论的,配置管理 – 在 Drupal 8 中部署。
在这个配方中,我们将从生产站点导出数据库转储以设置本地开发。数据库转储将通过命令行导入并清理。然后,我们将通过 Drush 执行 SQL 查询以验证清理。
准备工作
Drush 有能力使用站点别名。站点别名是配置项,允许您与远程 Drupal 站点交互。在这个配方中,我们将使用以下别名与一个虚构的远程站点交互,以展示典型的流程是如何获取远程数据库的。
注意,您不需要使用 Drush 别名来下载配方中创建的数据库转储;您可以使用您熟悉的方法(手动从命令行使用 mysqldump 或 phpMyAdmin):
$aliases['drupal.production'] = [
'uri' => 'example.com',
'remote-host' => 'example.com',
'remote-user' => 'someuser',
'ssh-options' => '-p 2222',
];
有关站点别名的更多信息,请参阅 Drush 文档 docs.drush.org/en/master/usage/#site-aliases。站点别名允许您与远程 Drupal 安装交互。
我们还假设本地开发站点尚未配置以连接到数据库。
如何操作...
- 我们将使用
sql-dump命令将数据库导出到文件。该命令返回需要重定向到文件的输出:
$ drush @drupal.production sql-dump > ~/prod-dump.sql
-
导航到你的本地 Drupal 网站目录,并将
sites/default/default.settings.php复制到sites/default/settings.php。 -
编辑新的
settings.php文件,并在文件末尾添加一个数据库配置数组;这将用于 Drupal 的数据库:
// Database configuration.
$databases['default']['default'] = [
'driver' => 'mysql',
'host' => 'localhost',
'username' => 'mysql',
'password' => 'mysql',
'database' => 'data',
'prefix' => '',
'port' => 3306,
'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
];
- 使用
sql-cli命令,我们可以导入我们创建的数据库导出:
$ drush sql-cli < ~/prod-dump.sql
sql-sanitize命令允许你在数据库中混淆用户电子邮件和密码:
$ drush sql-sanitize
- 为了验证我们的信息已导入并清理,我们将使用
sql-query命令对数据库运行查询:
$ drush sql-query "SELECT uid, name, mail FROM users_field_data;"
它是如何工作的...
当使用 Drush 时,我们有使用 Drush 别名的功能。Drush 别名包含一个配置,允许工具连接到远程服务器并与该服务器的 Drush 安装交互。
为了使用站点别名,你需要在远程服务器上安装 Drush。
sql-dump 命令执行数据库驱动程序的适当导出命令,通常是 MySQL 和 mysqldump 命令。它流到终端,必须管道到目的地。当管道到本地 SQL 文件时,我们可以导入它并执行创建命令以导入我们的数据库模式和数据。
sql-dump 命令支持 --result-file 选项;然而,它会将文件保存到 Drupal 安装的相关位置。
使用 sql-cli 命令,我们将能够通过 Drush 执行数据库的 SQL 命令。这允许我们将文件内容重定向到 sql-cli 命令并运行 SQL 命令集。数据导入后,sql-sanitize 命令将替换用户名和密码。
最后,sql-query 命令允许我们直接将 SQL 命令传递到数据库并返回其结果。在我们的配方中,我们将查询 users_field_data 以验证我们已导入用户并且电子邮件已被清理。
更多内容...
通过命令行与 Drupal 交互简化了数据库操作。我们将在以下章节中更详细地探讨这一点。
使用 gzip 与 sql-dump
有时,数据库可以相当大。sql-dump 命令有一个 gzip 选项,它将使用 gzip 命令输出 SQL 导出。为了运行该命令,你只需:
$ drush sql-dump –-gzip dump.sql.gz
最终结果减少了导出文件的大小:
-rw-r--r-- 1 user group 3058522 Jan 14 16:10 dump.sql
-rw-r--r-- 1 user group 285880 Jan 14 16:10 dump.sql.gz
如果你创建了一个压缩的数据库导出,确保在尝试使用 sql-cli 命令导入之前解压缩它。
使用控制台与数据库交互
在撰写本书时,控制台不提供清理数据库的命令。该功能目前记录在这个问题中;请参阅 github.com/hechoendrupal/drupal-console/issues/3192。
database:connect和database:client命令将启动数据库客户端。这允许你登录到数据库的命令行界面:
$ drupal database:client
$ drupal database:connect
这些命令类似于 Drush 的sql-cli和sql-connect命令。客户端命令将带你去数据库的命令行工具,其中 connect 显示连接字符串。
Drupal Console 还提供了database:dump命令。与 Drush 不同,这将为你将数据库转储写入 Drupal 目录:
$ drupal database:dump
[OK] Database exported to: /path/to/drupal/www/data.sql
使用 Drush 管理用户
当你需要将账户添加到 Drupal 时,你将访问“人员”页面并手动添加新用户。Drush 为 Drupal 提供完整用户管理,从创建到角色分配、密码恢复和删除。此工作流程允许你轻松创建用户,并为他们提供登录,无需进入你的 Drupal 网站。
在这个菜谱中,我们将创建一个带有staffmember用户的staff角色,并通过 Drush 以该用户身份登录。
如何操作...
- 使用
role-create命令创建一个标记为staff的新角色:
$ drush role-create staff
Created "staff"
- 使用
role-lists命令验证角色是否已在 Drupal 中创建:
$ drush role-list
ID Role Label
anonymous Anonymous user
authenticated Authenticated user
administrator Administrator
staff Staff
user-create命令将创建我们的用户:
$ drush user-create staffmember
User ID : 2
User name : staffmember
User roles : authenticated
User status : 1
- 为了添加角色,我们需要使用
user-add-role命令:
$ drush user-add-role staff staffmember
Added role staff role to staffmember
- 我们现在将使用
user-login命令以staffmember用户身份登录:
$ drush user-login staffmember --uri=http://example.com
http://example.com/user/reset/2/1452810532/Ia1nJvbr2UQ3Pi_QnmITlVgcCWzDtnKmHxf-I2eAqPE
- 复制链接并将其粘贴到浏览器中以登录该用户:

它是如何工作的...
当你在 Drupal 中重置密码时,将生成一个特殊的单次登录链接。登录链接基于生成的哈希值。Drush 命令验证存在于 Drupal 网站上的给定用户,然后将它传递给用户模块的user_pass_reset_url函数。
URL 由用户的 ID、生成链接时的时间戳以及基于用户最后登录时间、链接生成和电子邮件的哈希值组成。当链接加载时,此哈希值将被重建并验证。例如,如果用户在生成链接之后登录,链接将变得无效。
当在已安装网络浏览器的机器上使用时,Drush 将尝试在网页浏览器中为你打开链接。浏览器选项允许你指定应启动哪个浏览器。此外,你可以使用 no-browser 来防止浏览器启动。
更多内容...
命令行提供了简化用户管理和用户管理的能力。接下来,我们将更详细地探讨这个主题。
高级用户登录用例
user-login命令是一个有用的工具,允许一些高级用例。例如,你可以在用户名后附加一个路径并跳转到该路径。你可以传递 UID 或电子邮件而不是用户名来以用户身份登录。
您可以使用user-login来保护您的管理员用户账户。在 Drupal 中,标识为 1 的用户被视为根用户,可以绕过所有权限。很多时候,这是默认的维护账户,用于在 Drupal 站点上工作。您不必手动登录,可以将账户设置为非常健壮的密码短语,并在需要访问您的站点时使用user-login命令。这样,唯一能够以管理员账户登录的用户是那些可以访问在网站上运行 Drush 命令的用户。
使用 Drupal Console
Drupal Console 还提供与用户交互的命令。尽管它们不允许创建用户或角色,但它们提供基本的用户管理。
user:login:url 命令将为指定的用户 ID 生成一个一次性登录链接。这使用与 Drush 命令相同的方法:
$ drupal user:login:url 2
user:password:reset 命令允许您将用户的密码重置为新提供的密码。您可以提供用户 ID 和新密码作为参数,但如果缺失,将交互式地提示输入:
$ drupal user:password:reset 2 newpassword
create:users 命令提供了一种交互式生成大量用户的方法,这对于调试很有用。然而,它不能像 Drush 那样创建具有特定密码的单独用户:
通过 Console 生成代码框架
当 Drupal Console 首次推出时,最大的亮点之一是其生成代码的能力。该项目已经变成一个更大的 Drupal 运行器,通过命令行界面,但其中大部分的机智在于代码生成。
如您在前几章和食谱中可能已经注意到的,可能会有一些日常任务和一些样板代码。Drupal Console 使 Drupal 开发者能够创建各种组件,而无需编写所有样板代码。
在第十章《实体 API》中,我们介绍了自定义实体类型的创建。在这个食谱中,我们将使用 Drupal Console 自动化大部分过程,以生成我们的内容实体。
准备工作
对于这个食谱,您需要安装 Drupal Console。该工具将为我们生成其他所有内容。您需要安装一个 Drupal 8 站点。许多 Console 的命令将无法工作(或列出),除非它们可以访问已安装的 Drupal 站点。这是因为它与 Drupal 的服务容器交互的方式。
如何做到这一点...
- 从您的 Drupal 站点的根目录开始,使用
generate:module命令生成一个模块,并遵循交互式过程。使用提示的默认值,以及给它一个模块名称:
$ drupal generate:module
// Welcome to the Drupal module generator
Enter the new module name:
> Content Entity Provider
Enter the module machine name [content_entity_provider]:
>
Enter the module Path [/modules/custom]:
>
Enter module description [My Awesome Module]:
>
Enter package name [Custom]:
>
Enter Drupal Core version [8.x]:
>
Do you want to generate a .module file (yes/no) [yes]:
>
Define module as feature (yes/no) [no]:
>
Do you want to add a composer.json file to your module (yes/no) [yes]:
>
Would you like to add module dependencies (yes/no) [no]:
>
Do you want to generate a unit test class (yes/no) [yes]:
>
Do you want to generate a themeable template (yes/no) [yes]:
>
Do you confirm generation? (yes/no) [yes]:
>
Generated or updated files
1 - modules/custom/content_entity_provider/content_entity_provider.info.yml
2 - modules/custom/content_entity_provider/content_entity_provider.module
3 - modules/custom/content_entity_provider/composer.json
4 - modules/custom/content_entity_provider/tests/src/Functional/LoadTest.php
5 - modules/custom/content_entity_provider/content_entity_provider.module
6 - modules/custom/content_entity_provider/templates/content-entity-provider.html.twig
- 接下来,我们将生成我们的
content实体。指定将提供实体的模块名称:
$ drupal generate:entity:content
Enter the module name [devel]:
> content_entity_provider
Enter the class of your new content entity [DefaultEntity]:
> CustomContentEntity
Enter the machine name of your new content entity [custom_content_entity]:
>
Enter the label of your new content entity [Custom content entity]:
>
Enter the base-path for the content entity routes [/admin/structure]:
>
Do you want this (content) entity to have bundles (yes/no) [no]:
>
Is your entity translatable (yes/no) [yes]:
>
Is your entity revisionable (yes/no) [yes]:
>
Generated or updated files
1 - modules/custom/content_entity_provider/content_entity_provider.permissions.yml
2 - modules/custom/content_entity_provider/content_entity_provider.links.menu.yml
3 - modules/custom/content_entity_provider/content_entity_provider.links.task.yml
4 - modules/custom/content_entity_provider/content_entity_provider.links.action.yml
5 - modules/custom/content_entity_provider/src/CustomContentEntityAccessControlHandler.php
6 - modules/custom/content_entity_provider/src/CustomContentEntityTranslationHandler.php
7 - modules/custom/content_entity_provider/src/Entity/CustomContentEntityInterface.php
8 - modules/custom/content_entity_provider/src/Entity/CustomContentEntity.php
9 - modules/custom/content_entity_provider/src/CustomContentEntityHtmlRouteProvider.php
10 - modules/custom/content_entity_provider/src/Entity/CustomContentEntityViewsData.php
11 - modules/custom/content_entity_provider/src/CustomContentEntityListBuilder.php
12 - modules/custom/content_entity_provider/src/Form/CustomContentEntitySettingsForm.php
13 - modules/custom/content_entity_provider/src/Form/CustomContentEntityForm.php
14 - modules/custom/content_entity_provider/src/Form/CustomContentEntityDeleteForm.php
15 - modules/custom/content_entity_provider/custom_content_entity.page.inc
16 - modules/custom/content_entity_provider/templates/custom_content_entity.html.twig
17 - modules/custom/content_entity_provider/src/Form/CustomContentEntityRevisionDeleteForm.php
18 - modules/custom/content_entity_provider/src/Form/CustomContentEntityRevisionRevertTranslationForm.php
19 - modules/custom/content_entity_provider/src/Form/CustomContentEntityRevisionRevertForm.php
20 - modules/custom/content_entity_provider/src/CustomContentEntityStorage.php
21 - modules/custom/content_entity_provider/src/CustomContentEntityStorageInterface.php
22 - modules/custom/content_entity_provider/src/Controller/CustomContentEntityController.php
- 使用 Drupal Console 安装您的模块:
$ drupal module:install content_entity_provider
Installing module(s) "content_entity_provider"
[OK] The following module(s) were installed successfully: "content_entity_provider"
// cache:rebuild
Rebuilding cache(s), wait a moment please.
[OK] Done clearing cache(s).
- 查看结构并找到您的自定义内容实体设置:

它是如何工作的...
控制台最大的特点之一是它能够减少开发者编写 Drupal 8 代码所需的时间。控制台利用 Twig 模板引擎提供代码生成。这些 Twig 模板包含变量和逻辑,它们被编译成最终结果代码。
一组生成器类接收特定的参数,这些参数通过适当的命令接收,并将它们传递给 Twig 进行渲染。这使得控制台能够轻松地与 Drupal 核心的变化保持同步,同时仍然提供有价值的代码生成。
创建 Drush 命令
Drush 提供了一个 API,允许开发者编写他们自己的命令。这些命令可以是模块的一部分,通过 Drupal 安装加载,或者它们可以被放置在本地用户的 Drush 文件夹中用于一般目的。
通常,贡献的模块会创建命令来自动化用户界面操作。然而,为特定操作创建自定义 Drush 命令可能很有用。在这个菜谱中,我们将创建一个命令,该命令加载在过去 10 天内未登录的所有用户并重置他们的密码。
准备工作
对于这个菜谱,你需要 Drush 可用。我们将在本地用户目录中创建一个命令。
如何操作...
- 在你的用户
~/.drush文件夹中创建一个名为disable_users.drush.inc的文件:
<?php
/**
* @file
* Loads all users who have not logged in within 10 days and disables them.
*/
- 添加 Drush 命令钩子,该钩子将允许 Drush 发现我们通过文件提供的命令:
/**
* Implements hook_drush_command().
*/
function disable_users_drush_command() {
$items = [];
$items['disable-users'] = [
'description' => 'Disables users after 10 days of inactivity',
];
return $items;
}
- 接下来,我们将创建命令回调函数,它将包含所有我们的逻辑。由于我们的文件名是
disable_users.drush.inc,我们的命令是disable-users,所以钩子最终变成了drush_disable_users_disable_users:
/**
* Implements drush_hook_COMMAND().
*/
function drush_disable_users_disable_users() {
}
- 更新函数以创建一个表示 10 天前的
DateTime对象。我们将使用它来生成查询的时间戳:
/**
* Implements drush_hook_COMMAND().
*/
function drush_disable_users_disable_users() {
// Get the default timezone and make a DateTime object for 10 days ago.
$system_date = \Drupal::config('system.date');
$default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
$now = new DateTime('now', new DateTimeZone($default_timezone));
$now->modify('-10 days');
}
- 现在,我们将添加我们的查询,该查询将查询所有登录时间戳超过 10 天的用户实体:
/**
* Implements drush_hook_COMMAND().
*/
function drush_disable_users_disable_users() {
// Get the default timezone and make a DateTime object for 10 days ago.
$system_date = \Drupal::config('system.date');
$default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
$now = new DateTime('now', new DateTimeZone($default_timezone));
$now->modify('-10 days');
$query = \Drupal::entityQuery('user')->condition('login', $now->getTimestamp(), '>');
$results = $query->execute();
if (empty($results)) {
drush_print('No users to disable!');
}
}
- 接下来,我们将遍历结果并标记用户为禁用:
/**
* Implements drush_hook_COMMAND().
*/
function drush_disable_users_disable_users() {
// Get the default timezone and make a DateTime object for 10 days ago.
$system_date = \Drupal::config('system.date');
$default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
$now = new DateTime('now', new DateTimeZone($default_timezone));
$now->modify('-10 days');
$query = \Drupal::entityQuery('user')->condition('login', $now->getTimestamp(), '>');
$results = $query->execute();
if (empty($results)) {
drush_print('No users to disable!');
}
foreach ($results as $uid) {
/** @var \Drupal\user\Entity\User $user */
$user = \Drupal\user\Entity\User::load($uid);
$user->block();
$user->save();
}
drush_print(dt('Disabled !count users', ['!count' => count($results)]));
}
- 为了发现你的新命令,Drush 的缓存需要被清除:
$ drush cache-clear drush
- 检查命令是否存在:
$ drush disable-users --help
Disables users after 10 days of inactivity
它是如何工作的...
Drush 通过扫描特定目录以查找遵循COMMANDFILE.drush.inc模式的文件来工作。你可以将 Drush 中的COMMANDFILE视为 Drupal 钩子系统中模块名称的表示。在实现 Drush 钩子时,以HOOK_drush格式,你需要将 HOOK 替换为你的COMMANDFILE名称,就像你在 Drupal 中使用模块名称一样。
在这个菜谱中,我们创建了一个disable_users.drush.inc文件。这意味着文件中的所有钩子和命令都需要使用disable_users进行钩子调用。Drush 使用此来加载返回我们命令信息的hook_drush_command钩子。
我们然后在 drush_hook_command 钩子中提供了我们逻辑的功能。对于这个钩子,我们将钩子替换为我们的 COMMANDFILE 名称,即 disable_users,得到 drush_disable_users_command。我们将命令替换为我们在 hook_drush_command 中定义的命令,即 disable-users。然后我们有了最终的 drush_disable_users_disable_users 钩子。
更多内容…
Drush 命令在其定义中可以指定额外的选项。我们探讨了它们控制命令所需 Drupal 集成级别的功能。
指定 Drupal 的引导级别
Drush 命令在执行前可以指定 Drupal 引导的级别。Drupal 有多个引导级别,其中只有系统的特定部分被加载。默认情况下,命令的引导级别为 DRUSH_BOOTSTRAP_DRUPAL_LOGIN,这与通过 Web 访问 Drupal 的级别相同。
命令根据其目的可以选择完全不引导 Drupal 或仅引导到数据库系统加载。例如,Git 发布说明模块这样的实用程序 Drush 命令不与 Drupal 交互。它指定了一个引导级别为 DRUSH_BOOTSTRAP_DRUSH,因为它只与存储库交互,根据 Git 标签和提交生成变更日志。
相关链接
-
参考如何创建自定义 Drush 命令,请访问
docs.drush.org/en/master/commands/。 -
参考如何安装 Drush,请访问
docs.drush.org/en/master/install/。 -
参考 Drush 引导过程,请访问
docs.drush.org/en/master/bootstrap/。
创建控制台命令
Drupal Console 利用了 Symfony Console 项目和其他第三方库,以利用现代 PHP 的最佳实践。在这个过程中,它遵循 Drupal 8 的实践。这使得 Console 能够通过读取其类加载器来使用命名空间进行命令检测和与 Drupal 的交互。
这允许开发者通过在模块中实现自定义类来轻松创建控制台命令。
在这个菜谱中,我们将创建一个命令,该命令加载过去 10 天内未登录的所有用户并重置他们的密码。我们将使用脚手架命令生成我们命令的基础。
准备工作
对于这个菜谱,你需要安装 Drupal Console。该工具将为我们生成其他所有内容。你需要安装一个 Drupal 8 站点。
如何操作…
-
创建一个新的模块来存放你的 Drupal Console 命令,例如
console_commands:
drupal generate:module
// Welcome to the Drupal module generator
Enter the new module name:
> Console Commands
Enter the module machine name [console_commands]:
>
Enter the module Path [/modules/custom]:
>
Enter module description [My Awesome Module]:
>
Enter package name [Custom]:
>
Enter Drupal Core version [8.x]:
>
Do you want to generate a .module file (yes/no) [yes]:
>
Define module as feature (yes/no) [no]:
>
Do you want to add a composer.json file to your module (yes/no) [yes]:
>
Would you like to add module dependencies (yes/no) [no]:
>
Do you want to generate a unit test class (yes/no) [yes]:
>
Do you want to generate a themeable template (yes/no) [yes]:
>
Do you confirm generation? (yes/no) [yes]:
>
Generated or updated files
1 - modules/custom/console_commands/console_commands.info.yml
2 - modules/custom/console_commands/console_commands.module
3 - modules/custom/console_commands/composer.json
4 modules/custom/console_commands/tests/src/Functional/LoadTest.php
5 - modules/custom/console_commands/console_commands.module
6 - modules/custom/console_commands/templates/console-commands.html.twig
- 接下来,我们将使用
generate:command命令生成命令的基本文件。将其命名为disable_users命令:
drupal generate:command
// Welcome to the Drupal Command generator
Enter the extension name [devel]:
> console_commands
Enter the Command name. [console_commands:default]:
> console_commands:disable_users
Enter the Command Class. (Must end with the word 'Command'). [DefaultCommand]:
> DisableUsersCommand
Is the command aware of the drupal site installation when executed?. (yes/no) [no]:
> yes
Do you confirm generation? (yes/no) [yes]:
>
Generated or updated files
1 - modules/custom/console_commands/src/Command/DisableUsersCommand.php
2 - modules/custom/console_commands/console.services.yml
3 - modules/custom/console_commands/console/translations/en/console_commands.disable_users.yml
- 编辑创建的
DisableUsersCommand.php文件,并从执行方法中删除样板示例代码:
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
}
- 更新函数以创建一个表示 10 天前的
DateTime对象。我们将使用它来生成查询的时间戳:
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
// Get the default timezone and make a DateTime object for 10 days ago.
$system_date = \Drupal::config('system.date');
$default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
$now = new \DateTime('now', new \DateTimeZone($default_timezone));
$now->modify('-10 days');
}
- 现在,我们将添加我们的查询,该查询将查询所有登录时间戳大于 10 天的用户实体:
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
// Get the default timezone and make a DateTime object for 10 days ago.
$system_date = \Drupal::config('system.date');
$default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
$now = new \DateTime('now', new \DateTimeZone($default_timezone));
$now->modify('-10 days');
$query = \Drupal::entityQuery('user')->condition('login', $now->getTimestamp(), '>');
$results = $query->execute();
if (empty($results)) {
$output->writeln('<info>No users to disable!</info>'); }
}
- 接下来,我们将遍历结果并将用户标记为禁用:
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
// Get the default timezone and make a DateTime object for 10 days ago.
$system_date = \Drupal::config('system.date');
$default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
$now = new \DateTime('now', new \DateTimeZone($default_timezone));
$now->modify('-10 days');
$query = \Drupal::entityQuery('user')->condition('login', $now->getTimestamp(), '>');
$results = $query->execute();
if (empty($results)) {
$output->writeln('<info>No users to disable!</info>');
}
foreach ($results as $uid) {
/** @var \Drupal\user\Entity\User $user */
$user = \Drupal\user\Entity\User::load($uid);
$user->block();
$user->save();
}
$total = count($results);
$output->writeln("Disabled $total users");
}
- 启用模块以访问以下命令:
$ drupal module:install console_commands
它是如何工作的...
Drupal Console 通过使用命名空间发现方法与模块集成。当控制台在 Drupal 安装中运行时,它将发现所有可用的项目。然后,它将发现\Drupal\{ a module }\Command命名空间中的任何文件,这些文件实现了\Drupal\Console\Command\Command接口。
每次调用 Drupal Console 时,它都会重新扫描 Drupal 目录以查找可用的命令,因为它不会保留可用命令的缓存。
更多内容...
Drupal Console 提供了更直观的开发者体验,因为它遵循 Drupal 核心的编码格式。我们将讨论如何使用控制台创建实体。
使用控制台命令创建实体
控制台的一个优点是它能够利用 Symfony Console 的提问助手,提供强大的交互体验。Drupal Commerce 使用控制台提供commerce:create:store命令来生成商店。该命令的目的是简化特定实体的创建。
\Drupal\commerce_store\Command\CreateStoreCommand类覆盖了默认的交互方法,该方法用于从用户那里提示数据。它将提示用户输入商店的名称、电子邮件、国家/地区和货币。
开发者可以实现类似的命令,为高级用户提供更简单的方式来处理模块和配置。
参见








浙公网安备 33010602011771号