Drupal模块讲解-Authcache缓存原理详解教程

 更新时间:2016年11月25日 16:19  点击:1798
本教程来详解Drupal的Authcache缓存模块的缓存机制原理。Drupal带有内置的缓存模块:Boost、Authcache,但是这两个模块的原理不同,boost是生成静态页面,Authcache是生存页面缓存。

相关文章:Drupal的模块高级应用之Authcache-动态加载内容教程

Authcache模块和Boost模块的原理不一样,Boost模块是生成静态页面,所以缓存的效果最好,速度最快。Authcache模块是利用Drupal自身的缓存机制,生成页面缓存,由于进入到了Drupal环节,因此速度没有Boost缓存快,但是优点就是可以灵活的使用PHP/Drupal相关方法,动态处理数据。

Drupal模块讲解-Authcache缓存原理详解教程


首先,我们从Drupal的bootstrap讲起。

function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
  // Not drupal_static(), because does not depend on any run-time information.
  static $phases = array(
    DRUPAL_BOOTSTRAP_CONFIGURATION,
    DRUPAL_BOOTSTRAP_PAGE_CACHE,
    DRUPAL_BOOTSTRAP_DATABASE,
    DRUPAL_BOOTSTRAP_VARIABLES,
    DRUPAL_BOOTSTRAP_SESSION,
    DRUPAL_BOOTSTRAP_PAGE_HEADER,
    DRUPAL_BOOTSTRAP_LANGUAGE,
    DRUPAL_BOOTSTRAP_FULL,
  );
….
}

这是Drupal自带的bootstrap的几个环节(Drupal7),从CONFIGURATION、一直到 FULL,这样整个Drupal就启动了,所有的模块也加载了。
其中我们发现,有一个环节叫 PAGE_CACHE,我们来把这个阶段的处理函数完整的贴出来,以便大家能更好的理解这段代码。

function _drupal_bootstrap_page_cache() {
  global $user;
 
  // Allow specifying special cache handlers in settings.php, like
  // using memcached or files for storing cache information.
  require_once DRUPAL_ROOT . '/includes/cache.inc';
  foreach (variable_get('cache_backends', array()) as $include) {
    require_once DRUPAL_ROOT . '/' . $include;
  }
  // Check for a cache mode force from settings.php.
  if (variable_get('page_cache_without_database')) {
    $cache_enabled = TRUE;
  }
  else {
    drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE);
    $cache_enabled = variable_get('cache');
  }
  drupal_block_denied(ip_address());
  // If there is no session cookie and cache is enabled (or forced), try
  // to serve a cached page.
  if (!isset($_COOKIE[session_name()]) && $cache_enabled) {
    // Make sure there is a user object because its timestamp will be
    // checked, hook_boot might check for anonymous user etc.
    $user = drupal_anonymous_user();
    // Get the page from the cache.
    $cache = drupal_page_get_cache();
    // If there is a cached page, display it.
    if (is_object($cache)) {
      header('X-Drupal-Cache: HIT');
      // Restore the metadata cached with the page.
      $_GET['q'] = $cache->data['path'];
      drupal_set_title($cache->data['title'], PASS_THROUGH);
      date_default_timezone_set(drupal_get_user_timezone());
      // If the skipping of the bootstrap hooks is not enforced, call
      // hook_boot.
      if (variable_get('page_cache_invoke_hooks', TRUE)) {
        bootstrap_invoke_all('boot');
      }
      drupal_serve_page_from_cache($cache);
      // If the skipping of the bootstrap hooks is not enforced, call
      // hook_exit.
      if (variable_get('page_cache_invoke_hooks', TRUE)) {
        bootstrap_invoke_all('exit');
      }
      // We are done.
      exit;
    }
    else {
      header('X-Drupal-Cache: MISS');
    }
  }
}

当我们看到最下面,exit ;(We are done)之处,我们就知道,Drupal已经处理完了请求,后面的环境(Session、数据库、模块、FULL)等环节就不用启动了,因此大大节省了服务器的处理时间和提高了响应时间。

这就是Drupal自带的缓存处理机制!!

Drupal自带的缓存机制缺点也很明显,就是只对匿名用户有效。

因此,Authcache模块就出现了,Authcache就是利用Drupal自带的缓存机制,实现对登录用户的缓存。

继续看上面的代码,其中有3行,如下:

foreach (variable_get('cache_backends', array()) as $include) {
  require_once DRUPAL_ROOT . '/' . $include;
}

