logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

Drupal 模块的基本结构

04/10/2025, by Ivan

Drupal 8 基础模块构建实用指南 第二部分
从 .info 到测试,只涵盖基础内容

基本结构

loremipsum.info.yml

name: Lorem ipsum
type: module
description: 'Drupal 的 Lorem ipsum 生成器'
package: Development
core: 8.x
configure: loremipsum.form

Info 文件现在使用 YML 格式,并且模块与主题之间的差别需要通过类型声明来明确。配置 (configure) 声明指向一个路由(稍后会详细说明),除此之外就没有别的内容。事实上,这是你模块唯一必需的文件。将其保存到 (root/modules) 文件夹后,你就可以在 /admin/modules 启用模块,而不会破坏你的网站。但正如你接下来会看到的,这还远远不够。

loremipsum.module

<?php

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function loremipsum_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.loremipsum':
      return t('
        <h2>Drupal 的 Lorem ipsum 生成器</h2>
        <h3>使用说明</h3>
        <p>Lorem ipsum dolor sit amet... <strong>开玩笑的!</strong></p>
        <p>解压到 <em>modules</em> 文件夹(目前在 Drupal 8 安装的根目录下),然后在 <strong>/admin/modules</strong> 启用。</p>
        <p>接着访问 <strong>/admin/config/development/loremipsum</strong>,输入你自己的短语集来构建随机生成的文本(或者使用默认的 Lorem ipsum)。</p>
        <p>最后访问 <strong>www.example.com/loremipsum/generate/P/S</strong>,其中:</p>
        <ul>
          <li><em>P</em> 表示 <em>段落</em> 数量</li>
          <li><em>S</em> 表示最大 <em>句子</em> 数量</li>
        </ul>
        <p>还有一个生成器区块,你可以选择生成多少段落和句子,其余的系统会自动完成。</p>
        <p>如果需要,还可以设置一个专门的 <em>generate lorem ipsum</em> 权限。</p>
        <h3>注意</h3>
        <p>大多数 bug 已经修复,漏洞也已补上,功能也有所增加。但该模块仍在开发中。请报告 bug 和建议,好吗?</p>
      ');
  }
}

一个好的实践是在这里至少放置 hook_help() 的实现。还要注意 use 语句,它引入了 RouteMatchInterface 类。这主要是因为 hook_menu() 已经不存在了。

……随着深入,你会注意到 .module 文件也会被用来存放主题相关的信息。所以要保留好它。

loremipsum.install

<?php

/**
 * @file
 * Lorem ipsum 模块的安装函数。
 */

use Drupal\user\RoleInterface;

/**
 * Implements hook_install().
 */
function loremipsum_install() {
  user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array(
    'generate lorem ipsum' => TRUE,
  ));
}

这里我们使用了另一个类:RoleInterface。基本上,这个文件告诉 Drupal:“一旦启用了该模块,就找到 *generate lorem ipsum* 权限并启用它”。

但这个权限是在哪里定义的呢?

loremipsum.permissions.yml

generate lorem ipsum:
  title: 'Generate Lorem ipsum'

如你所见,这比调用 hook_permission() 简单得多。完整的语法请参考 PermissionHandler 文档

loremipsum.routing.yml

loremipsum.generate:
  path: '/loremipsum/generate/{paragraphs}/{phrases}'
  defaults:
    _controller: '\Drupal\loremipsum\Controller\LoremIpsumController::generate'
  requirements:
    _permission: 'generate lorem ipsum'

loremipsum.form:
  path: '/admin/config/development/loremipsum'
  defaults:
    _form: '\Drupal\loremipsum\Form\LoremIpsumForm'
    _title: 'Lorem ipsum 设置'
  requirements:
    _permission: 'administer site configuration'

路由文件取代了 hook_menu()。每个条目(无缩进的行)定义一个路由,接下来缩进的行描述具体的设置。

loremipsum.generate 路由指向一个页面,它接受两个 {} 参数;它对应的是控制器(稍后会详细说明),而 loremipsum.form 指向一个带标题的(配置)表单。

两个路由都需要权限,但你也可以将其替换为 _access: 'TRUE' 以便开放访问。

loremipsum.services.yml

用于声明自定义服务。

loremipsum.links.menu.yml

loremipsum.form:
  title: 'Lorem Ipsum 设置'
  description: '配置 Lorem Ipsum 模块的设置。'
  route_name: loremipsum.form
  parent: 'system.admin_config_development'

虽然路由文件在 /admin/config/development/loremipsum 创建了页面,但这些定义是将页面添加到管理菜单所必需的。

loremipsum.links.task.yml

用于为特定路由创建额外的 本地任务(标签页)

loremipsum.links.action.yml

用于为特定路由创建额外的 本地操作(按钮)

loremipsum.links.contextual.yml

用于为特定 UI 元素创建额外的 上下文操作

loremipsum.libraries.yml

用于记录 CSS 和 Javascript 库的依赖关系。详情请参考 相关文档

README.md

解压到 *modules* 文件夹(当前位于 Drupal 8 安装的根目录),然后在 `/admin/modules` 启用。

