7 定义自己的实体类型

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

其实2011年的时候,自己就想编写一个模块,定义一个实体类型,最初的想法是把区块改造为实体,我在官方网站上面搜索,发现已经有了这样的一个模块了,bean,就是这个时候冒出来的。后来想把Contact模块,重新实现为实体的形式,不过发现了有个Entity Forms模块,可以做这件事情。再往后,就想不出来哪些东西可以改造为实体了。一直到搭建网上书店时,解决面包屑问题的时候,有了将面包屑改造为实体的想法,并且在现有的模块当中,没有这样的技术实现。

在这一章中,大家跟我一道来学习一下,如何采用实体的形式来实现Drupal里面的面包屑功能。面包屑这个实体,有一点点小小的特殊,它的bundle只有一个,还是面包屑,类似于用户,实体下面的bundle只有一个。

从我的角度,推荐几个模块,供大家阅读、学习、使用,推荐的有Profile2模块,这个模块是基于Entity API模块的,很具有代表性,而且我们在实际当中,也经常用到这个模块,还有,这个模块进入了Drupal8的内核了。其次推荐Examples里面的entity_example模块,推荐model模块,这是一个基于Entity API的更简单实用的例子,只不过模块本身没有太大价值。

现有的面包屑模块很多,这里罗列一下,custom_breadcrumbsmenu_breadcrumbpath_breadcrumbsbreadcrumbcrumbshanseltaxonomy_breadcrumb,其中path_breadcrumbscrumbs是后起之秀。我们这里把我们的模块的名字叫做breadcrumb2,参考一下Profile2,我在breadcrumb项目的问题列表中申请成为维护者,但是没有人回应,现在看起来需要另起炉灶了。

在我们创建模块之前,请大家把Profile2model两个模块的代码阅读一遍,为什么呢?因为我们的很多代码都是从这里直接复制过来的。作者也是第一次创建一个实体类型,很多时候也是摸着石头过河。


Drupal版本:

7.1 Info 文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

   我们创建一个breadcrumb2.info文件,输入以下内容:

name = Breadcrumb2

description = Supports configurable breadcrumbs.

core = 7.x

files[] = breadcrumb2.admin.inc

files[] = breadcrumb2.info.inc

files[] = breadcrumb2.test

dependencies[] = link

dependencies[] = rules

dependencies[] = field_validation

configure = admin/structure/breadcrumbs

这里面的键值我这里就不讲解了,我们这个模块依赖于rulesentity模块,由于rules本身依赖于entity,所以我们这里只需要依赖于rules即可。我们需要添加一个链接字段,面包屑导航里面包含的就是多个链接,所以我们这里依赖于link模块。由于我们将path,也就是内部路径,处理成为了字段的形式,所以我们需要加一些验证,也就用到了field_validation模块了。


Drupal版本:

7.10 为面包屑添加字段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

导航到admin/structure/breadcrumbs/fields,添加两个字段“Path”Breadcrumb Link。这是添加好的样子:

图片3.png 

对于“Path”字段,我们将其设置为必填的,其它采用默认配置。对于Breadcrumb Link,选中了“Optional URL”,将“Link Title”设置为了“Required Title”,将“Number of values”设置为了“unlimited”(不限)。我们后面根据需要可能会调整这里的配置。


Drupal版本:

7.11 添加实体

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们首先定义菜单项:

  $items['breadcrumb/add'] = array(

    'title' => 'Add breadcrumb',

    'page callback' => 'breadcrumb2_add',

    'access arguments' => array('administer breadcrumbs'),

    'file' => 'breadcrumb2.admin.inc',

  );

接着,向breadcrumb2.admin.inc添加回调函数breadcrumb2_add

/**

 * Returns a breadcrumb submission form.

 */

function breadcrumb2_add(){

  $breadcrumb = entity_get_controller('breadcrumb2')->create();

  drupal_set_title(t('Create breadcrumb'));

  $output = drupal_get_form('breadcrumb2_form', $breadcrumb);

 

  return $output;

}

在这个函数里面,我们首先使用entity_get_controller创建了一个初始化的面包屑,然后设置了标题,最后调用drupal_get_form,调用的表单IDbreadcrumb2_form,同时我们把$breadcrumb传递给了它。我们这样做的目的,是希望breadcrumb2_form同时能够适应于编辑表单。我们来看看breadcrumb2_form的定义:

/**

 * The breadcrumb edit form.

 */

function breadcrumb2_form($form, &$form_state, $breadcrumb) {

  // Save the breadcrumb for later, in case we need it.

  $form['#breadcrumb'] = $breadcrumb;

  $form_state['breadcrumb'] = $breadcrumb;

  

  $form['bid'] = array(

    '#type' => 'value',

    '#value' => isset($breadcrumb->bid) ? $breadcrumb->bid : NULL,

  );

  

  // Add the field related form elements.

  field_attach_form('breadcrumb2', $breadcrumb, $form, $form_state);

 

  $form['actions'] = array('#type' => 'actions');

  $form['actions']['submit'] = array(

    '#type' => 'submit',

    '#value' => t('Save'),

    '#weight' => 40,

  );

  if (!empty($breadcrumb->bid)) {

    $form['actions']['delete'] = array(

      '#type' => 'submit',

      '#value' => t('Delete breadcrumb'),

      '#weight' => 45,

      '#limit_validation_errors' => array(),

      '#submit' => array('breadcrumb2_form_submit_delete')

    );

  }

  $form['#validate'][] = 'breadcrumb2_form_validate';

  $form['#submit'][] = 'breadcrumb2_form_submit';

  return $form;

}

首先,我们将$breadcrumb保存到了$form$form_state里面,这里保存原始的面包屑对象。接着,我们添加了'bid'元素,它的类型为value,为什么这样做呢?因为有些模块会使用entity_extract_ids来提取实体的ID,在Drupal7里面,如果我们不预先保存一个实体ID的话,entity_extract_ids就会提取不出来,Drupal8已经改进了这个问题,我遇到过这个问题,所以在这里加了了'bid'。接着是使用field_attach_form,把实体上面的字段也添加进来,由于字段的添加,是动态的,我们也不知道具体有几个字段,通过field_attach_form,我们就可以把这个工作委托给Field模块了,它能够帮我们做这件事情。再往下是提交、删除按钮,我们把这两者放到了actions里面了,actionsDrupal7里面的一个新的表单元素类型;对于删除按钮,我们为它指定了一个单独的提交函数breadcrumb2_form_submit_delete,同时为它设置了'#limit_validation_errors',通过这个设置,在删除面包屑的时候,即便是存在验证错误的话,也可以正常提交。最后是为表单设置$form['#validate']$form['#submit'],我们在第一集里面学过,即便不设置这两行代码,表单系统也会自动使用这两个函数,为什么明确的设置呢?因为我看到其它实体的添加表单都是这样明确设置的,包括节点的添加表单,或许这样做的好处的,代码的可读性更强。