其中,获取’cache_backends’的时候,加载了一个数组变量,所以在Drupal自身的缓存阶段要使用到authcache,那就必须修改这个 cache_backends。
果如其然,如下所示,我们在安装authcache的时候,就必须设置如下变量。

$conf['cache_backends'][] = 'sites/all/modules/authcache/authcache.cache.inc';
$conf['cache_backends'][] = 'sites/all/modules/authcache/modules/authcache_builtin/authcache_builtin.cache.inc';

这个时候,我们就加载进了authcache.cache.inc和文件了。

继续…
我们打开authcache.cache.inc 其中,就是定义一些函数。
继续查看authcache_builtin.cache.inc文件,看到如下代码:

$delivered = authcache_builtin_cacheinc_retrieve_cache_page();
if ($delivered) {
  exit;
}

也就是说在这个时候,如果命中了缓存就直接输入页面内容,不再继续boot!这个地方也就代替了原本Drupal自己查找缓存和计算命中缓存的逻辑,使用authcache自己的算法,根据用户的角色不同,使用的缓存不同。
这就是authcache的核心!

当然authcache还可以做更多,比如,
1. 根据用户不同,生产不同的缓存(需要处理)。
2. 配合authcache_p13n模块,动态处理某些局部页面,比如某个block。
3. 修改缓存的某个些内容。(稍后会详细讲解)
等等,这就是authcache比boost灵活的地方,当然也是缺点,需要调用很多PHP、数据库等等,肯定比boost慢一些。

PHP APC提供两种缓存功能,即缓存Opcode(目标文件),我们称之为apc_compiler_cache。同时它还提供一些接口用于PHP开发人员将用户数据驻留在内存中,我们称之为apc_user_cache,下面我们来看看PHP APC缓存安装配置.

最简单的方法,找到php安装目录的pecl

自动安装

# /usr/local/php/bin/pecl install apc

接下来按提示一步步完成即可

然后配置 /usr/local/php/etc/php.ini  末尾加入

extension=apc.so
 
手动安装

官网:http://cn2.php.net/manual/zh/book.apc.php
下载:http://pecl.php.net/package/APC 找最新的

下载apc

# wget http://pecl.php.net/get/APC-3.1.13.tgz
# tar -zxvf APC-3.1.13.tgz
# cd APC-3.1.13

生成configure文件

# /usr/local/php/bin/phpize

 执行configure 并且make && make install

# ./configure --enable-apc --enable-apc-mmap --with-php-config=/usr/local/php/bin/php-config
# make
# make install

拷贝添加SO文件

# cp /usr/local/php/lib/php/extensions/no-debug-non-zts-20100525/apc.so /usr/local/php/lib/php/extensions/apc.so
# chmod 755 /usr/local/php/lib/php/extensions/apc.so

 修改php.ini使之启动APC模块

# vi /usr/local/php/etc/php.ini
extension_dir = "/usr/local/php/lib/php/extensions"
extension=apc.so
apc.enabled = 1
; apc.cache_by_default = on
; apc.shm_segments = 1
; apc.shm_size = 128
; apc.ttl = 7200
; apc.user_ttl = 7200
; apc.num_files_hint = 1024
; apc.write_lock = On
; apc.gc_ttl=3600
; apc.ttl=0
; apc.mmap_file_mask=/tmp/apc.XXXXXX

 重启apache

# service httpd restart
 
使用APC

test.php

<?php
print_r(apc_cache_info());

注意 在浏览器看会不是很友好,可以查看网页源代码就看到很清晰

Array
(
    [num_slots] => 1031
    [ttl] => 0
    [num_hits] => 3
    [num_misses] => 1
    [num_inserts] => 1
    [expunges] => 0
    [start_time] => 1398341530
    [mem_size] => 4240
    [num_entries] => 1
    [file_upload_progress] => 1
    [memory_type] => mmap
    [locking_type] => pthread mutex Locks
    [cache_list] => Array
        (
            [0] => Array
                (
                    [type] => file
                    [device] => 64768
                    [inode] => 1179758
                    [filename] => /data/www/www.111cn.net /test.php
                    [num_hits] => 3
                    [mtime] => 1398341609
                    [creation_time] => 1398341617
                    [deletion_time] => 0
                    [access_time] => 1398341630
                    [ref_count] => 1
                    [mem_size] => 4240
                )
 
        )
 
    [deleted_list] => Array
        (
        )
 
    [slot_distribution] => Array
        (
            [109] => 1
        )
 
)

多次点击,可以发现 num_hits  在变化,说明缓存命中了!

压力测试看效果

关闭开启apc分别压力测试对比一下你会发现开启apc之后吞吐率会比没开启apc要提高许多。

