Drupal8入坑指南

为什么选择 Drupal?

我们先来看看3大开源 CMS 系统各自的主要特点:

WordPress:

  • 用户友好,非常适合非开发人员
  • 免费和付费的插件、主题数量庞大
  • 拥有最活跃的社区(WP市场占有率60%以上)
  • 优秀的本地化支持

Joomla:

  • 注重用户友好的同时,兼顾了自定义开发,平衡了 WP 和 Drupal 各自的优点
  • 优秀的本地化支持

Drupal:

  • 强大的自定义开发支持,非常适合开发人员
  • 支持CLI命令行工具

其实只要稍微作对比就会发现,如果没有特殊的需求,开发的首选依然是 WordPress。但是也显而易见,用户友好的代价就是缺乏一定的灵活性,比如说我已经规划好了整个网站的架构,然后发现如果需要用 WordPress 来实现的话,需要各种各样的插件支持,这样反而提升了开发的复杂度。这也是为什么我会选择 Drupal。如果说 WordPress 是一台完美组装好的强力赛车,那 Drupal 就是一辆底盘优秀,但是其余部件都要自己选择并组装的赛车,其突出的特点就是可配置性。

从哪里获取资料?

尽管 Drupal 的功能非常强大,但是它所占的市场规模实在是太小了(3%左右),因此国内的相关资料少得可怜,百度直接找到的活跃社区,就是Drupal中国,但是上面的学习资料太散了,不适合入门学习,后来只能回归官方文档。学习了几天之后,感觉非常吃力,不仅仅因为是英文,而且官方的文档就是一本说明书,不是教程。类似的还有社区文档,社区的文档的参考价值比官方文档高,不过依然不够完善,进一步搜索后,才找到了我真正需要的东西:

初探——掌握 Drupal 的使用方法

《Beginning Drupal 8》:Todd Tomlinson 写的一本用来入门 Drupal 的书,Google 搜索 PDF 可以直接找到,有概念有实践。粗略浏览一遍就能对 Drupal 有一个整体的印象,之后再结合 Drupal 的社区文档进行学习,就容易消化多了。

进阶——掌握 Drupal 的开发方法

开发资料,最基础的依然是社区开发文档,比如Theming Drupal 8,然后配合 Packt上的两本书:《Drupal 8 Theming with Twig》和 《Matering Drupal 8》,就可以掌握最基本的开发方法和步骤了。

可惜的是,到目前为止,并没有找到有价值的中文资料,有待补充。当然,除了文档和书籍,官方论坛也是个好地方,一般用来解决开发过程中遇到的各式各样的问题。

上手 Drupal

关于开发环境一类的搭建就不多赘述了,能选择 Drupal 进行开发的,基本也都是老手了。唯一需要注意的一点是,注意 php 和 mysql 的配置,比如我是 Windows 平台上用 XAMPP 搭建的环境,就参考 Quick install Drupal with XAMPP on Windows 进行配置。

Drupal 的目录结构

了解一个项目最好的出发点,就是先了解它的组成结构。前面也说到了,通过 Composer 直接安装的项目,Drupal 的主要文件位于 “/web” 目录下,而 “/vendor” 目录下则是这个 Drupal 发行版所需的各种依赖。Drupal 的主要文件结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
├── core                              # Drupal 核心部分
│ ├── assets
│ ├── config
│ ├── includes
│ ├── lib # Drupal 核心依赖库
│ ├── misc
│ ├── modules # Drupal 核心模块
│ ├── profiles # Drupal 安装配置
│ ├── scripts # Drupal CLI脚本
│ ├── tests
│ ├── themes # Drupal 默认主题
│ └── ...
├── modules # Contributed/Custom 模块
├── profiles # Distribution/Custom 安装配置
├── sites
│ ├── default
│ │ ├── files # 站点文件(包含图片、CSS、JS、导出的配置、语言等)
│ │ ├── default.services.yml
│ │ ├── default.settings.php
│ │ └── settings.php # 站点设置文件(数据库、代理、PHP设置等)
│ └── ...
├── themes # Contributed/Custom 主题
├── INSTALL.txt
├── README.txt
├── autoload.php # 加载依赖
├── example.gitignore
├── index.php # 主入口文件
├── robots.txt
├── update.php # 数据库升级
└── web.config

安装 Drupal

建议使用 Composer 安装 Drupal,以便随后可以通过 Composer 管理整个项目。