接下来是表单验证函数:

function breadcrumb2_form_validate($form, &$form_state) {

  $breadcrumb = $form_state['breadcrumb'];

  

  // Notify field widgets to validate their data.

  field_attach_form_validate('breadcrumb2', $breadcrumb, $form, $form_state);

}

代码比较简单,我们模块本身没有多少验证工作,所以这里直接将验证工作委托给了Field模块,这里使用的是field_attach_form_validate

再往下,是提交函数,逻辑也比较简单:

/**

 * Breadcrumb form submit handler.

 */

function breadcrumb2_form_submit($form, &$form_state) {

 

  $breadcrumb = &$form_state['breadcrumb'];

  // Notify field widgets.

  field_attach_submit('breadcrumb2', $breadcrumb, $form, $form_state);

  

  // Save the breadcrumb

  breadcrumb2_save($breadcrumb);

 

  drupal_set_message(t('Breadcrumb saved.'));

  $form_state['redirect'] = 'breadcrumb/' . $breadcrumb->bid;

 

}

我们首先使用field_attach_submit,将提交委托给了Field模块,接着我们调用我们的API函数breadcrumb2_save,保存实体。最后设置一个消息,并重定向。

在这部分,需要注意的是field_attach_formfield_attach_form_validatefield_attach_submit的使用。这里我有个疑问,不知道为什么不把field_attach_submit叫做field_attach_form_submit

编写完这些代码以后,可以测试一下了,如果你按照这里所列的代码,跟着做的,现在访问breadcrumb/add页面,我们可以看到一个表单页面,这里面包含前面我们添加的两个字段。一切正常。输入一些测试数据,提交,我们遇到了第一个问题:

图片4.png 

这是一个PHP错误。我在网上搜索了这个问题,很多人也遇到了同样的问题,但是没有找到答案。我们在开发的过程中,是存在一些问题,这些问题起初我也没有注意到,我也希望一次能够搞定所有的问题,但是错误总是不经意的出现。如果你能够,在现有的代码基础上,把所有的问题都解决掉,那么证明你完全掌握了如何定义一个实体类型。我也是在解决这些问题的过程中,才明白了里面的很多细微的地方。


Drupal版本:

7.12 调试并解决已有代码的问题

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

通过Google搜索,找不到答案。有很多同样的问题,但是里面没有我们想要的答案。这个时候,我们需要打开common.inc文件,找到7750行,Drupal核心的版本不一样,这里显示的行数也不一致。但是代码是一样的:

/**

 * Get the entity controller class for an entity type.

 */

function entity_get_controller($entity_type) {

  $controllers = &drupal_static(__FUNCTION__, array());

  if (!isset($controllers[$entity_type])) {

    $type_info = entity_get_info($entity_type);

//print debug($type_info);

    $class = $type_info['controller class'];

//drupal_set_message('123456');

    $controllers[$entity_type] = new $class($entity_type);

  }

  return $controllers[$entity_type];

}

当然,核心里面是没有print debugdrupal_set_message这两句的,这是我调试的时候添加了,我想查看一下$type_info里面包含哪些信息。发现这里打印出来的信息,和我们在前面定义的有出入。我首先想到的是,是不是'controller class'没有设置正确?我们需要自己定义一个控制器类?

问题1

通过print debug,我还是发现了一个错误,在breadcrumb2_entity_info里面:

      'entity keys' => array(

        'id' => 'pid',

      ),

漏网之鱼,复制别人的代码的时候,经常会遇到这种情况,将它修正过来:

      'entity keys' => array(

        'id' => 'bid',

      ),

问题2

'bundle keys'注视掉,我们只有一个bundle,没有‘type’这个概念。   

/*

      'bundle keys' => array(

        'bundle' => 'type',

      ),

  */

 

问题3

前面的两个问题,是我重读breadcrumb2_entity_info代码以后,所做的两个修正;但是问题仍然存在。我通过进一步的测试,发现验证是正常的,问题出在了表单的提交函数里面。我把breadcrumb2_form_submit里面的代码全部注销掉,问题没有了,当然我们的面包屑实体也没有保存起来。进一步的细化,发现breadcrumb2_save($breadcrumb);出了问题。

接着检查了breadcrumb2_save的代码,里面的逻辑也非常简单,只有这么简单的一行:

return $breadcrumb->save();

Breadcrumb里面,我们并没有定义save方法,这个方法应该是它的父类Entity定义的。所以回到Entity API模块里面查找Entity类的对应代码。此时我发现,这个类存放到了entity\includes下面的entity.inc文件中了。注意,这个文件夹下面,包含4inc文件,分别为entity.controller.incentity.incentity.property.incentity.ui.incentity.wrapper.inc。我们打开entity.inc文件,找到save方法:

  /**

   * Permanently saves the entity.

   *

   * @see entity_save()

   */

  public function save() {

    return entity_get_controller($this->entityType)->save($this);

  }

对这里的代码,做以下修改:

  public function save() {

    drupal_set_message($this->entityType);

    //return entity_get_controller($this->entityType)->save($this);

  }

我们去掉了实际的保存逻辑代码,加上了一个drupal_set_message,我想看看此时传递过来的实体类型到底是什么。

再次测试,发现$this->entityType的值竟然为“breadcrumb”,而实际应该为“breadcrumb2”,如果我们的模块名字为breadcrumb就好了,就不会遇到这个问题了。找到原因以后,很快就定位到了错误的代码地方,Breadcrumb的构造方法,原来为:

  public function __construct($values = array()) {

    parent::__construct($values, 'breadcrumb');

  } 

我们将它修正为:

  public function __construct($values = array()) {

    parent::__construct($values, 'breadcrumb2');

  }

一字之差,就让我们调试了半天,复制粘贴别人的代码的时候,经常会遇到这样的问题,要改的地方没有改过来,或者改错了。

 

问题4