# ab -n1000 -c10 http://www.111cn.net /test.php

网站为了提高性能,一般会采用缓存。Drupal中可以实现游客缓存,如果装上Authcache模块可以加速用户登录响应,对不同的role进行动态加载缓存。以下是教程详细过程。

相关文章:Drupal模块讲解-Authcache缓存原理详解教程

本文讲一下如果通过修改authcache的核心代码,来实现缓存页面的个性化内容。

Drupal的模块<a href=高级应用之Authcache-动态加载内容教程" />


通用的缓存,或多或少都是要进行个性化处理的,比如用户名显示、动态加载用户资料、用户好友等等。

一般情况下,这种局部个性化,都是通过两种手段实现:一个是SSI,另一个是CSI。

Authcache本身可以实现局部personalization, 模块叫p13n。

Authcache的ajax模块属于CSI,ESI模块应该是属于SSI,但是由于ESI模块需要搭建varnish服务器,配置VCL,加上服务器的设置问题,会导致ESI容易出错,并且本身ESI传递cookie也会有些问题,因此ESI实际上实现起来相当复杂。

所以,如果我们要使用服务器端的personalization,通过PHP修改根据某些条件修改某些内容的话,需要hack一些authcache的代码。
1. autcache.module文件

找到下面一句,Line 188
// Invoke cache backends and serve page.
修改成如下:

 代码如下 复制代码
 // Invoke cache backends and serve page.
  if (authcache_page_is_cacheable()) {
    $cache = authcache_backend_cache_save();
    authcache_serve_page_from_cache($cache, authcache_key());
  }
  else {
    ////process html result
    global $conf;
    $conf['page_compression'] = FALSE;
 
    $cache = new stdClass();
 
    ////process html result
    $cache->data['body'] = ob_get_contents();
    ob_clean();
 
    foreach (variable_get('authcache_page_process', array()) as $include) {
      require_once DRUPAL_ROOT . '/' . $include;
    }
    foreach (variable_get('authcache_page_process_interface', array()) as $process) {
      require_once DRUPAL_ROOT . '/' . $include;
      if (is_callable($process)) {
        $process($cache);
      }
    }
    echo $cache->data['body'];
  }
  exit;
}


其中,主要是加了else后面的处理代码。
2. authcache.cache.inc文件

从85行开始,到函数结尾,修改成如下格式。

 

 代码如下 复制代码
  $return_compressed = FALSE; ///NEW //Don't send compressed content
 
  if ($page_compression) {
    header('Vary: Accept-Encoding', FALSE);
    // If page_compression is enabled, the cache contains gzipped data.
    if ($return_compressed) {
      // $cache->data['body'] is already gzip'ed, so make sure
      // zlib.output_compression does not compress it once more.
      ini_set('zlib.output_compression', '0');
      header('Content-Encoding: gzip');
    }
    else {
      // The client does not support compression, so unzip the data in the
      // cache. Strip the gzip header and run uncompress.
      $cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8));
    }
  }
 
  ///NEW
  foreach (variable_get('authcache_page_process', array()) as $include) {
    require_once DRUPAL_ROOT . '/' . $include;
  }
  foreach (variable_get('authcache_page_process_interface', array()) as $process) {
    if (is_callable($process)) {
      $process($cache);
    }
  }



注意,有两个地方,///NEW 标注,表示新加的内容,中间有一段是原有的code。

改完之后,我们就完工了。
如何使用呢?

新建一个文件,比如在custom模块下面,叫custom_authcache.inc,黏贴如下代码:

 代码如下 复制代码
<?php
/**
Add the following lines to settings.php
 
$conf['authcache_page_process'][] = 'sites/all/modules/custom/custom/custom_authcache.inc';
$conf['authcache_page_process_interface'][] = 'custom_authcache_common_process';
 
If you want to add more process interface, add your function name as an item in this array, $conf['authcache_page_process_interface'].
If you want to include file, please add file name to this array, $conf['authcache_page_process']
 
Core Changes:
modules/authcache/authcache.cache.inc
modules/authcache/authcache.module
**/
 
/*
* Process authcache content to replace content
*/
function custom_authcache_common_process(&$cache) {
  $cache->data['body'] = str_ireplace('<span id="replace_placeholder_1"/>', _get_real_data(), $cache->data['body']);
}


看上面的注释,复制两行代码到settings.php文件。
具体的说明注释已经很详细了,相信应该没问题。

这样,这个custom_authcache_common_process函数就可以动态替换HTML里面的内容了,达到了个性化页面的目的。