1
2
3
4
5
# 如果下载太慢可以尝试替换阿里云的镜像源或者使用代理
composer create-project drupal/recommended-project my_site_dir

# 安装完本体后别忘了把 drush 也装上(drush 是 drupal 的命令行工具)
composer require drush/drush

需要注意的是,由于新版本的 Drupal 考虑到安全性,把网站根目录和依赖分别放在两个不同的目录(/web 和 /vendor)中,因此在服务器部署的时候,需要注意文件所在位置,这个坑比较麻烦,这里举个例子:

比如我有一个项目需要部署在一个共享云主机上面,由于只能通过 FTP 访问网站根目录,没法配置 Apache,因此只有两种方法,一是使用官网直接下载的版本(core 和 vendor 在同一目录下),二是通过 RewriteRule 改写当前路径。考虑到安全问题,我们还是使用根目录和依赖分开的目录结构,为此需要配置 .htaccess 文件(假设目前根目录为 drupal):

1
2
3
4
5
6
7
8
# example.com/.htaccess
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www.example.com$ [NC,OR]
RewriteCond %{HTTP_HOST} ^example.com$
RewriteRule ^$ web/$1 [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule (.*) web/$1 [L]

这样一来,只要我访问 example.com 就相当于访问 example.com/web。不过此时还有可能会遇到问题,需要在 “site/default/setting.php” 种加上一句:

1
2
3
if (isset($GLOBALS['request']) and '/web/index.php' === $GLOBALS['request']->server->get('SCRIPT_NAME')) {
$GLOBALS['request']->server->set('SCRIPT_NAME', '/index.php');
}

如果不这样的话,"/web/index.php" 就会在根目录被 Rewrite 一次,导致找不到页面,而至于 “/web/.htaccess”,可以完全不用管它。

和 WordPress 一样,通过直接访问站点的方式进行安装,除此之外 Drupal 还支持通过 Drush 进行命令行安装。安装的第一步需要选择语言,建议先默认使用英文安装,中文安装有时候会失败,原因是下载超时,当然也有解决方法:

core/lib/Drupal/Core/Http/ClientFactory.php 50行的位置,修改超时或者设置代理即可解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$default_config = [
// Security consideration: we must not use the certificate authority
// file shipped with Guzzle because it can easily get outdated if a
// certificate authority is hacked. Instead, we rely on the certificate
// authority file provided by the operating system which is more likely
// going to be updated in a timely fashion. This overrides the default
// path to the pem file bundled with Guzzle.
'verify' => TRUE,
'timeout' => 30, # 这里修改为 300 或者更高
'headers' => [
'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(),
],
'handler' => $this->stack,
// Security consideration: prevent Guzzle from using environment variables
// to configure the outbound proxy.
'proxy' => [
'http' => NULL,
'https' => NULL,
'no' => [],
],
];

如果默认使用英文安装,则可以在安装完成后手动启动 Language 和 Interface Translation 两个模块,接着去官网下载语言包,在 Interface Translation 的配置中导入下载的语言包,最后去 Language 配置中设置默认语言为中文即可。

介绍常用功能

为了方便理解,以下演示的语言都设置为英文。安装完成后会自动跳转到首页,此时可以在页头看到一个工具栏,这个工具栏只有登录后才会显示。在 Drupal 中,登录后台最直接的方式就是在 URL 后面加上 “/user”,登录之后会跳转到用户的个人页面,此时便可以通过工具栏访问不同的栏目了。

image-20200418004526170

Drupal 的主要功能区如上图所示:

  • Content(内容):管理文章和媒体
  • Structure(结构):管理区块、内容类型、菜单、分类、视图
  • Appearance(外观):管理主题
  • Extend(扩展):管理模块
  • Configuration(配置):配置站点、语言等
  • People(人员):管理用户
  • Reports(报告):查看更新以及日志
  • Help(帮助):查看模块帮助

内容类型

作为CMS,首先最关心的应该是如何添加页面和内容。内容的话,首先添加站点所需的内容类型,Drupal 特别优秀的一点就是对内容类型的字段操作。

image-20200418180947890

添加自定义字段后,可以通过 “Manage form display” 管理添加的这些字段在创建内容时显示的可见性、顺序以及格式(Format:用来限定字段的数据类型、显示格式等),而 “Manage display” 则是管理对应内容类型展示给用户的内容的可见性、顺序以及格式。

页面和区块

对于页面,Drupal 有两种常见的创建页面方式:视图(Views) 和页面管理器(Page Manager UI)。二者都调用了 Page Manager 模块,区别在于视图是集成到 Drupal 核心的,安装好 Drupal 之后就可以使用,而页面管理器是一个额外的模块,需要额外安装。无论通过哪种方式创建页面,都需要调用该页面的模板(page–suggestion.html.twig,后面主题开发会介绍),模板上会指定内容显示的区域(Region),光创建好页面是没有用的,还需要指定访问该页面的 URL 以及在页面对应的区域中布置区块(Block):

image-20200418213934941

页面的模板往往是由主题指定的,因此不同的主题会有不同的区块布局(Block Layout)。区块分为系统自带的区块(Core、System、Menus等)和自定义区块(Custom),网页上的所有内容,都是区块,举个列子:在内容(Contnet)中创建的文章,其实都是通过 “Main page content” 这个系统区块添加到页面中的。因此,如果你有一段 HTML 代码需要添加到页面的对应位置,那么在自定义区块库(Custom block library)中添加一个自定义区块即可(补充:由于区块本质也是内容,因此在自定义区块库中也可以创建自己想要的区块类型,比如我可以指定某类区块都是富文本而另一类区块都是纯 HTML。)

至此,Drupal 添加页面和内容的流程就很清晰了:

image-20200419002431476

视图

前面说了,创建页面可以用视图或者页面管理器,实际上视图是页面管理器的超集,因此只需掌握如何使用视图创建页面就够了。

image-20200418221017604

从视图(Views)进来,就可以发现,实际上包括 Drupal 的后台页面在内,都是一个个的视图组成的。创建视图也是学习 Drupal 的路上遇到的第一个硬骨头。选择 “Add view” 来添加一个视图,我们可以指定视图要显示的内容以及视图呈现的形式(Display):作为一个页面或者作为一个区块,之后就会进入视图的详细设置页面。接下来以首页(Frontpage)为例来看看视图中具体的设置。

image-20200418223519270

首先是显示(Display),这是视图最好用的功能,你可以给选定内容不同的显示方式,比如页面(Page)、区块(Block)、摘要(Teaser)这三种最常用的显示方式(除此之外还有附件(Attachment)、嵌入(Embed)、实体引用(Entity Reference)以及信息聚合(Feed)),如果有特殊需要,你还可以自定义一种显示方式( 在 Structure->Display modes ->View modes 中)。这个功能最常见的应用,就是在首页只显示文章的摘要(Teaser),点击标题后进入文章页查看全文(Default)。

注:默认显示(Default)一般是不可见的,如果需要更改默认显示,要先在视图的设置中勾选上:Always show the master (default) display

视图里面一般需要配置的项目有:

  • 格式(FORMAT)
    • Format:设置内容显示的方式,Unformatied list 就是用 <div> 标签包裹内容;
    • Show:设置显示具体的内容(Content)还是只显示字段(Field),如果显示具体内容,后面还可以设置内容的显示方式。
  • 字段(FIELDS)
    • 如果上面设置只显示字段,那么就可以在这里添加需要显示的字段。
  • 过滤器(FILTER)
    • 过滤内容,可以多个条件组合使用。
  • 排序(SORT)
    • 对内容显示进行排序。
  • 页面/块设置(PAGE/BLOCK SETTINGS)
    • 设置页面的路径(即 URL)、所属菜单等信息。
  • 分页(PAGER)
    • Use pager:设置是否使用分页器以及分页的样式、每页显示的数量等;
    • More link:设置是否在分页器后显示 “more” 链接,点击该链接即查看全部内容。
  • 上下文过滤器(CONTEXTUAL FILTERS)
    • 配合当前 URL 中提供的信息进行内容过滤。
  • 联系(RELATIONSHIPS)
    • 和其他实体建立联系,比如和分类(Taxonomy)建立联系,然后便可以配合上下文过滤器实现通过分类名称来过滤内容。

分类和菜单

说到分类(Taxonomy)和菜单(Menus),如果有使用 WordPress 的经验,那么应该对这两个东西不陌生了。同样还是举例子吧,在 Drupal 中,可以使用分类来实现文章标签的功能:

image-20200418234159320

首先在分类页面下新建一个词汇 “标签(Tags)”,之后再这个词汇下建立多个术语(terms),对应不同的文章类型。之后在对应文章的文章类型中,新增一个字段,用来添加这个标签:

image-20200418234419794最后在创建文章的时候,选择或者填写预先设置好的标签即可。假如还需要给不同的标签创建一个归档页,则利用上面讲到的视图:新建一个视图,过滤器选择对应分类术语即可。(实际上 Drupal 自带归档页视图,只不过没有启用,在视图页的 “Disabled” 栏目下面)

Drupal 默认有 5 个菜单,这 5 个菜单只可以编辑不可以删除:

image-20200418235126300

以管理菜单(Administration )为例:

image-20200418235305417

可以通过 “Add link” 添加菜单项目,然后通过菜单项前面的符号拖动菜单项排序和分级。每个菜单项本质是其对应页面的链接。因此我们现在可以更新一下上面说到的 Drupal 添加页面和内容的流程:

image-20200419002400379

介绍常用模块(不间断更新)

系统管理

  • Allowed Formats:添加内容类型的 Body 字段时,指定可用的文本编辑器。
  • Business Rules:Drupal 8.X 以上版本对 Rules 模块的替代,用来实现根据不同“条件(Condition)”执行对应的“动作(Action)”,比 Rules 模块好用。
  • Structure Sync:允许导出菜单、分类、自定义区块。
  • Configuration Partial Export:允许到处单个或者多个配置。
  • Admin Toolbar:允许 Toolbar 显示下拉菜单。
  • Rename Admin Paths:允许重命名登录和后台管理页面的路径,默认为 “/user” 和 “/admin”。
  • Display Suite:允许对内容类型的字段进行分栏,提供多栏显示的功能。

内容创作

  • Blazy:提供图片懒加载功能。
  • Gutenburg:WordPress 古腾堡编辑器。
  • Views Infinite Scroll:无限滚动分页。
  • Metatag:提供页面或者内容的元信息,方便 SEO。
  • Layout Builder:提供可视化的显示方式编辑(Manage display)
  • ImageWidgetCrop:提供图片剪切的功能。
  • Background Images Formatter:格式化 CSS 中使用的背景图片。

主题开发

名词解释

  • Entity:Drupal 管理的一切内容都是实体(Entity),包括 Nodes、Users 、Comments等;
  • Nodes:Drupal 上的所有内容均被存储并视为结点(Node),Node 可以是任何内容,例如页面,文章;
  • Field:可以添加到元素的数据字段,例如标题,正文,注释,标签,图像;
  • theme hook suggestions:Drupal确定模板文件可以使用的名称的过程,类似于 Wordpress 决定使用哪个页面模板的过程。

主题的文件结构

自定义的主题建议放在 /themes/custom/ 下面 (非必须)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|-*.info.yml          // 定义元数据,全局库和区域块
|-*.libraries.yml // 定义主题可以加载的 JS/CSS 库
|-*.breakpoints.yml // 定义响应式断点(仅当Drupal需要与断点进行交互时)
|-*.theme // 定义条件逻辑、数据预处理以及扩展基本主题设置
|-templates // 定义主题页面模板
| |-maintenance-page.html.twig
| |-node.html.twig
|-config // 定义主题所需的各种变量
| |-install
| | |-*.settings.yml
| |-schema
| | |-*.schema.yml
|-css
| |-style.css
|-js
| |-script.js
|-images
| |-buttons.png
|-logo.svg
|-screenshot.png

编写 *.info.yml 文件

必需属性:

1
2
3
name: xxxxx   // 主题的名字,用来显示在“外观”菜单中
type: theme // 指明类型为 theme 而非 module 或者 profile
core: 8.x // 指明 Drupal Core 的版本

可选属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
description: xxxxxx // 主题的描述
package: Core // 指定主题的分组
php: x.x.x // 所需最低版本的 PHP
version: x.x.x // 给主题指定一个版本
base theme: classy // 继承某个主题的资源
logo: xxx // 指定主题LOGO
screenshot: xxx // 指定主题缩略图(588x438)
features: // 指定要在主题“设置”页面上显示的功能列表
- comment_user_verification
- comment_user_picture
- favicon
- logo

ckeditor_stylesheets: // 定义 CKEditor 的样式
- https://fonts.googleapis.com/css?family=Open+Sans
- css/base/elements.css

regions: // 指定主题的区域(注意没有横杠)
header: Header
content: Content
sidebar_first: 'First sidebar'
regions_hidden: // 指定要移除的区域(这里有横杠)
- sidebar_last

libraries: // 启用主题时加载的 CSS/JS
- */global-styling

libraries-override: // 启用主题时覆盖原本的 CSS/JS
contextual/drupal.contextual-links:
css:
component: // 上面三个部分都是对应库的 namespace
/core/themes/stable/css/contextual/contextual.module.css: false

libraries-extend: // 当某个库加载的同时额外加载的库
core/drupal.user:
- classy/user1
- classy/user2

其他还有 hiddenengine等可选属性

设置区域块

设置区域块包含两个步骤:

  • 将区域元数据添加到 THEMENAME.info.yml 文件;
  • 将定义的区域输出到 page.html.twig 文件。

注:如果在主题中声明任何区域,将会使默认区域无效,因此这种情况下需要自己声明所有区域

默认区域:

  1. page.header
  2. page.primary_menu
  3. page.secondary_menu
  4. page.highlighted
  5. page.help (dynamic help text, mostly for admin pages)
  6. page.content (main content of current page)
  7. page.sidebar_first
  8. page.sidebar_second
  9. page.footer
  10. page.breadcrumb

首先需要在 *.info.yml 中指定所需的 regions,比如:

1
2
3
4
regions:
header: 'Header'
content: 'Content'
footer: 'Footer'

之后就需要在 templates/page.html.twig 中添加定义的区域:

1
{{ page.header }}

page 为所定义区域的命名空间

加载 CSS 和 JS 资源

加载 CSS 和 JS 包含两个步骤:

  • * .libraries.yml 文件中定义所有库;
  • 将库附加到所有页面或者特定页面。

首先在 *.libraries.yml声明需要使用的库(CSS/JS),如下所示,{} 内部可以定义各种参数,具体参考:Defining Libraries: Options & Details

注意: *.info.ymllibraries 所定义的库会应用于所有主题页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cuddly-slider:
version: 1.x
css:
theme:
css/cuddly-slider.css: {}
js:
js/cuddly-slider.js: {}
dependencies:
- core/jquery

global-styling:
version: 1.x
css:
theme:
css/layout.css: {}
css/style.css: {}
css/colors.css: {}

global-scripts:
version: 1.x
js:
js/navmenu.js: {}

在这个例子中,cuddly-slider.js 储存在 js/ 目录中,cuddly-slider.css 储存在 css/ 目录中。默认情况下,Drupal 8 不再在所有页面上加载 jQuery,因此需要指定 dependencies,由于 Drupal 的 Core 包含了 jQuery,因此只需要指定 core/jquery 即可,对于其他需要使用的依赖,按照 resource/library 的格式引用即可

关于 css 中的 theme,表示 css 文件所属的样式类型,共有 5 种不同的样式类型,不同的样式类型有不同的 CSS 权重:

  • base: CSS reset/normalize plus HTML element styling. Key assigns a weight of CSS_BASE = -200
  • layout: macro arrangement of a web page, including any grid systems. Key assigns a weight of CSS_LAYOUT = -100
  • component: discrete, reusable UI elements. Key assigns a weight of CSS_COMPONENT = 0
  • state: styles that deal with client-side changes to components. Key assigns a weight of CSS_STATE = 100
  • theme: purely visual styling (“look-and-feel”) for a component. Key assigns a weight of CSS_THEME = 200

将库应用到所有页面

*.info.ymllibraries 中添加所需的全局库:

1
2
3
libraries:
- */global-styling
- */global-scripts

将库应用到部分页面

*.theme 文件中使用钩子函数 THEME.preprocess_HOOK() ,其中 THEME 是主题名,HOOK 是需要加载库的页面名:

1
2
3
function fluffiness_preprocess_maintenance_page(&$variables) {
$variables['#attached']['library'][] = 'fluffiness/cuddly-slider';
} // 在维护页面中加载“fluffiness/cuddly-slider”库

注意:除了上述方法,还可以在 Twig 模板中使用 attach_library() 函数加载库

设置断点

注意:仅当 Drupal 需要与断点进行交互时才需要将 CSS 断点写到 *.breakpoints.yml 文件中,例如在使用“响应式图像”模块的情况下。

主题和模块可以通过创建一个名为 *.breakpoints.yml 的配置文件来定义断点。该文件中每一块内容代表一个断点,每个断点包括一个标识符,例如 bartik.mobile,随后是定义这个断点的属性:

  1. label:断点的标签
  2. mediaQuery:媒体查询语句
  3. weight:断点的权重(顺序)
  4. multipliers:支持的像素分辨率乘数

最小 min-width 的断点应具有最小的权重,而最大 min-width 的断点应具有较大的权重。默认情况下,模块将按从最小到最大的顺序对断点进行排序,即移动端优先的原则。

断点可以进行分组。例如布局断点和图像断点分开,详细使用方法去看文档。

使用 Twig 模板

Twig 模板的使用方法参考:Creating and Using Templates

Twig 基本语法

变量

  1. 定义变量
1
2
3
{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}

调用变量:{{ foo.bar }}

  1. Filter

变量可以通过过滤器修改,类似于 shell,通过“|”管道符分隔。可以链接多个过滤器,一个过滤器的输出将作为下一个过滤器的输入

1
2
3
4
5
{{ name|striptags|title }}
{{ list|join(', ') }} {# 带参数的形式 #}
{% apply upper %} {# 在代码段上应用过滤器 #}
This text becomes uppercase
{% endapply %}

函数

1
2
3
4
5
6
7
{% for i in range(0, 3) %}	{# 普通形式 #}
{{ i }},
{% endfor %}

{% for i in range(low=1, high=10, step=2) %} {# 命名参数形式 #}
{{ i }},
{% endfor %}

注:命名参数还可以用来跳过一些不想更改其默认值的参数

控制结构

1
2
3
4
5
6
7
{% if users|length > 0 %}
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
{% endif %}

引用其他模板

1
{{ include('sections/articles/sidebar.html') }}

模板继承

模板继承使您可以构建基本的“骨架”模板,该模板包含站点的所有常见元素,并定义子模板可以覆盖的 block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
&copy; Copyright 2011 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
</html>

子模版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% extends "base.html" %}

{% block title %}Index{% endblock %}
{% block head %}
{{ parent() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}

调试 Twig 模板

调试 Twig 模板有两种方法:使用 Devel 模块或者使用 twig_xdebug 模块配合 xdebug 调试。Devel 主要用来检查Twig 模板的变量,实际调试过程中,还是 xdebug 好用,因为可以在 IDE 中查看变量和调用栈。因此我选择主要用 xdebug 进行调试,Devel 辅助查看变量。

使用 xdebug 调试 Twig 需要的步骤:

  1. 安装 xdebug 并在 php.ini 中开启:

    1
    2
    3
    4
    zend_extension=D:\xampp\php\ext\php_xdebug-2.9.4-7.4-vc15-x86_64.dll

    [xDebug]
    xdebug.remote_enable=1

    启动之后,xdebug 默认监听 127.0.0.1:9000

  2. 浏览器安装 Xdebug Helper (Google Chrome & Firefox)

  3. 安装 twig_xdebug 模块并开启

    1
    2
    composer require --dev drupal/twig_xdebug
    drush en -y twig_xdebug
  4. 在 twig 文件中使用 {{ breakpoint() }}

  5. 如果需要使用 dump() 函数检查变量,则需要本地开启 debug 模式:drupal site:mode dev

注意:经过测试,使用 drupal site:mode dev 之后,还是需要 drupal cr 才能看到效果,使用 Disable Drupal 8 caching during development 的第一种方法即使用“settings.local.php”的方法在当前版本(version 8.8.5)确认有效

Twig模板命名约定

  • HTML 模板:html.html.twig (core/modules/system/templates/html.html.twig)

    提供 HTML 页面基本结构的标记,包括 <head><title><body> 标记

  • Page 模板:page.html.twig (core/modules/system/templates/page.html.twig)

    提供每个单独页面的模板,比如首页:page--front.html.twig

    匹配模式:page–[front|internal/path].html.twig

  • Regions 模板:region.html.twig (core/modules/system/templates/region.html.twig)

    匹配模式:region–[region].html.twig

  • Blocks 模板:block.html.twig (core/modules/block/templates/block.html.twig)

    匹配模式:block–[module|–[delta].html.twig

  • Nodes 模板:node.html.twig (core/modules/node/templates/node.html.twig)

    匹配模式:node–[content-type|nodeid]–[viewmode].html.twig

  • Taxonomy terms 模板:taxonomy-term.html.twig (core/modules/taxonomy/templates/taxonomy-term.html.twig)

    匹配模式:taxonomy-term–[vocabulary-machine-name|tid].html.twig

  • Fields 模板:field.html.twig (core/modules/system/templates/field.html.twig)

    匹配模式:field–[[type|name]|[entity-type]–[field-name|content-type]].html.twig

  • Comments 模板:comment.html.twig (core/modules/comment/templates/comment.html.twig)

    匹配模式:comment–[comment-field-name]–[node-type].html.twig

  • Views 模板:views-view.html.twig (core/themes/stable/templates/views/views-view.html.twig)

    匹配模式: views-view–[viewid]–[view-display-id].html.twig

    ​ views-view–[viewid]–[view-display-type].html.twig

    ​ views-view–[view-display-type].html.twig

    ​ views-view–[viewid].html.twig

另外还有 Forums、Maintenance Page、Search result 等页面的模板。

注:添加模板后,必须重新构建缓存,以使 Drupal 使用新模板:drush cr

覆盖模板

为了覆盖已有的模板,需要:

  • 找到想要覆盖的模板文件;
  • 将模板文件从其原本位置复制到对应主题内;
  • 按需修改模板。

有时只需要覆盖模板的某一部分内容,这时可以使用 Theme hook suggestions:

模板中使用属性对象

许多 Twig 模板具有一个或多个作为变量传递的属性对象。默认情况下,以下属性对象变量可用于所有模板:attributestitle_attributescontent_attributes

1
<div{{ attributes }}></div>

常用方法:

1
2
3
4
5
attributes.addClass()
attributes.removeClass()
attributes.setAttribute($attribute, $value)
attributes.removeAttribute($attribute)
attributes.hasClass($class)

创建一个新的属性对象:

1
2
3
<div{{ create_attribute({'class': ['region', 'region--header']}) }}>
{{ content }}
</div>

可以在 .theme 文件中对属性对象进行预处理或者修改:

1
2
3
4
5
6
7
8
9
10
/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
if ($variables['menu_name'] == 'main') {
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
}
$variables['attributes']['class'] = array_merge($variables['attributes']['class'], ['my-main-menu']); }
}

创建高级主题设置

高级主题设置即在“管理/外观/设置”里面对应主题名的设置页面,通常由 THEME.theme 或者 theme-settings.php 两个文件来进行控制,使用 Forms API 添加需要进行设置的项目。以 THEMENAME_form_system_theme_settings_alter(&$form, $form_state) 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo_form_system_theme_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id = NULL) {
// Work-around for a core bug affecting admin themes. See issue #943212.
if (isset($form_id)) {
return;
}

$form['foo_example'] = array(
'#type' => 'textfield',
'#title' => t('Widget'),
'#default_value' => theme_get_setting('foo_example'),
'#description' => t("Place this text in the widget spot on your site."),
);
}

默认值需要定义在 config/install/THEME.settings.yml 文件中:

1
foo_example: default value

随后在所有主题的 PHP 文件中,可通过:

1
$foo_example = theme_get_setting('foo_example');

获取这个值,如果需要在 Twig 文件中使用,需要先在 THEME.theme 文件中定义:

1
2
3
4
<?php
function foo_preprocess_node(&$variables) {
$variables['foo_example'] = theme_get_setting('foo_example');
}

之后就可以在 Twig 文件中直接使用 {{ foo_example }} 获取变量值

附录1:常用命令

  • 安装 Drupal
1
composer create-project drupal/recommended-project ./dir-name
  • 安装 drush
1
composer require drush/drush
  • 更新模块
1
2
3
4
5
6
composer outdated 'drupal/*' # 检查可更新模块
composer update drupal/modulename --with-dependencies # 更新对应模块

drush updatedb
drush cache:rebuild
drush config:export --diff # 更新数据库、重建缓存同时导出可能更改的配置
  • 列出已安装模块
1
composer [global] show -i
  • 快速创建 Drupal 实例
1
php ./core/scripts/drupal quick-start PROFILE

附录2:实用技巧

  • 在 JS 中判断当前页面是否为主页:

先在 * .libraries.yml 文件中加载 drupalSettings

1
2
3
4
5
js:
js/main.js: {}
dependencies:
- core/jquery
- core/drupalSettings

然后在 JS 文件中调用 drupalSettings.path.isFront

1
2
3
(function ($, drupalSettings) {
console.log(drupalSettings.path.isFront);
})(jQuery, drupalSettings);

Drupal8入坑指南
https://infiniture.cn/2020/04/19/Drupal8入坑指南/
作者
NickHopps
发布于
2020年4月19日
许可协议