接着访问 `/admin/config/development/loremipsum` 并输入你自己的短语集
以生成随机文本(或者使用默认的 Lorem ipsum)。

最后访问 `www.example.com/loremipsum/generate/P/S`,其中:
- *P* = 段落数量
- *S* = 最大句子数量

此外还有一个生成器区块,你可以选择生成多少段落和句子,其余的由系统完成。

如果需要,还有一个专门的 *generate lorem ipsum* 权限。

注意
---------

大多数 bug 已修复,漏洞已补上,功能已增加。但该模块仍在开发中。请报告 bug 和建议,好吗?

是的,README 文件现在用 Markdown 编写。如果你问我,这相当酷。

现在让我们深入文件夹,更详细地研究具体细节。

LICENSE.TXT

不要包含 LICENSE.txt(或类似文件)。打包脚本会自动添加。

/config/install/loremipsum.settings.yml

loremipsum:
   page_title: 'Lorem ipsum'
   source_text: "Lorem ipsum dolor sit amet, consitteur adipisci elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua". ... "

该文件存储默认设置,这些设置会通过下一个文件分配给正确的字段:

loremipsum:
  page_title: 'Lorem ipsum'
  source_text: "Lorem ipsum dolor sit amet, consectetur adipisci elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "

该文件存储默认设置,这些设置会通过下一个文件分配给正确的配置表单字段。

/config/schema/loremipsum.schema.yml

loremipsum.settings:
  type: config_object
  label: 'Lorem Ipsum 设置'
  mapping:
    loremipsum:
      type: mapping
      mapping:
        page_title:
          type: text
          label: 'Lorem ipsum 生成器页面标题:'
        source_text:
          type: text
          label: '用于 lorem ipsum 生成的源文本:'
block.settings.loremipsum_block:
  type: block_settings
  label: 'Lorem ipsum 区块'
  mapping:
    loremipsum_block_settings:
      type: text
      label: 'Lorem ipsum 区块设置'

即使你没有为模块定义自定义表,这个 schema 文件仍然会被使用 —— 在这里你可以看到配置表单字段的默认值。

在开发此代码时,我发现“开箱即用”地填充表单字段是最难的部分之一。但幸运的是,有一个模块可以解决这个问题:Drupal 8 配置检查器,它能帮助你调试默认设置。

此外,YML schema 文件 还有很多其他用途

/src/Controller/LoremIpsumController.php

<?php

namespace Drupal\loremipsum\Controller;

use Drupal\Component\Utility\Html;

/**
 * Lorem ipsum 页面控制器。
 */
class LoremIpsumController {

  /**
   * 根据参数生成 Lorem ipsum 文本。
   * 该回调映射到路径
   * 'loremipsum/generate/{paragraphs}/{phrases}'。
   * 
   * @param string $paragraphs
   *   需要生成的段落数量。
   * @param string $phrases
   *   每个段落内可以生成的最大句子数量。
   */
  public function generate($paragraphs, $phrases) {

我们到达了模块的核心部分 —— 一个只有一个方法的类,用于生成占位符文本。正如你所见,LoremIpsumController 类中的方法对应于路由 YML 文件中的定义:

04_3

白框内是 loremipsum.routing.yml 文件的代码,背景是我们正在处理的文件。

接着,代码获取模块配置并保存以便后续使用:

    // 默认设置.
    $config = \Drupal::config('loremipsum.settings');
    // 页面标题和源文本.
    $page_title = $config->get('loremipsum.page_title');
    $source_text = $config->get('loremipsum.source_text');

上面提到的参数(loremipsum.page_title 和 loremipsum.source_text)来自于 YAML 配置文件:

05_3

然后我们将 $source_text 中的句子分割成数组:

$repertory = explode(PHP_EOL, $source_text);

并使用该数组来构建段落:

    $element['#source_text'] = array();

    // 生成 X 个段落,每段最多 Y 个句子。
    for ($i = 1; $i <= $paragraphs; $i++) {
      $this_paragraph = '';
      // 当我们说“最多 Y 个句子”时,并不表示“1 到 Y”。
      // 所以我们从一半到 Y 开始取值。
      $random_phrases = mt_rand(round($phrases / 2), $phrases);
      // 同时不要重复最后一句。
      $last_number = 0;
      $next_number = 0;
      for ($j = 1; $j <= $random_phrases; $j++) {
        do {
          $next_number = floor(mt_rand(0, count($repertory) - 1));
        } while ($next_number === $last_number && count($repertory) > 1);
        $this_paragraph .= $repertory[$next_number] . ' ';
        $last_number = $next_number;
      }
      $element['#source_text'][] = Html::escape($this_paragraph);
    }

注意 ['#source_text'] 是传递给模板的渲染数组,并且数组中的每个元素都会通过 Html::escape() 进行安全处理。

最后,我们给渲染数组添加标题,指定主题函数并返回:

    $element['#title'] = Html::escape($page_title);
     
    // 主题函数.
    $element['#theme'] = 'loremipsum';
    
    return $element;
  }

}

但在将该变量传递到模板之前,我们还需要对其进行处理。

下一步:

主题化 模块。

接下来:

添加配置表单

定义区块

为模块编写测试