原来的错误消失了,新的错误出现了:

图片4.png 

这个错误也非常严重,但是相比前面的错误,至少这个错误还能把页面完整显示出来。刚才发现Breadcrumb类定义的有问题。现在根据错误提示,“entity_id”有问题,也就是我们的bid设置的有问题,前面已经修正了一个地方了。我重读了Profile2模块里面的Profile类的定义,发现里面的代码中,包括很多属性,其中就包括pid。人家是这样定义的:

class Profile extends Entity {

 

  /**

   * The profile id.

   *

   * @var integer

   */

  public $pid;

。。。。

    当时我定义Breadcrumb类的时候,不知道这些属性的含义,另外在类的成员函数里面也没有用到这些属性,所以一股脑的把它们删除了,现在我们为Breadcrumb添加一个属性:

  /**

   * The breadcrumb id.

   *

   * @var integer

   */

  public $bid;

 

问题5

修改后,我们清空缓存,再次测试,原来的错误消失了,又出现了新的问题:

图片5.png 

我直接打开includes\database\query.inc文件,找到716行,看源代码:

  public function preExecute() {

    // Confirm that the user did not try to specify an identical

    // field and default field.

    if (array_intersect($this->insertFields, $this->defaultFields)) {

      throw new FieldsOverlapException('You may not specify the same field to have a value and a schema-default value.');

    }

 

    if (!empty($this->fromQuery)) {

      // We have to assume that the used aliases match the insert fields.

      // Regular fields are added to the query before expressions, maintain the

      // same order for the insert fields.

      // This behavior can be overridden by calling fields() manually as only the

      // first call to fields() does have an effect.

      $this->fields(array_merge(array_keys($this->fromQuery->getFields()), array_keys($this->fromQuery->getExpressions())));

    }

 

    // Don't execute query without fields.

    if (count($this->insertFields) + count($this->defaultFields) == 0) {

      throw new NoFieldsException('There are no fields available to insert with.');

    }

 

    // If no values have been added, silently ignore this query. This can happen

    // if values are added conditionally, so we don't want to throw an

    // exception.

    if (!isset($this->insertValues[0]) && count($this->insertFields) > 0 && empty($this->fromQuery)) {

      return FALSE;

    }

    return TRUE;

  }

这里的黑体部分,就是我们出错的地方,此时我突然想到了一个问题,我们的breadcrumb数据库表,里面只有一个bid字段,并且这个字段是自增的字段。我们向数据库里面插入数据的时候,是不需要设置这个字段的,除此以外,我们没有别的字段了。我要哭了。在没有遇到这个问题以前,我是没有思考过这个问题的。我查看了多个实体类型的定义,发现主表里面,都是有属性的,即便是Field Collection里面也有一个属性field_name。现在为我们的breadcrumb数据库表,添加什么样的属性呢?最初我考虑的是createdchanged,但是这两个属性对于我们来说,没有多大用处,最后我觉得,使用path,把它定义为这里的属性,不用Field模块来管理它了,当然也就不使用Field validation负责它的验证了,我们自己编写它的验证逻辑。这次改动有点多哟,好事多磨。

首先是schema里面的定义,粗体表示新增的

      'bid' => array(

        'type' => 'serial',

        'not null' => TRUE,

        'description' => t("'Primary Key: Unique breadcrumb item ID."),

      ),

      'path' => array(

        'description' => t('URL where breadcrumb should be shown.'),

        'type' => 'varchar',

        'length' => 256,

        'not null' => TRUE,

      ),

接着为类Breadcrumb,添加一个属性$path: 

class Breadcrumb extends Entity {

  /**

   * The breadcrumb id.

   *

   * @var integer

   */

  public $bid;

 

   /**

   * The internal path where breadcrumb should be shown.

   *

   * @var string

   */

  public $path;

 

这下,我们的breadcrumb2.info.inc文件有用了:

class Breadcrumb2MetadataController extends EntityDefaultMetadataController {

 