前页我们讲了 Drupal8模块开发之路由、控制器和菜单链接教程 ,现在我们将学习进一步开发Drupal8模块,区块和表单。

上一教程:Drupal8模块开发之路由、控制器和菜单链接教程

在本教程中,我们将学习进一步的开发,我们可以在这个库 (link is external)里找到我们需要的沙盒模块代码示例,里面两个重要的新功能:区块和表单。

为此,我们将创建一个自定义区块来返回一些可配置的文本,在那之后,我们将创建 一个简单的表单来打印输出用户提交到的数据到屏幕上。

 不知道怎么下载这个库的同学看这里:

2014-07-22_074331.jpg

2014-07-22_074244.jpg

Drupal8 区块

在Drupal8里,有一个很酷的新变化时,以往的Block(区块) API 已经转换成了插件的形式(一个全新的概念),这意味着区块将是可被重复使用的功能块(在引擎下),

你现在可以在后台UI界面创建一个Block区块,并在整个网站的任何地方重用它,你现在不会再局限于只能使用一次这个区块。

让我们先创建一个简单的区块来打印"Hello World"到屏幕上! 一般情况下,我们首先需要在模块根文件夹下的src/Plugin/Block目录下创建一个class类

文件DemoBlock.php,我们这里暂且把这个新的区块命名为"DemoBlock", 所以你最终在这个文件中,看到的代码会是像这样:

 

 代码如下 复制代码
<?php
 
namespace DrupaldemoPluginBlock;

use DrupalblockBlockBase;
use DrupalCoreSessionAccountInterface;
 
/**
 * Provides a 'Demo' block.
 *
 * @Block(
 *   id = "demo_block",
 *   admin_label = @Translation("Demo block"),
 * )
 */
 
class DemoBlock extends BlockBase {
  
  /**
   * {@inheritdoc}
   */
  public function build() {   
    return array(
      '#markup' => $this->t('Hello World!'),
    );
  }
  
  /**
   * {@inheritdoc}
   */
  public function access(AccountInterface $account) {
    return $account->hasPermission('access content');
  } 
  
}




像所有其他的class类文件一样,我们要先在开始时先声明类的命名空间。然后,我们使用BlockBase类以便于我们等下可以继承和扩展它,以及也需要使用AccountInterface类以便于让我们可以获取当前登录的用户。然后接下来的一些代码你肯定没有在Drupal 7见过,这就是:annotations注释。

Annotations注释是一个PHP发现工具,使用这些注释,我们可以让Drupal知道我们想注册一个新的区块(@ Block),我们设定了Id号:demo_block 和admin_label :Demo block(这些注视将会通过注视翻译系统被drupal识别)。

 接下来,我们在DemoBlock类里扩展下BlockBase类,我们将实现两个方法(都是最常见的),build()方法是最重的,因为它返回一个可打印的区块信息数组,access()方法控制查看此区块的访问权限,这里传递给它的参数是AccountInterface类的一个实例,在这里,实际传递的就是当前用户信息参数。

 另外一个有趣的事情需要注意的是,我们不再像以前一样使用全局t()函数直接翻译文本了,但是我们会继承使用父类的t()方法,所以你看到的是$this->t().

 就是这样,你现在可以清空缓存,然后进入区块的配置页,最酷的是你现在已经成功创建了一个区块,并且你可以把这个区块放在任何网站里你设定的regions区域。

Drupal 8 区块配置

现在我们已经知道了如何创建一个新的区块,让我们进一步学习下如何用API为它添加配置表单,我们将让你可以编辑此区块,给你一个文本输入框,让你可以输入一个名字,然

后前台就会显示"Hello 名字"来替换 "Hello World".

首先,我们需要定义一个文本框,所以在我们的DemoBlock类里,我们可以添加一个新的方法叫做blockForm():

 代码如下 复制代码
/**
 * {@inheritdoc}
 */
public function blockForm($form, &$form_state) {
  
  $form = parent::blockForm($form, $form_state);
  
  $config = $this->getConfiguration();
 
  $form['demo_block_settings'] = array(
    '#type' => 'textfield',
    '#title' => $this->t('Who'),
    '#description' => $this->t('Who do you want to say hello to?'),
    '#default_value' => isset($config['demo_block_settings']) ? $config['demo_block_settings'] : '',
  );
  
  return $form;
}


这个表单API应该看起来很熟悉,似乎和以往的drupal 7里的差不多,然而,细看之下,你会发现有一些新的东西在里面,首先,我们从父类里获取$form数组(所以我们是在已存在

的表单基础上新增创建我们的字段),标准的面向对像,然后,我们通过BlockBase类定义的getConfiguration()方法来获取配置信息,并且我们把demo_block_settings的值作为了

#default_value的默认值.

接下来,到了我们要对我们传递的字段值进行提交处理并保存进区块配置里的时候了:

 代码如下 复制代码

/**
* {@inheritdoc}
*/
public function blockSubmit($form, &$form_state) {
 
 $this->setConfigurationValue('demo_block_settings', $form_state['values']['demo_block_settings']);
 
}



这个方法也存在于DemoBlock类里,它被用于保存demo_block_settings的值(请注意保持键名的一致性)

最后我们需要调整我们的build()方法,让它在打印Hello时包含name名字:

 代码如下 复制代码


/**
 * {@inheritdoc}
 */
public function build() {
  
  $config = $this->getConfiguration();
  
  if (isset($config['demo_block_settings']) && !empty($config['demo_block_settings'])) {
    $name = $config['demo_block_settings'];
  }
  else {
    $name = $this->t('to no one');
  }
  
  return array(
    '#markup' => $this->t('Hello @name!', array('@name' => $name)),
  ); 
}



现在,这应该看起来相当容易,我们获取区块的配置信息,如果我们的字段值存在,我们就把它打印出来,如果不存在,就打印"to no one", 你现在可以清空缓存后,编辑区块,填写一个值,然后保存,然后你就能在前台看到" Hello + 名字值"了。

 
Drupal 8 表单

最后,我们将在本教程中,探讨如何创建一个简单的表单,由于空间的限制,我不会覆盖它的配置管理(通过表单提交存储值),相反,我将举例说明一个简单的表单设定,然后让提交的值简单的打印在屏幕上,以演示它可以正常工作。

在Drupal 8 里,表单定义函数都在一个class类里被组织在一起,所以让我们定义一个简单的DemoForm类,在src/Form/DemoForm.php中:

 代码如下 复制代码

<?php
 
/**
 * @file
 * Contains DrupaldemoFormDemoForm.
 */
 
namespace DrupaldemoForm;
 
use DrupalCoreFormFormBase;
 
class DemoForm extends FormBase {
  
  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'demo_form';
  }
  
  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, array &$form_state) {
    
    $form['email'] = array(
      '#type' => 'email',
      '#title' => $this->t('Your .com email address.')
    );
    $form['show'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    );
    
    return $form;
  }
  
  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, array &$form_state) {
    
    if (strpos($form_state['values']['email'], '.com') === FALSE ) {
      $this->setFormError('email', $form_state, $this->t('This is not a .com email address.'));
    }
  }
  
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {
    
    drupal_set_message($this->t('Your email address is @email', array('@email' => $form_state['values']['email'])));
  }
  
}


除了OOP思想的改变,一切应该看起来像以前的drupal 7 一样熟悉。Form API 仍然基本没有什么变化(除了一些新的表单元素的加入和相关class类的封装),所以会发生什么?

首先,我们使用了命名空间的类和核心的FormBase类,所以接下来我们就可以在我们自己的DemoForm类里扩展它,然后我们实现了4个方法,其中3个应该是很熟悉的。 getFormid()方法是新增的并且是必须要有的,这个方法用于简单的返回form的机器名称,buildForm()方法也是必须要有的,它用于构建一个表单,怎么用?其实就和你之前在drupal 7中的构建方法差不多,validateForm()方法是可选的,它像drupal 7里一样,用于验证表单提交数据,最后,submitForm()方法用于处理提交的表单数据,一切是不是很合乎逻辑组织。

所以,我们想用这个表单实现什么?我们有一个email邮件字段(Drupal 8的新表单元素),我们希望用户填写它,默认情况下,Drupal会检查是否输入了一个正确的邮箱格式的值,但在我们的验证函数里我们会确保它是一个合法的带.com后缀的邮箱地址,如果不是,我们会在字段上设置了一个错误提示,最后提交处理,然后打印一个提示消息在网页上。

为了使用这个表单,我们需要做最后一件事,就是提供为这个表单提供一个路由,所以请编辑demo.routing.yml文件,并增加以下代码:

 代码如下 复制代码

demo.form:
  path: '/demo/form'
  defaults:
    _form: 'DrupaldemoFormDemoForm'
    _title: 'Demo Form'
  requirements:
    _permission: 'access content'


这应该看起来很熟悉,因为我们之前有篇文章介绍过怎么路由一个简单页面,唯一最大的区别是, 在 defaults下面,我们用_form替代了之前的_content,并为_form指定了一个表单类,

