Skip to content

Latest commit

 

History

History
289 lines (210 loc) · 16.4 KB

File metadata and controls

289 lines (210 loc) · 16.4 KB

因可以在其他地方找到而自豪

"非我所创综合症"的反义词,代表了Drupal核心开发者的思维方式的转变——尝试找到最好的工具并集成到软件(Drupal)中去,而不是构造Drupal专属的,仅对Drupal用户有益的定制化技术。

在Drupal 8中你会看到很多这种哲学的体现。在众多的外部库中,我们引入了PHPUnit来进行单元测试,Guzzle来执行HTTP请求(Web Service),一系列的Symfony组件(可参考《Create your own framework on top of the Symfony2 Components》),Composer用于解决外部依赖和自动加载问题。

除此之外,这一变化也反映在核心基础代码之中,我们在Drupal 8中做出了《大量的架构变更》,以此来响应外部世界的进步:解耦,面向对象,对PHP的新特性例如命名空间和Traits的支持。

变得更加面向对象了

让我们来看看Drupal 8架构中的一些反映上述变化的代码:

Drupal 7: example.info


    name = Example
    description = An example module.
    core = 7.x
    files[] = example.test
    dependencies[] = user

所有的Drupal模块都需要一个.info文件来完成系统中的注册动作。上面的例子是Drupal 7模块中的典型写法。文件格式是一种类似Ini的写法,但是其中还包含了一些Drupal风格的元素,例如array[]语法,这导致无法使用PHP的标准INI文件访问函数来访问info文件。files[]关键字在Drupal 7中用于触发自定义类的载入,也是一种Drupal特有的方式,模块开发者开发的面向对象代码必须添加一个files[]元素来定义类,看起来有点傻

Drupal 8: example.info.yml

    name: Example
    description: An example module.
    core: 8.x
    dependencies:
      - user
    # Note: New property required as of Drupal 8!
    type: module

Drupal 8的info文件改为很多其他语言和框架都在使用的YAML格式。语法类似(“:”代替了 “=”,不同的数组语法),读写都很容易。类的自动加载,不再使用古怪的files[]关键字,而改头换面为用Composer支撑的 PSR-4标准。这种方式采用规定的类名称/文件夹的转换方式,形成一种更像自然语言的规则(modules/example/src/ExampleClass.php),Drupal不再需要手工注册即可自动载入面向对象的代码。

Drupal 7: hook_menu()

example.module

    /**
    * Implements hook_menu().
    */
    function example_menu() {
      $items['hello'] = array(
        'title' => 'Hello world',
        'page callback' => '_example_page',
        'access callback' => 'user_access',
        'access arguments' => 'access content',
        'type' => MENU_CALLBACK,
      );
      return $items;
    }
    /**
    * Page callback: greets the user.
    */
    function _example_page() {
      return array('#markup' => t('Hello world.'));
    }

这是Drupal 7经典的"Hello world"模块,定义了一个/hello的URL,当这个地址被访问时,会判断当前用户是否具有"access content"权限,如果鉴权通过,则会执行_example_page()的代码——显示渲染后的"Hello world"。hook_menu()是Drupal 7以及更早版本中被诟病已久的数组API(API => ArrayPI),这种方式的问题在于,很难进行输入(比如,忘掉return $items,然后抓耳挠腮不知错在何处),没有IDE能够对这种东西进行自动完成,对于关键字的新增和变更,文档也只能进行手工同步。hook_menu文档中也很明显的表现出一个问题:menu的负担太重了——注册URL以及相应的鉴权方法和执行代码,又提供了在界面上暴露连接的多种方式,甚至还能切换主题,以及更多的任务。

Drupal 8: 路由和Controller

example.routing.yml


    example.hello:
      path: '/hello'
      defaults:
        _content: '\Drupal\example\Controller\Hello::content'
      requirements:
        _permission: 'access content'

src/Controller/Hello.php

    namespace Drupal\example\Controller;
    use Drupal\Core\Controller\ControllerBase;

    /**
    * Greets the user.
    */
    class Hello extends ControllerBase {
      public function content() {
        return array('#markup' => $this->t('Hello world.'));
      }
    }

