跳转到主要内容
希望之翼 提交于 29 April 2014

Drupa 8 即将到来,目前看来发布日期依然一波三折。其中一个最大的顾虑就是一大堆api做了更改(Symfony, OOP, PSR-0/PSR-4, Twig, YAML等等),使得面向过程和函数的传统程序员不再适应。

诚然如此,这也是一个让大家去学习面向对象的好机会。作为一个程序猿,知识永远是你最犀利的武器。

今天,我将带领大家领略如何升级simple dialog模块到drupal 8.

升级步骤

我们先分析下simple dialog目前用到了哪些drupal 7的api。
  1. 配置表单:用到了drupal_get_form()来生成配置表单
  2. 路径:用到了hook_menu()来生成路径
  3. JS/CSS:用到了hook_init来加载js,css
  4. theme:用到了一个theme函数
  5. 帮助:用到了hook_help()来生成帮助信息
下面我们就按照上面的列表,来一步一替换的升级。

基础知识

Drupal 8提供了两个地方来存放模块,可以放到sites/all/modules或modules。

info文件现在由yaml格式来编写了, 格式如下:

name: Simple Dialog
type: module
description: 'Provides an API to create simple modal dialogs. Leverages the jQuery ui dialog plugin included with Drupal.'
core: 8.x
package: User interface
configure: simple_dialog.settings

配置API

下面我们来添加配置功能,8使用了一个全新的配置管理,我们需要在模块目录内创建一个config目录,然后创建simple_dialog.settings.yml来保存配置。

js_all: true
classes: ''
defaults:
  settings: 'width:300;height:auto;position:[center,60]'
  target_selector: 'content'
  title: ''

接着我们要创建数据表,文件路径为:

<simple_dialog root>/config/schema/simple_dialog.schema.yml

# Schema for configuration files of the Simple Dialog module.

simple_dialog.settings:
  type: mapping
  label: 'Simple Dialog settings'
  mapping:
    js_all:
      type: boolean
      label: 'Add simple dialog javscript files to all pages'
    classes:
      type: string
      label: 'Additional Classes'
    defaults:
      type: mapping
      label: 'Defaults'
      mapping:
        settings:
          type: string
          label: 'Default Dialog Settings'
        target_selector:
          type: string
          label: 'Default Target Selector'
        title:
          type: string
          label: 'Default Dialog Title'

配置管理form

以往通过一个全局函数来定义form的方式可以忘记了,我们现在需要用到ConfigFormBase类来创建form。 创建文件<simple_dialog root>/lib/Drupal/simple_dialog/Form/SimpleDialogSettingsForm.php
namespace Drupal\simple_dialog\Form;
use Drupal\Core\Form\ConfigFormBase;
 
/**
 * Defines a form to configure maintenance settings for this site.
 */
class SimpleDialogSettingsForm extends ConfigFormBase {
 
  /**
   * {@inheritdoc}
   */
  public function getFormID() {
    return 'simple_dialog_settings_form';
  }
 
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &$form_state) {
    $config = $this->config('simple_dialog.settings');
 
    $form['javascript']['js_all'] = array(
      '#type' => 'checkbox',
      '#title' => $this->t('Add simple dialog javscript files to all pages'),
      '#description' => t("This setting is for people who want to limit which pages the simple dialog javscript files are added to. If you disable this option, you will have to add the js files manually (using the function simple_dialog_add_js() ) to every page that you want to be able to invoke the simple dialog using the 'simple-dialog' class. If you are adding simple dialog links to the page using theme('simple_dialog'...) the necessary javascript is added within those functions so you should be okay.'"),
      '#default_value' => $config->get('js_all'),
    );
 
    $form['classes'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Additional Classes'),
      '#description' => t("Supply a list of classes, separated by spaces, that can be used to launch the dialog. Do not use any leading or trailing spaces."),
      '#default_value' => $config->get('classes'),
    );
 
    $form['default_settings'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Default Dialog Settings'),
      '#description' => t('Provide default settings for the simple dialog. The defaults should be formatted the same as you would in the "rel" attribute of a simple dialog link. See the <a href="/admin/help/simple_dialog">help page</a> under "HTML Implementation" for more information.'),
      '#default_value' => $config->get('defaults.settings'),
    );
 
    $form['default_target_selector'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Default Target Selector'),
      '#description' => t('Provide a default html element id for the target page (the page that will be pulled into the dialog). This value will be used if no "name" attribute is provided in a simple dialog link.'),
      '#default_value' => $config->get('defaults.target_selector'),
    );
 
    $form['default_title'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Default Dialog Title'),
      '#description' => t('Provide a default dialog title. This value will be used if no "title" attribute is provided in a simple dialog link.'),
      '#default_value' => $config->get('defaults.title'),
    );
    return parent::buildForm($form, $form_state);
  }
 
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {
    $this->config('simple_dialog.settings')
      ->set('js_all', $form_state['values']['js_all'])
      ->set('classes', $form_state['values']['classes'])
      ->set('defaults.settings', $form_state['values']['default_settings'])
      ->set('defaults.target_selector', $form_state['values']['default_target_selector'])
      ->set('defaults.title', $form_state['values']['default_title'])
      ->save();
    parent::submitForm($form, $form_state);
  }
}

让我来解释下代码。

namespace Drupal\simple_dialog\Form;
use Drupal\Core\Form\ConfigFormBase;

首先,我们要申明本文件以及包含的类。这个名称遵循PSR-0标准, 与模块的lib路径结构匹配(<simple_dialog root>/lib/Drupal/simple_dialog/Form/SimpleDialogSettingsForm.php)
public function getFormID() {
  return 'simple_dialog_settings_form';
}

getFormID方法返回了form的id。

public function buildForm(array $form, array &$form_state) {
  $config = $this->config('simple_dialog.settings');
  // ... form definition
  return parent::buildForm($form, $form_state);
}

buildForm方法是你使用form api来构造你的form的地方。

public function submitForm(array &$form, array &$form_state) {
  $this->config('simple_dialog.settings')
    ->set('js_all', $form_state['values']['js_all'])
    ->set('classes', $form_state['values']['classes'])
    ->set('defaults.settings', $form_state['values']['default_settings'])
    ->set('defaults.target_selector', $form_state['values']['default_target_selector'])
    ->set('defaults.title', $form_state['values']['default_title'])
    ->save();
  parent::submitForm($form, $form_state);
}

submitForm方法处理form的submit逻辑。

 

路径

路径在8里面也改动比较大,hook_menu()悲剧的消失了。要创建路径,首先要创建文件 simple_dialog.routing.yml 

simple_dialog.settings:
  path: '/admin/config/content/simple-dialog'
  defaults:
    _form: '\Drupal\simple_dialog\Form\SimpleDialogSettingsForm'
  requirements:
    _permission: 'administer simple dialog'

上面代码里我们做了如下定义:

  1. 路径名字为 "simple_dialog.settings"
  2. 路径指向form "\Drupal\simple_dialog\Form\SimpleDialogSettingsForm"
  3. 你必须有'administer simple dialog'权限才能访问此路径

看起来很简单吧?现在我们来创建menu。创建文件simple_dialog.menu_links.yml

simple_dialog.settings:
  title: Simple Dialog
  description: 'Configure default settings for simple dialogs'
  parent: system.admin_config_ui
  route_name: simple_dialog.settings

这个基本很好理解,不用多加阐述。

接下来定义权限,hook_permission()居然还能用,泪奔~

/**
 * Implements hook_permission().
 */
function simple_dialog_permission() {
  return array(
    'administer simple dialog' => array(
      'title' => t('Administer Simple Dialog'),
    ),
  );
}

添加CSS/JS

hook_init在8里面同样牺牲了,那换个函数,用drupal_add_js(), drupal_add_css() drupal_add_library()?

这些同样消失了。。最终找到了个幸存者hook_page_build()。

 

/**
 * Implements hook_page_build()
 */
function simple_dialog_page_build(&$page) {
  $path = drupal_get_path('module', 'simple_dialog');
  // Add JavaScript/CSS assets to all pages.
  // @see drupal_process_attached()
  $page['#attached']['css'][$path . '/css/simple_dialog.css'] = array('every_page' => TRUE);
  if (\Drupal::config('simple_dialog.settings')->get('js_all')) {
    simple_dialog_attach_js($page, TRUE);
  }
}
 
/**
 * Adds the necessary js and libraries to make the
 * dialog work. Really just adds the jquery.ui
 * library and the simple dialog javscript file
 * but if we need to add anything else down the road,
 * at least it's abstracted into an api function
 *
 * @param array $element
 *        The renderable array element to #attach the js to
 *
 * @param boolean $every_page
 *        Optional variable to specify the simple dialog code should be added
 *        to every page. Defaults to false. If you're calling this function,
 *        you likely will not need to change this as the module has settings
 *        to specify adding the js on every page
 */
function simple_dialog_attach_js(&$element, $every_page = FALSE) {
  $element['#attached']['library'][] = 'system/ui.dialog';
  $element['#attached']['js'][] = array(
    'data' => array('simpleDialog' => array(
      'classes' => \Drupal::config('simple_dialog.settings')->get('classes'),
      'defaults' => array(
        'settings' => \Drupal::config('simple_dialog.settings')->get('defaults.settings'),
        'target_selector' => \Drupal::config('simple_dialog.settings')->get('defaults.target_selector'),
        'title' => \Drupal::config('simple_dialog.settings')->get('defaults.title'),
      ),
    )),
    'type' => 'setting',
  );
  $element['#attached']['js'][drupal_get_path('module', 'simple_dialog') . '/js/simple_dialog.js'] = array('every_page' => $every_page);
}

这里面,variable_get()也不能用了,我们使用了Drupal这个类的方法config().

 

主题

在7版本的时候,我给模块加了点theme函数,虽然这个theme函数并不是必须,但显得我专业不是?!

好了,那我们继续贴出升级后的theme函数.

/**
 * Implements hook_theme().
 */
function simple_dialog_theme($existing, $type, $theme, $path) {
  return array(
    'simple_dialog_link' => array(
      'variables' => array(
        'text' => NULL,
        'path' => NULL,
        'selector' => NULL,
        'title' => NULL,
        'options' => array(),
        'link_options' => array(),
        'class' => array(),
      ),
      'template' => 'simple-dialog-link',
    ),
  );
}

看出来hook_theme()其实没怎么更改,不过template文件就没那么轻松了,

以下是使用twig后的模板。

{#
/**
 * @file
 * Default theme implementation to print a simple dialog link
 *
 * Available variables:
 *   - text: The link text for the anchor tag.
 *   - path: The URL to pull the dialog contents from.
 *   - attributes: The attributes for the link compiled in a preprocessor
 *
 * @see template_preprocess_simple_dialog_link()
 *
 * @ingroup themeable
 */
#}
<a href="{{ path }}" {{ attributes }}>{{ text }}</a>

最后

hook_help()一点不用变,就可以在8下面工作,我们顺利完工! 骚年,看你的目录结构是否是这样的:  

作业

升级一个小模块到D8。   原文:http://fuseinteractive.ca/blog/watch-i-try-upgrade-module-drupal-8-what-happens-next-you-wont-believe#.U19dSfmSzvR

 

Drupal 版本