这个类就是刚刚我们创建的。

清空缓存后就可以输入地址导航到demo/form,然后就可以看到这个表单并测试它。

如果你熟悉以前drupal 7中的drupal_get_form,也知道怎样加载一个表单的方式,因此,你就可以使用drupal 8中的全局Drupal类 (link is external),比如formBuilder()方法来获取一个表单,像这样

 代码如下 复制代码
$form = Drupal::formBuilder()->getForm('DrupaldemoFormDemoForm');



然后你就可以返回$form 并渲染输出了。

 
结论


在这篇文章中,我们继续探索了Drupal 8模块开发的两个新的话题:区块和表单。我们已经看到了如何创建我们自己的区块(然后你就可以在区块管理界面创建新区块)。我们还学会了如何添加和存储一个自定义的配置值(这个值你可以在稍后使用它)。在表单的话题中,我们看到了一个简单的FormBase类的实现,我们用来把用户提交的数据打印到屏幕上。

 

随着 php5 面向对象的完善,现在很多php主流框架都往面向对象发展。Drupal8为了适应新php主题框架的需要,也将用面向对象的方式取代面向过程,在Proudly Found Elsewhere的倡议下,Dupal8包含了很多看起来不像是为了drupal开发的的代码。

其中最大的变化是引入了Symfony框架,这对开发人员来说,会产生两个重大影响

首先,这可能将会大大增加Drupal开发者的数量,比如那些曾经自认为是大师,不屑于面向过程开发的传统"高端程序猿",现在Drupal8,也是面向对像了,也用了新框架,他们已经没有理由拒绝加入drupal开发者的行列

第二,新的框架内核变化,会给现在那些没有多少PHP现代框架开发经验的人许多恐惧,他们会担心自己不能适应这种开发方式的巨大转变,但是没关系,我们都需要重新学习,学习Symfony框架,还有充满希望的Drupal8, 像其他PHP框架一样,它们都是很容易扩展的,我们要给自己信心。

在目前,Drupal 8 还在其发布周期中,目前最新的版本是alpha12,我们会基于这个版本,来展示一些Drupal 7开发者会先遇到的一些基本变化,我们应该先熟悉这些基本变化。

我怎样创建一个模块?

我们第一件事是要确定必须的文件和文件夹结构来告诉Drupal8识别我们的新模块。在Drupal 7 里,我们必须创建至少2个文件(.info 和 .module文件),但是在Drupal 8里,.info文件替换成了.info.yml,文件里的内容数据信息相同,但格式略有差异。

另一个主要变化是,自定义模块和第三方贡献模块文件夹的存放位置现在被放到了根目录下的modules/,这是因为所有核心的代码已经被移动到了它单独的文件夹core/,当然,在根目录下的modules文件夹内,我们依然鼓励你像以往的Drupal 7 一样,可以创建 custom 和contrib 子文件夹来分别存放你的自定义模块和官网下载的第三方模块。

让我们继续,我们现在先来学习创建一个叫demo(非常简单)的模块,并把它放到modules/custom/文件夹下,正如我之前所提到的,我们需要在这个新创建的demo/文件夹里,创建一个 demo.info.yml 文件,并在文件里输入以下代码:

 代码如下 复制代码
name: Drupal 8 Demo module
description: 'Demo module for Drupal 8 alpha12'
type: module
core: 8.x



这些大部分你应该看着很熟悉吧(name, description and core), type 字段也是现在必须有的一项,它的值可以是module,theme,theme_engine分别定义此文件类型是模块,还是主题或者模板引擎,另外要注意的一件重要的细节是,注意yml文件里的空格,正确缩进可以像数组一样组织数据。

你可以查看此文档 (https://www.drupal.org/node/2000204)说里其他可用的key|value , 然后打开一个模块文件夹下的.info.yml文件,尝试改变key|value,这里是官方的变更说明 (https://www.drupal.org/node/1935708)

现在,只要有这一个文件就足够了,你可以进入导航菜单下的Extend(扩展)页,找到Demo模块,并启用它。

正如我之前提到的,现在我不需要再创建一个.module的文件,drupal 8就已经可以识别此模块了,因为在drupal 8里,.module文件已经不再是必须存在的文件。因为现在大部分的业务逻辑代码都转移到了service类,控制器和插件里,我们稍后会看到这一点。

什么是路由?调用hook_menu()会发生什么?它的回调怎么工作?


在drupal 7里,hook_menu()可能是最能实现这些功能需求的钩子,因为它就是被用于设定路径并且把这些路径和回调函数连接起来,它也用来创建菜单链接和其他一些东西

在Drupal 8里,我们不再需要hook_menu()了,因为我们会大量使用Symfony2的组件来处理路由,这涉及定义一个路由配置和在控制器里处理一个回调(Controller类的方法), 让我们来实际看下它是如何工作的,我们来创建一个简单的页面来输出那句程序界的永恒经典:Hello world!

首先,我们需要为我们的模块创建一个路由文件:demo.routing.yml ,这个文件也是放在demo模块根目录下,在这个文件里,我们可以有如下(简单)路由设定:

 代码如下 复制代码

demo.demo:
  path: '/demo'
  defaults:
    _content: 'DrupaldemoControllerDemoController::demo'
    _title: 'Demo'
  requirements:
    _permission: 'access content'




第一行代码是一个新路由的开始,这个路由的名字叫做demo(原作者真蛋疼,你就不能取一个不一样的路由名嘛,非要和模块名一样,你让我的小伙伴误会了怎么办?),第一个是demo是模块名,第二个demo是路由名.

第二行path, 我们指定了这个路由的注册路径。

第三行defaults默认设置,我们设定了两件事:默认的页面标题(_title)和引用一个DemoController类的一个方法(_content)

第四行requirements要求,我们指定了用户需要有什么权限才能查看该页面。

你可以查此文档 (https://www.drupal.org/node/2092643)知道你还可以在这个路由文件里设定哪些选项。

现在,让我们来创建第一个叫做DemoController的控制器,并且它包含了一个方法叫做:demo(),用于当用户访问该页面时调用此方法。

在模块目录里,创建一个src的文件夹和一个Controller的文件夹,这就是我们存放controller类的地方,好了,进入Controller文件夹,创建文件:DemoController.php。

该控制器的位置,正如我们将看到的一样,把其他类放入src文件夹里是采用PSR-4标准的一部分,在之前,我们有一个更深层更大的文件夹结构(PSR-0标准),但现在有一个过渡阶段,这两种标准的文件夹结构路径都会起作用,所以你如果看到代码类仍然被放在lib/文件夹下,不用觉得奇怪,那是PSR-0标准。

在DemoController.php文件里,我们可以声明我们的类了:

 代码如下 复制代码

<?php
/**
 * @file
 * Contains DrupaldemoControllerDemoController.
 */

namespace DrupaldemoController;

/**
 * DemoController.
 */
class DemoController {
  /**
   * Generates an example page.
   */
  public function demo() {
    return array(
      '#markup' => t('Hello World!'),
    );
  }     
}


这是我们为了在一个页面上显示"Hello World!"的一个结构最简单的实例,在顶部,我们指定了类的命名空间,在下面,我们声明了我们的类

在DemoController类里,我们只需要demo()方法,用来返回一个像以往的drupal 7里一样的渲染数组,没有大量的东西,现在我们要做的就是清空缓存,并在浏览器里输入地址:http://example.com/demo ,然后我们就应该可以看到一个输出有Hello World!的页面!

怎么创建菜单链接?

在Drupal 7里,为了让菜单链接显示在网站上,我们可以在实施hook_menu()时,添加已经注册过的路径,但在Drupal8里,这个不再需要用hook来处理,我们可以像配置一样使用一个yml文件来声明菜单链接

让我们来看看,该怎样创建一个菜单链接来显示在Structure(结构)管理菜单下,首先,我们在我们的模块根目录下创建一个demo.menu_links.yml 文件,在这个文件中,我们将设定菜单链接和在现有网站菜单上的位置,为了实现我们所要做的,我们需要在文件里输入以下代码:

 代码如下 复制代码

demo.demo:
title: Demo Link
description: 'This is a demo link'
parent: system.admin_structure
route_name: demo.demo



这又是一个基于缩进的yml结构文件,

第一行我们定义了模块的菜单链接的机器名(就像之前定义路由一样),接下来,我们设定了链接的标题,描述,父菜单路径(作为子菜单放在该父菜单下),以及该菜单关联的路由名。

父菜单选项的值是父菜单路径(通它的所属模块附加),你想找个这个父菜单链接,你需要查找一些类似的*.menu_links.yml文件,因为这里的值是system.admin_structure,所以我就可以知道,它肯定是由核心的System模块设定的,所以你就可以判断这个父菜单链接存在于system.menu_links.yml文件里。

route_name是我们想要为这个菜单关联的路由名(这们之前创建的),当你清空缓存后,然后输入浏览器地址:http://example.com/admin/structure你应该可以看到一个新的菜单标题和描述,并且此链接是链接到demo/path的,怎么样,很不错吧?
结论

在这篇文章中,我们开始讨论Drupal 8 的模块开发,在这个阶段(alpha12发布),正是开始学习怎样使用新的API接口来开发贡献模块,为此,我会大家一起退出传统框架的思维,用全新的思维一起探索学习Drupal 8的变化。

首先,我们了解了一些基础知识,如何启动一个Drupal8模块(文件和文件夹结构等),所有这些与Drupal 7的不同之处,我们还看到了如何设定路由和一个控制器类的方法来调用一个路由地址,最后,我们已经看到了如何创建一个使用我们所定义路由的菜单链接。

在接下来的教程中,我们将继续构建完善这个模块,并期待发现在Drupal8中其他一些很酷的东西,我们将看到我们如何创建区块和如何配合使用表单和配置管理系统,下期见。

[!--infotagslink--]

相关文章

  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • IDEA中的clean,清除项目缓存图文教程

    这篇文章主要介绍了IDEA中的clean,清除项目缓存图文教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-25
  • iOS蓝牙设备名称缓存问题的解决方法

    这篇文章主要给大家介绍了关于iOS蓝牙设备名称缓存问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-08
  • Python运行提示缺少模块问题解决方案

    这篇文章主要介绍了Python运行提示缺少模块问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-10
  • AngularJS实现Model缓存的方式

    这篇文章主要介绍了AngularJS实现Model缓存的方式,分享了多种AngularJS实现Model缓存的方法,感兴趣的小伙伴们可以参考一下...2016-02-05
  • Perl模块编写说明

    这两天在用Perl编写一些监控脚本,其实写代码也是一件挺有意思的事情,就是挺废时间的。而且,由于语法不太熟,基本想到一个东西都要先Google一下看怎么实现。...2020-06-29
  • Nodejs下DNS缓存问题浅析

    本文给大家一起探讨nodejs下dns的缓存问题,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧...2016-11-22
  • Python PyPDF2模块安装使用解析

    这篇文章主要介绍了Python PyPDF2模块安装使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-04-22
  • PHP的运行机制与原理(底层)

    说到php的运行机制还要先给大家介绍php的模块,PHP总共有三个模块:内核、Zend引擎、以及扩展层;PHP内核用来处理请求、文件流、错误处理等相关操作;Zend引擎(ZE)用以将源文件转换成机器语言,然后在虚拟机上运行它;扩展层是一组...2015-11-24
  • @CacheEvict + redis实现批量删除缓存

    这篇文章主要介绍了@CacheEvict + redis实现批量删除缓存方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-12
  • python怎么删除缓存文件

    在本篇文章里小编给大家整理的是一篇关于python删除缓存文件方法,需要的朋友们可以学习下。...2020-07-19
  • IIS7、iis7.5中禁止缓存单个静态文件的配置方法

    这篇文章主要介绍了IIS7、iis7.5中禁止缓存单个静态文件的配置方法,需要的朋友可以参考下...2017-07-06
  • vue项目中禁用浏览器缓存配置案例

    这篇文章主要介绍了vue项目中禁用浏览器缓存配置案例,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-09-12
  • c#通用登录模块分享

    这是一款简单的ASP.NETC#注册登录模块制作思路,非常简单实用,虽然没怎么考虑登陆的安全性,但作为C#的朋友学习交流使用。...2020-06-25
  • java中Base64编码原理实例讲解

    这篇文章主要介绍了java中Base64编码原理实例讲解,文章讲解的很清晰,有对于这方面不太懂的同学可以研究下...2021-02-10
  • Vue中key的作用及原理详解

    本文主要介绍了Vue3中key的作用和工作原理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-10-13
  • Node.js API详解之 console模块用法详解

    这篇文章主要介绍了Node.js API详解之 console模块用法,总结分析了Node.js API中console模块基本函数、使用方法与操作注意事项,需要的朋友可以参考下...2020-05-12
  • 强制页面不缓存的方法

    页面不缓存可以让我们有更新就立即更新出来用户不需要清除浏览器缓存或不停的按f5刷新了,这里整理了解一些关于页面不缓存的方法,具体的如下。 一,js,css,图片文件不...2016-09-20
  • 安卓手机如何清理缓存

    小编给大家带来一篇关于安卓手机缓存怎么清理的问题解答,有需要的可以参考一下 &#8195;&#8195;安卓手机怎么清理缓存 android清除程序缓存的方法&#8195;&#8195;一,...2017-07-06
  • Python xmltodict模块安装及代码实例

    这篇文章主要介绍了Python xmltodict模块安装及代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-05