在Drupal 8的路由系统中,采用了同Symfony路由系统一致的YAML格式,用于实现url以及访问控制逻辑。原有的page callback方式现在采用了"Controller"类的方式(可参看model-view-controler模式),Controller遵循PSR-4标准存放于指定命名的文件夹中。这个类存在于example模块的命名空间中,这也防止了同其他模块的命名冲突。最后,这个类使用use和extends语句引入了ControllerBase类,因此这个controller能访问到所有的ControllerBase类的方法,例如$this->t()(t()函数的OO版本)。另外,ControllerBase是一个标准的PHP类,所有的方法和属性在IDE中都可以自动完成,这将大大减少猜测和出错的机会。

Drupal 7: hook_block_X()

block.module

    /**
    * Implements hook_block_info().
    */
    function example_block_info() {
      $blocks['example'] = array(
        'info' => t('Example block'),
      );
      return $blocks;
    }
    /**
    * Implements hook_block_view().
    */
    function example_block_view($delta = '') {
      $block = array();
      switch ($delta) {
        case 'example':
          $block['subject'] = t('Example block');
          $block['content'] = array(
            'hello' => array(
              '#markup' => t('Hello world'),
            ),
          );
          break;
      }
      return $block;
    }

这是Drupal中的典型的插接方式——包括block, 文本格式,图片效果等:某种_info()钩子,然后一个或多个其他钩子来进行其他动作(查看,应用和配置等)。这其中又包含大量的数组,这是因为这些API不具备自描述特性,除了肉眼观察大量模块的.api.php文件之外,别无他法——而这些文件也未必能够提供足够的信息要求你如何命名你的实现。有些是必选,有些不是,哪个是哪个,等等诸如此类的问题。

Drupal 8: Blocks(以及很多其他东西)是一个插件

在Drupal 8中,这些古怪的API转换为新的插件系统,举例如下:

src/Plugin/Block/Example.php

    namespace Drupal\example\Plugin\Block;
    use Drupal\Core\Block\BlockBase;

    /**
    * Provides the Example block.
    *
    * @Block(
    *   id = "example",
    *   admin_label = @Translation("Example block")
    * )
    */
    class Example extends BlockBase {
      public function build() {
        return array('hello' => array(
          '#markup' => $this->t('Hello world.')
        ));
      }
    }

大多数内容跟Controller例子很像;一个插件就是一个基类(BlockBase)的派生,基类维护了很多基础内容。Block API声明了BlockPluginInterface接口,并进行了实现。

接口以IDE友好的方式提供和描述了各种API。学习Drupal 8新API的最好方式之一就是浏览接口。

类上方的注释被称为标注。用PHP注释来指定逻辑的元数据可能让人觉得奇怪,不过这种方式已经被众多的现代PHP库采用,并且也为PHP社区所接受。

Drupal 7:Hooks

在Drupal 7以及更早的版本中,通过hook来实现扩展机制。作为API的作者,可以利用module_invoke_all(), module_implements(), drupal_alter()等方式来声明Hook,例如:

      // Compile a list of permissions from all modules for display on admin form.
      foreach (module_implements('permission') as $module) {
        $modules[$module] = $module_info[$module]['name'];
      }

在一个需要响应这一事件的模块中,可以创建一个名为modulename_hookname()的函数,输出该hook指定的结果。例如:

    /**
    * Implements hook_permission().
    */
    function menu_permission() {
      return array(
        'administer menu' => array(
          'title' => t('Administer menus and menu items'),
        ),
      );
    }

这在Drupal初期是一个很先进的扩展方式(Drupal在2001年出世,当时是PHP3的天下,没有提供面向对象或者类似的机制),这一方式也造成不少麻烦:

  • 利用"特定名称的函数"进行扩展的机制是一种很Drupal的方式,也是新晋开发者最难以理解的问题之一。

  • 至少有四种不同的函数可以触发hook:module_invoke()module_invoke_all()module_implements()drupal_alter()。这使得查找实现某一扩展的所有代码非常困难。

  • Hook缺乏一致性:有些"info"类的hook,提供一个数组(或者数组的数组的数组..),有些是“事件”类型的hook,在某些事情发生的时候触发。需要阅读每个hook的文档来确定各个hook需要的输入和输出。

Drupal 8:事件

Hooks在Drupal 8的多数事件驱动行为中还是普遍存在的(info风格的hook被迁移到了YAML或者插件标注中),Drupal 8中迁移到Symfony(例如bootstrap/exit, 路由系统)的部分也随之转换为Symfony事件分发系统。在这个系统中,事件在某个逻辑触发的时候进行分发,模块可以订阅需要进行交互的事件。

下面的演示,是Drupal 8的配置API,位于core/lib/Drupal/Core/Config/Config.php。他定义了一系列的'CRUD'方法,例如save()、delete()等。每个方法在结束任务之后都会分发一个事件,让其他模块可以做出反应。例如Config::save():

      public function save() {
        // <snip>Validate the incoming information.</snip>
        // Save data to Drupal, then tell other modules this was just done so they can react.
        $this->storage->write($this->name, $this->data);
        // ConfigCrudEvent is a class that extends from Symfony's "Event" base class.
        $this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
      }

最少有一个模块在配置变化时候需要做出反应:核心的语言模块。因为如果刚刚发生变更的配置中包含了缺省的站点语言,会需要清除PHP缓存文件来让配置生效.

要达到这个目的,语言模块要做三件事:

  • 在language.services.yml文件(Symfony服务容器的配置文件,用于注册可重用代码)中注册一个订阅类:
    language.config_subscriber:
      class: Drupal\language\EventSubscriber\ConfigSubscriber
      tags:
        - { name: event_subscriber }
  • 引用类中实现EventSubscriberInterface,并声明一个getSubscribedEvents方法,这个方法列出要处理的事件,并为每个事件提供一个或多个回调函数来进行处理,并提供权重,来决定调用顺序(Symfony的权重顺序同Drupal的相反):
    class ConfigSubscriber implements EventSubscriberInterface {
      static function getSubscribedEvents() {
        $events[ConfigEvents::SAVE][] = array('onConfigSave', 0);
        return $events;
      }
    }
  • 定义回调方法,用于处理配置保存触发的事件:
      public function onConfigSave(ConfigCrudEvent $event) {
        $saved_config = $event->getConfig();
        if ($saved_config->getName() == 'system.site' && $event->isChanged('langcode')) {
          // Trigger a container rebuild on the next request by deleting compiled
          // from PHP storage.
          PhpStorageFactory::get('service_container')->deleteAll();
        }
      }

上面的方式给开发者提供了一个明确的注册工具,让一个模块可以用多个类订阅不同的事件。这避免了过去我们经常要在hook中使用switch语句的情况,也避免了同一语句块中大量的不相干代码对开发人员造成的困扰,让我们可以用不同的类来处理不同的逻辑。这同时也意味着我们的事件机制是后加载的,而不是随时保存在PHP的内存中。

对事件进行调试,以及查找其实现代码也变得非常直观。从前需要在一大堆的过程化PHP函数中鉴别是否被hook调用,而现在只需要简单的查找注册事件即可,例如ConfigEvents::SAVE。

事件系统真正完成了到面向对象的转换。插件系统处理了info风格的hook,YAML代替了过去的注册系统,而事件系统则取代了事件风格的hook,并引入了强大的订阅机制,进一步提升了Drupal核心的扩展能力。

还有很多

Drupal API的变更可以在Drupal 8 API页面看到,这里你能找到分门别类的Drupal 8 API介绍:

drupal8 api

还可以浏览https://drupal.org/list-changes来查看完整的Drupal 7和Drupal 8的API差异(载入时间貌似比较长)。每个API的变化都包含了变化前和变化后的代码示例,可以帮助你进行迁移,并指出这个变更所对应的issue,介绍变更的内容和原因。

drupal 8 change record

打了好多字

转向现代的,面向对象的新Drupal,会多出很多啰嗦的问题。下面列出一些项目能够协助你跨越这些障碍:

  • Drupal Module Upgrader:如果要把Drupal 7模块升级到Drupal 8,一定要看看这个项目,他会告诉你哪里需要变更(指向相关的变更说明),或者自动的把你的代码转换到Drupal 8。你可以在视频中更多的了解该项目。

  • Console,对新手来说非常有用,自动生成.module/.info文件,PSR-4目录结构,YAML以及路由注册等内容。

  • 多数Drupal 8核心开发者极其信赖PhpStorm IDE,这个IDE的最新版本对Drupal开发提供了大量新功能。另外,如果你是Drupal的顶尖贡献者,可以免费获取(这不是广告,你可以加入 #drupal-contribute,看看会不会有一个小时没人提到PhpStorm。)

原文链接: https://www.acquia.com/blog/ultimate-guide-drupal-8-episode-7-code-changes-drupal-8