  public function entityPropertyInfo() {

    $info = parent::entityPropertyInfo();

    $properties = &$info[$this->type]['properties'];


    $properties['path'] = array(

      'label' => t('path'),

      'description' => t('The internal path where breadcrumb should be shown.'),

      'setter callback' => 'entity_property_verbatim_set',

      'setter permission' => 'administer breadcrumbs',

      'schema field' => 'path',

    );


    return $info;

  }

}

当然,我们的面包屑表单里面,也需要加上这个元素:

function breadcrumb2_form($form, &$form_state, $breadcrumb) {

  // Save the breadcrumb for later, in case we need it.

  $form['#breadcrumb'] = $breadcrumb;

  $form_state['breadcrumb'] = $breadcrumb;

  

  $form['bid'] = array(

    '#type' => 'value',

    '#value' => isset($breadcrumb->bid) ? $breadcrumb->bid : NULL,

  );

  $form['path'] = array(

    '#type' => 'textfield',

    '#title' => t('Path'),

    '#maxlength' => 60,

    '#default_value' => !empty($breadcrumb->path) ? $breadcrumb->path : '',

    '#weight' => -10,

  );

做完这些修改以后,卸载breadcrumb2模块,然后重新安装,然后删除Path字段,重新测试。成功了,只不过重定向回来的时候,路径还不存在,导致了空白问题。我通过phpmyadmin检查对应的数据,基本上都正确了,就剩下一个小问题,就是path属性没有保存下来,里面一直问题。这个问题解决起来也并不复杂:

function breadcrumb2_form_submit($form, &$form_state) {

 

  $breadcrumb = &$form_state['breadcrumb'];

  

  $breadcrumb->bid = $form_state['values']['bid'];

  $breadcrumb->path = $form_state['values']['path'];

  ….

粗体部分为新增的代码,这样就正常了。我是在解决这些问题的时候,更加深刻的掌握了创建一个实体类型的技术。原本打算重新写作这部分资料,后来还是觉得把过程中遇到的错误,也原原本本的呈现出来。这是Think in Drupal的一个风格,就是我们把开发中、配置中的错误也完全呈现出来,告诉大家的解决这些问题的步骤。


Drupal版本:

7.13查看面包屑实体

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

 首先定义菜单项:

  $items['breadcrumb/%breadcrumb2'] = array(

    'title' => 'Breadcrumb',

    'page callback' => 'breadcrumb2_page_view',

    'page arguments' => array(1),

    'access arguments' => array('administer breadcrumbs'),

    'file' => 'breadcrumb2.admin.inc',

  );

  $items['breadcrumb/%breadcrumb2/view'] = array(

    'title' => 'View',

    'type' => MENU_DEFAULT_LOCAL_TASK,

    'weight' => -10,

  );

   这里面我们使用了通配符%breadcrumb2,当我们传递过来一个bid以后,系统会自动的调用breadcrumb2_load函数,将bid转换为相应的面包屑对象。

接下来,我们看breadcrumb2_page_view的具体实现,向breadcrumb2.admin.inc里面添加以下代码:

/**

 * Breadcrumb view page.

 */

function breadcrumb2_page_view($breadcrumb, $view_mode ='full'){

  return $breadcrumb->view('full');

}

清除缓存,现在访问breadcrumb/1,已经可以显示出来了,但是面包屑的属性path没有显示出来。

此时有多种解决办法,一种是实现自己的控制器类BreadcrumbController,在里面实现自己的方法:

public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array())

    另一种,是在breadcrumb2_page_view里面,我们不调用$breadcrumb->view,然后自己去构建我们想要的内容。

function breadcrumb2_page_view($breadcrumb, $view_mode = 'full'){

 // return $breadcrumb->view($view_mode);

  $breadcrumb->content = array();

  

  if($view_mode = 'full'){

    $breadcrumb->content['path'] = array(

      '#markup' => filter_xss($breadcrumb->path),

      'weight' => -5,

    );

  }

  

  //Build fields content

  field_attach_prepare_view('breadcrumb2', array($breadcrumb->bid => $breadcrumb), $view_mode);

  entity_prepare_view('breadcrumb2', array($breadcrumb->bid => $breadcrumb));

  $breadcrumb->content += field_attach_view('breadcrumb2', $breadcrumb, $view_mode);

  return $breadcrumb->content;

}

   这种方式是我们自己调用field的集成。当然,如果我们熟悉$breadcrumb->view返回的数组结构的话,也可以这样编写代码:

$build = $breadcrumb->view($view_mode);

   if($view_mode = 'full'){

    $build['breadcrumb2'][$breadcrumb->bid]['path'] = array(

      '#markup' => filter_xss($breadcrumb->path),

      'weight' => -5,

    );

  }

return $build;

第三种办法,就是自己实现hook_ breadcrumb2_view这个钩子,这个钩子是从哪里定义的?这是Entity API模块帮助我们定义,只需要自己去实现即可,在module文件中添加以下代码,效果是一样的:

/**

 * Implement hook_breadcrumb2_view().

 */

function breadcrumb2_breadcrumb2_view($breadcrumb, $view_mode, $langcode){

  if($view_mode = 'full'){

    $breadcrumb->content['path'] = array(

      '#markup' => filter_xss($entity->path),

      'weight' => -5,

    );

  }

}

从这里面,我们可以看到Entity API帮助我们做了很多工作,省了不少事。


Drupal版本:

7.14 编辑面包屑实体

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

菜单项代码:

  $items['breadcrumb/%breadcrumb2/edit'] = array(

    'page callback' => 'breadcrumb2_page_edit',

    'page arguments' => array(1),

    'access arguments' => array('administer breadcrumbs'),

    'weight' => 0,

    'title' => 'Edit',

    'type' => MENU_LOCAL_TASK,

    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,

    'file' => 'breadcrumb2.admin.inc',

  );

这里在菜单项里面,用到了'context',它的含义可以参看第一集。接着我们向breadcrumb2.admin.inc添加breadcrumb2_page_edit函数:

/**

 * Breadcrumb edit page.

 */

function breadcrumb2_page_edit($breadcrumb){

  return drupal_get_form('breadcrumb2_form', $breadcrumb);

}

由于我们在前面创建面包屑的时候,编写breadcrumb2_form表单时,同时考虑了编辑时会用到,所以我们这里的代码就简洁了很多。将添加表单、编辑表单合二为一,这是一个好习惯。


Drupal版本:

7.15 删除面包屑实体

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

首先我们实现删除按钮的提交处理函数breadcrumb2_form_submit_delete

/**

 * Breadcrumb form submit handler for the delete button.

 */

function breadcrumb2_form_submit_delete($form, &$form_state) {

  $breadcrumb = $form_state['breadcrumb'];

  $form_state['redirect'] = 'breadcrumb/' . $breadcrumb->bid . '/delete';

}

在这里,当删除一个面包屑时,我们直接将它重定向到了对应的删除页面。对应路径的菜单项是这样定义的:

  $items['breadcrumb/%breadcrumb2/delete'] = array(

    'page callback' => 'drupal_get_form',

    'page arguments' => array('breadcrumb2_delete_confirm_form', 1),

    'access arguments' => array('administer breadcrumbs'),

    'weight' => 1,

    'title' => 'Delete',

    'type' => MENU_LOCAL_TASK,

    'context' => MENU_CONTEXT_INLINE,

    'file' => 'breadcrumb2.admin.inc',

  );

    breadcrumb2_delete_confirm_form是一个确认表单,我们把它的定义添加到breadcrumb2.admin.inc里面:

/**

 * Confirm form for deleting a profile.

 */

function breadcrumb2_delete_confirm_form($form, &$form_state, $breadcrumb) {

  $form_state += array('breadcrumb' => $breadcrumb);

  $confirm_question = t('Are you sure you want to delete breadcrumb for path %path?', array('%path' => $breadcrumb->path));

  return confirm_form($form, $confirm_question, 'breadcrumb/' . $breadcrumb->bid);

}

 

确认表单,采用了confirm_form,让Drupal系统来生成这个表单。最后我们来看一下,确认表单提交后的代码:

function breadcrumb2_delete_confirm_form_submit($form, &$form_state) {

  $breadcrumb = $form_state['breadcrumb'];

  $breadcrumb->delete();

  drupal_set_message(t('Deleted breadcrumb for path %path.', array('%path' => $breadcrumb->path)));

  $form_state['redirect'] = 'admin/structure/breadcrumbs';

}

这里面的代码,主要借鉴了Profile2里面的profile2_page.inc文件里面的代码。


Drupal版本:

7.16 安装时为实体创建字段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这里是手动的创建Breadcrumb link字段,我们希望在安装这个Breadcrumb2模块的时候,自动的帮我们创建这个字段。这个字段,对我们来说是必须的。首先,我们把现有的字段的信息导出来。这里我们使用Features模块。

这是Features导出来的代码:

/**

 * Implements hook_field_default_fields().

 */

function breadcrumb_link_field_default_fields() {

  $fields = array();

 

  // Exported field: 'breadcrumb2-breadcrumb2-field_breadcrumb_link'

  $fields['breadcrumb2-breadcrumb2-field_breadcrumb_link'] = array(

    'field_config' => array(

      'active' => '1',

      'cardinality' => '-1',

      'deleted' => '0',

      'entity_types' => array(),

      'field_name' => 'field_breadcrumb_link',

      'foreign keys' => array(),

      'indexes' => array(),

      'module' => 'link',

      'settings' => array(

        'attributes' => array(

          'class' => '',

          'rel' => '',

          'target' => 'default',

        ),

        'display' => array(

          'url_cutoff' => 80,

        ),

        'enable_tokens' => 1,

        'title' => 'optional',

        'title_maxlength' => 128,

        'title_value' => '',

        'url' => 0,

      ),

      'translatable' => '0',

      'type' => 'link_field',

    ),

    'field_instance' => array(

      'bundle' => 'breadcrumb2',

      'default_value' => NULL,

      'deleted' => '0',

      'description' => '',

      'display' => array(

        'default' => array(

          'label' => 'above',

          'module' => 'link',

          'settings' => array(),

          'type' => 'link_default',

          'weight' => 1,

        ),

      ),

      'entity_type' => 'breadcrumb2',

      'field_name' => 'field_breadcrumb_link',

      'label' => 'Breadcrumb Link',

      'required' => 0,

      'settings' => array(

        'attributes' => array(

          'class' => '',

          'configurable_title' => 0,

          'rel' => '',

          'target' => 'default',

          'title' => '',

        ),

        'display' => array(

          'url_cutoff' => '80',

        ),

        'enable_tokens' => 1,

        'title' => 'required',

        'title_maxlength' => '128',

        'title_value' => '',

        'url' => 'optional',

        'user_register_form' => FALSE,

        'validate_url' => 1,

      ),

      'widget' => array(

        'active' => 0,

        'module' => 'link',

        'settings' => array(),

        'type' => 'link_field',

        'weight' => '2',

      ),

    ),

  );

 

  // Translatables

  // Included for use with string extractors like potx.

  t('Breadcrumb Link');

 

  return $fields;

}

我把它改造了一下,放到了install文件里面了,我们这里并没有打算依赖于Features模块:

/**

 * Implements hook_install().

 */

function breadcrumb2_install() {

   // Add or remove the link field, as needed.

  $field = field_info_field('link');

  if (empty($field)) {

$field = array(

  'cardinality' => '-1',

      'entity_types' => array('breadcrumb2'),

      'field_name' => 'link',

      'module' => 'link',

      'type' => 'link_field',

    );

    $field = field_create_field($field);

  }

  $instance = field_info_instance('breadcrumb2', 'link', 'breadcrumb2');

  if (empty($instance)) {

    $instance = array(

      'bundle' => 'breadcrumb2',

      'default_value' => NULL,

      'deleted' => '0',

      'description' => '',

      'display' => array(

        'default' => array(

          'label' => 'above',

          'module' => 'link',

          'settings' => array(),

          'type' => 'link_default',

          'weight' => 1,

        ),

      ),

      'entity_type' => 'breadcrumb2',

      'field_name' => 'link',

      'label' => 'Breadcrumb Link',

      'required' => 0,

      'settings' => array(

        'attributes' => array(

          'class' => '',

          'configurable_title' => 0,

          'rel' => '',

          'target' => 'default',

          'title' => '',

        ),

        'display' => array(

          'url_cutoff' => '80',

        ),

        'enable_tokens' => 1,

        'title' => 'required',

        'title_maxlength' => '128',

        'title_value' => '',

        'url' => 'optional',

        'user_register_form' => FALSE,

        'validate_url' => 1,

      ),

      'widget' => array(

        'active' => 0,

        'module' => 'link',

        'settings' => array(),

        'type' => 'link_field',

        'weight' => '2',

      ),

    );

    $instance = field_create_instance($instance);

  }

}

    在卸载我们的模块时,我们还需要负责将其删除:

/**

 * Implements hook_uninstall().

 */

function breadcrumb2_uninstall() {

  $instance = field_info_instance('breadcrumb2', 'link', 'breadcrumb2');

  if (!empty($instance)) {

    field_delete_instance($instance);

  }

 

  $field = field_info_field('link');

  if ($field) {

    field_delete_field('link');

  }  

}

   注意这里面的field_info_fieldfield_create_fieldfield_info_instancefield_create_instancefield_delete_instancefield_delete_fieldDrupal API函数的用法。它们用来获取字段信息、创建一个字段、获取字段实例信息、创建一个字段实例、删除一个字段实例、删除一个字段。

我刚开始的时候,这样写的代码:

field_delete_field($field);

结果在卸载模块的时候,总是报错,卸载不了。后来才修正过来。所以用的时候要小心。要明白这个函数里面各个参数的具体含义。


Drupal版本:

7.17 面包屑本身功能实现

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这个模块,是用来设置页面上面的面包屑的,开发了这么多,我们看看如果通过我们的面包屑实体来设置面包屑。向module文件添加以下代码:

/**

 * Implements hook_page_alter().

 */

function breadcrumb2_page_alter() {

  // See if current page has path breadcrumbs.

  $breadcrumbs = drupal_get_breadcrumb();

  $current_path = current_path();

  $breadcrumb2 = breadcrumb2_load_by_path($current_path);

  if(empty($breadcrumb2)){

    return;

  }

  //$breadcrumb2 = breadcrumb2_load(1);

  $wrapper = entity_metadata_wrapper('breadcrumb2', $breadcrumb2);

  $breadcrumb_links = $wrapper->link->value();

  foreach($breadcrumb_links as $breadcrumb_link){

    $breadcrumbs[]= l($breadcrumb_link['title'], $breadcrumb_link['url']);

  }

  //print debug($breadcrumb_links);

  // Set breadcrumbs for current page if it exists.

  if ($breadcrumbs) {

    drupal_set_breadcrumb($breadcrumbs);

  }

}

这里面我们实现了hook_page_alter钩子,这个方式借鉴于path_breadcrumb模块,当然,我们也可以通过hook_preprocess_page来设置, crumbs模块里面有这种方式的实现。我们来看看这个钩子函数里面的代码,首先,我们根据当前路径,获取到面包屑实体$breadcrumb2;这里面我们对$breadcrumb2使用entity_metadata_wrapper做了封装,这样我们就可以方便的读取到实体里面字段link的数组了,这样做的好处是,不用考虑语言问题;之后对$breadcrumb_links数组进行循环,将里面的链接追加到当前面包屑数组$breadcrumbs里面;最后使用drupal_set_breadcrumb设置面包屑。

接下来,来看一下函数breadcrumb2_load_by_path的实现代码:

/**

 * Fetch a breadcrumb object. 

 *

 * @param $path

 *   Internal path.

 * @return

 *   A fully-loaded $breadcrumb object or FALSE if it cannot be loaded.

 *

 * @see breadcrumb2_load_multiple()

 */

function breadcrumb2_load_by_path($path) {

  $breadcrumbs = breadcrumb2_load_multiple(FALSE, array('path' => $path));

  return reset($breadcrumbs);

}

我们直接将其委托给了breadcrumb2_load_multiple,把array('path' => $path)作为条件参数传递了过去。如果返回了面包屑对象数组,我们就使用reset将数组中的第一个对象返回。这里的逻辑非常简单,和breadcrumb2_load函数类似。不过越是简单的东西,有时候越难理解。我原来的想法是这样的,使用EntityFieldQuery,把我们的$path作为属性参数传递给它,这样EntityFieldQuery将会读取来一个包含bids的数组,我们把bids数组传递给breadcrumb2_load_multiple,获得一组面包屑对象,然后再使用reset获取第一个对象元素。

Entity API帮我们做了这些工作,为了更好的理解,我们反向追踪一下代码。

图片4.png


通过阅读里面的代码,我们最终找到了,SQL语句是在DrupalDefaultEntityController里面的buildQuery方法中动态构建的。有兴趣的可以看一下里面的代码。

到这里我们的面包屑实体类型就创建完毕了。当然,模块还没有写完。我们接着会写Views的集成、Rules的集成。只有当集成了Rules以后,我们这种构建面包屑的方式,才能


Drupal版本:

7.2 为实体定义Schema

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

和开发普通的模块一样,当需要存储数据的时候,我们需要定义自己的数据库表,这个时候,就需要定义Schema了。这个我们以前也讲过了,来看这里的定义。首先创建breadcrumb2.install文件,在里面输入以下内容:

<?php

 

/**

 * @file

 * Install, update and uninstall functions for the breadcrumb2 module.

 */

 

/**

 * Implements hook_schema().

 */

function breadcrumb2_schema() {

  $schema['breadcrumb'] = array(

    'description' => 'Stores breadcrumb items.',

    'fields' => array(

      'bid' => array(

        'type' => 'serial',

        'not null' => TRUE,

        'description' => t("'Primary Key: Unique breadcrumb item ID."),

      ),

    ),

    'primary key' => array('bid'),

  );

  return $schema;

}

最上面的文件的描述说明,那段文字是直接从profile2.install里面复制过来的,下面的breadcrumb2_schema也是从那边复制过来的,只不过我们根据自己的需要做了修改。面包屑,这个实体比较简单,本来我想在这里定义一个属性path的,但是一想,自己需要为它创建一个表单元素,负责它的编辑、验证、存储,感觉有点麻烦,所以直接将它交给Field系统了。所以数据库表breadcrumb的结构非常简单,就一个bid主键,用来关联字段的,其它什么属性也没有定义。简单就好。

我们采用这样的策略,首先,定义好实体,然后向实体上面添加两个字段pathlink,之后将这两个字段的定义使用Features导出来,然后把里面的定义代码复制到我们的模块里面,这样我们在安装的时候,就直接为我们的面包屑实体创建好字段了。一步一步来。


Drupal版本:

7.3 实现hook_entity_info

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,我们创建breadcrumb2.module文件,首先输入以下代码:

<?php

 

/**

 * @file

 * Support for configurable breadcrumbs.

 */

   接着,实现hook_entity_info()

/**

 * Implement hook_entity_info().

 */

function breadcrumb2_entity_info() {

  $return = array(

    'breadcrumb2' => array(

      'label' => t('Breadcrumb'),

      'plural label' => t('Breadcrumbs'),

      'description' => t('Breadcrumb2 entity breadcrumbs.'),

      'entity class' => 'Breadcrumb',

      'controller class' => 'EntityAPIController',

      'base table' => 'breadcrumb',

      'fieldable' => TRUE,

      'view modes' => array(

        'full' => array(

          'label' => t('Breadcrumb'),

          'custom settings' => FALSE,

        ),

      ),

      'entity keys' => array(

        'id' => 'pid',

      ),

      'bundles' => array(

        'breadcrumb2' => array(

          'label' => t('Breadcrumb'),

          'admin' => array(

            'path' => 'admin/structure/breadcrumbs',

            'access arguments' => array('administer breadcrumbs'),

          ),

        ),

      ),

      'bundle keys' => array(

        'bundle' => 'type',

      ),

      'uri callback' => 'entity_class_uri',

      'access callback' => 'breadcrumb2_access',

      'module' => 'breadcrumb2',

      'metadata controller class' => 'Breadcrumb2MetadataController'

    ),

  );

  return $return;

}

首先,这个钩子的实现,我们主要参考了profile2_entity_info的实现,这里是以它为基础做的修改。其次,由于我们这里只有一个bundle,并且不允许创建其它的bundle,这个和user实体非常类似,所以中间的部分代码,我们借鉴的是user_entity_info的实现,我们来看一下user模块的实现:

function user_entity_info() {

  $return = array(

    'user' => array(

      'label' => t('User'),

      'controller class' => 'UserController',

      'base table' => 'users',

      'uri callback' => 'user_uri',

      'label callback' => 'format_username',

      'fieldable' => TRUE,

      // $user->language is only the preferred user language for the user

      // interface textual elements. As it is not necessarily related to the

      // language assigned to fields, we do not define it as the entity language

      // key.

      'entity keys' => array(

        'id' => 'uid',

      ),

      'bundles' => array(

        'user' => array(

          'label' => t('User'),

          'admin' => array(

            'path' => 'admin/config/people/accounts',

            'access arguments' => array('administer users'),

          ),

        ),

      ),

      'view modes' => array(

        'full' => array(

          'label' => t('User account'),

          'custom settings' => FALSE,

        ),

      ),

    ),

  );

  return $return;

}

我们的'entity keys''bundles''view modes',都是从user_entity_info借鉴过来的。所谓借鉴,就是将它们的代码复制过来,然后改成我们自己的。比葫芦画瓢。比如'entity keys',最初这个是从profile2中借鉴过来的,profile2_entity_info中这样定义的:

      'entity keys' => array(

        'id' => 'pid',

        'bundle' => 'type',

        'label' => 'label',

      ),

我们知道,pidprofile里面的主键,我们将它修改为bid,就成了最初的样子:

      'entity keys' => array(

        'id' => 'bid',

        'bundle' => 'type',

        'label' => 'label',

      ),

 

然后再借鉴一下user里面的实现,这个时候,我们会发现里面的'bundle''label'没有什么用,把它们删除,就成了现在的样子:

      'entity keys' => array(

        'id' => 'pid',

      ),

Drupal里面的很多钩子,尤其是这种带有info后缀的钩子,里面通常是一个大的数组,遇到这样的钩子,我们学习的路径,最好是找个类似的实现作为参考,当然我们还需要阅读这个钩子的文档,弄清楚里面的键值的具体含义,不过大部分键的含义,从字面上很容易理解出来,比如这里的'label''plural label''description''entity class''controller class''base table''fieldable'等。


Drupal版本:

7.4 实现Hook_menu

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在hook_entity_info里面,有这样的设置“'path' => 'admin/structure/breadcrumbs'”

这里所用的路径,我们需要在hook_menu里面定义一下,现在我们就来实现这个路径。向module文件中添加以下代码:

/**

 * Implements hook_menu().

 */

function breadcrumb2_menu() {

  $items['admin/structure/breadcrumbs'] = array(

    'title' => 'Breadcrumbs',

    'description' => 'Manage breadcrumbs.',

    'page callback' => 'breadcrumb2_overview_breadcrumbs',

    'access arguments' => array('administer breadcrumbs'),

    'file' => 'breadcrumb2.admin.inc',

  );

  $items['admin/structure/breadcrumbs/list'] = array(

    'title' => 'List',

    'type' => MENU_DEFAULT_LOCAL_TASK,

    'weight' => -10,

  );

}

注意,我们必须把admin/structure/breadcrumbs/list指定为MENU_DEFAULT_LOCAL_TASK,这样Field模块可以在admin/structure/breadcrumbs路径后面添加两个子标签,管理字段、管理显示。

接着创建breadcrumb2.admin.inc文件,向里面添加我们的回调函数breadcrumb2_overview_breadcrumbs

<?php

 

/**

 * @file

 * Breadcrumb administration and module settings UI.

 *

 */

 

/**

 * Displays the breadcrumb admin overview page.

 */

function breadcrumb2_overview_breadcrumbs(){

  $build['#markup'] = t('Breadcrumb2 overview breadcrumbs');

  return $build;

}

这里面还没有逻辑代码。我们只是想快速的实现一个骨架,然后再逐步的完善里面具体的细节。


Drupal版本:

7.5 权限与访问控制

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

在我们的菜单项里面,用到了权限'administer breadcrumbs',现在就让我们定义这个权限,向module文件中添加以下代码:

/**

 * Implements hook_permission().

 */

function breadcrumb2_permission() {

  $permissions = array(

    'administer breadcrumbs' => array(

      'title' => t('Administer breadcrumbs'),

      'description' => t('Edit and view all entity breadcrumbs.'),

    ),  

  );

  return $permissions;  

}

面包屑也有增删查改,但是这些工作都是由管理员来做的,所以我们这里比较省事,就定义了一个'administer breadcrumbs'权限,其它的都省去了。

hook_entity_info里面,有这样的定义'access callback' => 'breadcrumb2_access',这里的breadcrumb2_access,就是一个访问控制回调函数,来看一下我们的实现:

 

/**

 * Determines whether the given user has access to a breadcrumb.

 *

 * @param $op

 *   The operation being performed. One of 'view', 'update', 'create', 'delete'

 *   or just 'edit' (being the same as 'create' or 'update').

 * @param $breadcrumb

 *   Optionally a breadcrumb to check access for. If nothing is

 *   given, access for all breadcrumbs is determined.

 * @param $account

 *   The user to check for. Leave it to NULL to check for the global user.

 * @return boolean

 *   Whether access is allowed or not.

 */

function breadcrumb2_access($op, $breadcrumb = NULL, $account = NULL) {

  if (user_access('administer breadcrumbs', $account)) {

    return TRUE;

  }

  return FALSE;

}

对于比较复杂的实体,比如profile2,这个访问回调里面还会定义一个新的钩子hook_profile2_access

 

function profile2_access($op, $profile = NULL, $account = NULL) {

  if (user_access('administer profiles', $account)) {

    return TRUE;

  }

  if ($op == 'create' || $op == 'update') {

    $op = 'edit';

  }

  // Allow modules to grant / deny access.

  $access = module_invoke_all('profile2_access', $op, $profile, $account);

 

  // Only grant access if at least one module granted access and no one denied

  // access.

  if (in_array(FALSE, $access, TRUE)) {

    return FALSE;

  }

  elseif (in_array(TRUE, $access, TRUE)) {

    return TRUE;

  }

  return FALSE;

}

node_access里面,也定义了类似的钩子:

$access = module_invoke_all('node_access', $node, $op, $account);

 

当然,我们这里不需要定义一个hook_breadcrumb2_access,所以我们的就比较简单。


Drupal版本:

7.6 加载、删除、创建、保存实体

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

对于定义的实体类型,我们最好为其实现增删改查等API函数,方便别处调用:

{entity_type}_load(),

{entity_type}_create(),

{entity_type}_save(),

{entity_type}_delete_multiple(),

Entity API模块,为我们提供了entity_create()、 entity_save()、 entity_delete()等函数,直接使用Entity API里面的即可,我们还有必要定义自己的么?最好还是定义一下,也就是有这个必要。我们来看看我们的实现:

/**

 * Fetch a breadcrumb object. 

 *

 * @param $bid

 *   Integer specifying the breadcrumb id.

 * @param $reset

 *   A boolean indicating that the internal cache should be reset.

 * @return

 *   A fully-loaded $breadcrumb object or FALSE if it cannot be loaded.

 *

 * @see breadcrumb2_load_multiple()

 */

function breadcrumb2_load($bid, $reset = FALSE) {

  $breadcrumbs = breadcrumb2_load_multiple(array($bid), array(), $reset);

  return reset($breadcrumbs);

}

 

 

/**

 * Load multiple breadcrumbs based on certain conditions.

 *

 * @param $bids

 *   An array of breadcrumb IDs.

 * @param $conditions

 *   An array of conditions to match against the {breadcrumb} table.

 * @param $reset

 *   A boolean indicating that the internal cache should be reset.

 * @return

 *   An array of breadcrumb objects, indexed by bid.

 *

 * @see entity_load()

 * @see breadcrumb2_load()

 */

function breadcrumb2_load_multiple($bids = array(), $conditions = array(), $reset = FALSE) {

  return entity_load('breadcrumb2', $bids, $conditions, $reset);

}

 

/**

 * Deletes a breadcrumb.

 */

function breadcrumb2_delete(Breadcrumb $breadcrumb) {

  $breadcrumb->delete();

}

 

 

/**

 * Delete multiple breadcrumbs.

 *

 * @param $bids

 *   An array of breadcrumb IDs.

 */

function breadcrumb2_delete_multiple(array $bids) {

  entity_get_controller('breadcrumb2')->delete($bids);

}

 

 

/**

 * Create a breadcrumb object.

 */

function breadcrumb2_create($values = array()) {

  return new Breadcrumb($values);

}

 

 

/**

 * Saves a breadcrumb to the database.

 *

 * @param $breadcrumb

 *   The breadcrumb object.

 */

function breadcrumb2_save(Breadcrumb $breadcrumb) {

  return $breadcrumb->save();

}

这都是模式化的代码,对应的实现,可以参考profile2,也可以参考model模块。


Drupal版本:

7.7 定义实体类

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在breadcrumb2_entity_info里面,将'entity class'指定为了Breadcrumb,现在我们来看看这个类的定义。向breadcrumb2.module里面添加以下代码:

/**

 * The class used for breadcrumb entities

 */

class Breadcrumb extends Entity {

  

  public function __construct($values = array()) {

    parent::__construct($values, 'breadcrumb');

  }

 

  protected function defaultLabel() {

    return $this->path;

  }

 

  protected function defaultUri() {

    return array('path' => 'breadcrumb/' . $this->bid);

  }

}

  类似的代码可以参看model里面的Model类的实现。

   

  我们将'controller class'设置为了EntityAPIController,当然,我们也可以有自己的实现,比如将其设置为BreadcrumbController。此时需要定义这个控制器类,我们向module文件追加以下代码:

 

/**

 * The Controller for Breadcrumb entities

 */

class BreadcrumbController extends EntityAPIController {

  public function __construct($entityType) {

    parent::__construct($entityType);

  }

 

  /**

   * Create a breadcrumb - we first set up the values that are specific

   * to our breadcrumb schema but then also go through the EntityAPIController

   * function.

   * 

   * @param $type

   *   The machine-readable type of the breadcrumb.

   *

   * @return

   *   A breadcrumb object with all default fields initialized.

   */

  public function create(array $values = array()) {

    // Add values that are specific to our Breadcrumb

    $values += array( 

      'bid' => '',

    );

    

    $breadcrumb = parent::create($values);

    return $breadcrumb;

  }

  

这里也是模仿的model模块的实现。


Drupal版本:

7.8 元数据控制器类

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们将'metadata controller class'设置为了Breadcrumb2MetadataController,对于这个类型,我们将它放到了breadcrumb2.info.inc文件中了。来看一下它的代码:

 

<?php

 

/**

 * @file

 * Provides Entity metadata integration.

 */

 

/**

 * Extend the defaults.

 */

class Breadcrumb2MetadataController extends EntityDefaultMetadataController {

 

  public function entityPropertyInfo() {

    $info = parent::entityPropertyInfo();

    $properties = &$info[$this->type]['properties'];

 

    return $info;

  }

}

我们这里面,这个类是空类,里面其实没有自己的实现。我们是模仿profile2的,它的这个元数据控制器类,是这样定义的:

class Profile2MetadataController extends EntityDefaultMetadataController {

 

  public function entityPropertyInfo() {

    $info = parent::entityPropertyInfo();

    $properties = &$info[$this->type]['properties'];

 

    $properties['label'] = array(

      'label' => t('Label'),

      'description' => t('The profile label.'),

      'setter callback' => 'entity_property_verbatim_set',

      'setter permission' => 'administer profiles',

      'schema field' => 'label',

    );

    return $info;

  }

}

 

/**

 * Implements hook_entity_property_info_alter().

 */

function profile2_entity_property_info_alter(&$info) {

我们的面包屑里面,除了bid,不包含其它属性,同时只有一个bundle,我们这里可能就不需要定义这个元数据控制器类,只需要使用Entity API提供的默认的EntityDefaultMetadataController即可。

另外需要注意的是,在这个info.inc文件里面可以定义实现钩子hook_entity_property_info_alter。如果这个钩子实现可以放在这里的话,那么也可以在这里实现hook_entity_property_info,这是我的猜测。这样做的目的也是为了减小module文件的大小。


Drupal版本:

7.9 调试代码

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

一切工作准备就绪,让我们启用Breadcrumb2模块,我们需要预先安装好它所依赖的模块。都准备好了。启用好模块以后,没有报错,接着我们访问admin/structure,在这里我们并没有找到我们的面包屑:

图片1.png 

而管理字段、管理显示两个标签却显示了出来,点击这两个标签,发现它们的路径分别为admin/structure/breadcrumbs/fieldsadmin/structure/breadcrumbs/display。功能和我们在其它地方看到的一样。admin/structure/breadcrumbs哪里去了?

我们回过头来检查代码,发现我们在breadcrumb2_menu少写下面一句代码:

return $items;

一个很低级的错误。我们把它补上。我最初开发的时候,除了这个问题,还遇到了找不到Breadcrumb2MetadataController类的问题。因为我没有创建breadcrumb2.info.inc文件,也没有添加Breadcrumb2MetadataController的实现。我是在发现admin/structure/breadcrumbs不存在这个问题以后,清空缓存后,整个网站无法访问了,并且提示Breadcrumb2MetadataController不存在。这个时候,我才添加的breadcrumb2.info.inc文件。

现在清除缓存,admin/structure/breadcrumbs显示出来了:

图片2.png


Drupal版本: