欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

第1章 实体(Entity)API (三) 有大用

shiping1 的头像

8 EntityFieldQuery

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

在没有使用EntitiFieldQuery之前,我一直都是采用db_select的形式,这个我们在前面已经讲过了。我们先来看一个实际的例子,这段代码主要是我写的:

<?php

/**

 * @file

 * Field validation unique validator.

 *

 */

$plugin = array(

  'label' => t('Unique values'),

  'description' => t('Verifies that all values are unique in current entity or bundle.'),

  'handler' => array(

    'class' => 'field_validation_unique_validator',

  ),

);

 

class field_validation_unique_validator extends field_validation_validator {

 

  /**

   * Validate field. 

   */

  public function validate() {

    $flag = TRUE;

    $scope = $this->rule->settings['data'];

    $count = 0;

    foreach ($this->items as $delta1 => $item1) {

      if ($this->delta != $delta1) {

        if ($this->value == $item1[$this->rule->col]) {

          $flag = FALSE;

          break;

        }

      }

    }

    if ($flag) {

      $query = new EntityFieldQuery();

      if ($scope == 'global') {

      }

      elseif ($scope == 'entity') {

        $query->entityCondition('entity_type', $this->rule->entity_type);

      }

      elseif ($scope == 'bundle') {

        $query->entityCondition('entity_type', $this->rule->entity_type);

        $query->entityCondition('bundle', $this->rule->bundle);

      }

 

      list($id, $vid, $bundle) = entity_extract_ids($this->rule->entity_type, $this->entity);

      if ($this->rule->entity_type == 'user' && arg(0) =='user' && arg(2) =='edit' && empty($id)) {

        $id = arg(1);

      }

      if ($this->rule->entity_type == 'field_collection_item' && arg(0) == 'field-collection' && arg(3) =='edit' && empty($id)) {

        $id = arg(2); 

      }

      if ($this->rule->entity_type == 'profile2' && empty($id)) {

        $arg_index = 1;

        if (module_exists('profile2_page')) {

          $profile_type = profile2_type_load($this->entity->type);

          $path = profile2_page_get_base_path($profile_type);

          $arg_index = count(explode('/', $path));

        }

        $uid = arg($arg_index);

        if (arg($arg_index + 1) == 'edit' && is_numeric($uid) && $account = user_load($uid)) {

          if ($profile = profile2_load_by_user($account, $this->entity->type)) {

            $id = $profile->pid;

          }

        }

      }

      if (!empty($id)) {

        $query->entityCondition('entity_id', $id, '!=');

      }

      //Always bypass all access checkings.

      $query->addMetaData('account', user_load(1));

      $query->fieldCondition($this->rule->field_name, $this->rule->col, $this->value);

 

      // Store a copy of our matched entities for our use in tokens later.

      $matched_entities = $query->execute();

 

      $count = $query

        ->count()

        ->execute();

      if ($count) {

        $flag = FALSE;

 

      }

 

    }

 

    if (!$flag) {

      $token = array(

        '[count]' => $count,

      );

 

      // Find the first entity that failed this unique condition so we can

      // add a token referencing it. First, we have some special handling for

      // field collection entities so we can find the entity title of

      // whatever the specific field is connected to.

      $entity_types = array_keys($matched_entities);

      $entity_type = reset($entity_types);

      $matched_entity = reset($matched_entities);

      $first_match = reset($matched_entity);

      $entity_info = entity_get_info($entity_type);

      $entity_key_id = $entity_info['entity keys']['id'];

      $entitys = entity_load($entity_type, array($first_match->{$entity_key_id}));

      $entity = reset($entitys);

 

      if ($entity_type == 'field_collection_item') {

        $host_type = $entity->hostEntityType();

        $host_entity = $entity->hostEntity();

        $label = entity_label($host_type, $host_entity);

        $uri = entity_uri($host_type, $host_entity);

      }

      else {

        $label = entity_label($entity_type, $entity);

        $uri = entity_uri($entity_type, $entity);

      }

 

      $token['[existing-entity-label]'] = $label;

      $token['[existing-entity-link]'] = l($label, $uri['path'], $uri['options']);

 

      $this->set_error($token);

    }

  }

  

  /**

   * Provide settings option

   */

  function settings_form(&$form, &$form_state) {

    $default_settings = $this->get_default_settings($form, $form_state);

    //print debug($default_settings);

    $form['settings']['data'] = array(

      '#title' => t('Scope of unique'),

      '#description' => t("Specify the scope of unique values, support: global, entity, bundle."),

      '#type' => 'select',

      '#options' => array(

        'global' => t('Global'),

        'entity' => t('Entity'),

        'bundle' => t('Bundle'),

      ),  

      '#default_value' => isset($default_settings['data']) ? $default_settings['data'] : '',

    );

    parent::settings_form($form, $form_state);

  }

  

  /**

   * Provide token help info for error message.

   */

  public function token_help() {

    $token_help = parent::token_help();

    $token_help += array(

      '[count]' => t('Count of duplicate'),

      '[existing-entity-label]' => t('The label of the first entity that contains matching data.'),

      '[existing-entity-link]' => t('A link to the first entity that contains matching data.'),

    );

    return $token_help;

  }

}

这里面涵盖了EntitiFieldQuery的常见用法,首先,我们使用下面的语句,初始化查询语句:

$query = new EntityFieldQuery();

来自 http://www.thinkindrupal.com/node/5764

6完善的API函数

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

这个模块提供了更多的API函数,比如entity_create()、 entity_save()、 entity_delete()、 entity_view()、 entity_access(),这些函数可以用来方便的创建、保存、删除、查看实体,最后一个entity_access用来控制访问权限的。除了这几个以外,还有entity_id()、 entity_export()、 entity_import()entity_get_property_info()entity_id()是用来获取id的,entity_export负责导出,entity_import负责导入;entity_get_property_info是用来做什么的?它是用来获取实体上面的属性信息的,注意这里的属性,和Drupal核心里面的属性是不一样的概念,在Drupal7核心里面,属性是属性,字段是字段,而在Entity API模块里面,属性、字段都是属性。

来自 http://www.thinkindrupal.com/node/5742

 

6.1 hook_entity_property_info

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

Entity API模块定义了一个新的钩子,hook_entity_property_info(),通过这个钩子函数,就可以定义实体包含哪些属性信息了,这个信息里面还包括,属性的数据类型、获取、设置属性的回调函数。注意,很多常用的模块,都用到了这里定义的属性,比如Rules模块。而前面的entity_get_property_info,则是用来获取hook_entity_property_info()里面的定义信息的。这个钩子函数比较抽象,我们可以看一个例子,在sites\all\modules\entity\modules下面可以找到,Drupal核心中各个子系统的钩子实现。下面这个是用户系统的实现:

/**

 * Implements hook_entity_property_info() on top of user module.

 *

 * @see entity_entity_property_info()

 */

function entity_metadata_user_entity_property_info() {

  $info = array();

  // Add meta-data about the user properties.

  $properties = &$info['user']['properties'];

 

  $properties['uid'] = array(

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

    'type' => 'integer',

    'description' => t("The unique ID of the user account."),

    'schema field' => 'uid',

  );

  $properties['name'] = array(

    'label' => t("Name"),

    'description' => t("The login name of the user account."),

    'getter callback' => 'entity_metadata_user_get_properties',

    'setter callback' => 'entity_property_verbatim_set',

    'sanitize' => 'filter_xss',

    'required' => TRUE,

    'access callback' => 'entity_metadata_user_properties_access',

    'schema field' => 'name',

  );

下面的这段则是节点系统的实现:

  $properties['created'] = array(

    'label' => t("Date created"),

    'type' => 'date',

    'description' => t("The date the node was posted."),

    'setter callback' => 'entity_property_verbatim_set',

    'setter permission' => 'administer nodes',

    'schema field' => 'created',

  );

  $properties['changed'] = array(

    'label' => t("Date changed"),

    'type' => 'date',

    'schema field' => 'changed',

    'description' => t("The date the node was most recently updated."),

  );

  $properties['author'] = array(

    'label' => t("Author"),

    'type' => 'user',

    'description' => t("The author of the node."),

    'setter callback' => 'entity_property_verbatim_set',

    'setter permission' => 'administer nodes',

    'required' => TRUE,

    'schema field' => 'uid',

  );

除了这些实体系统以外,字段系统也都被包含了进来,不过字段系统的代码比较抽象:

/**

 * Implements hook_entity_property_info() on top of field module.

 *

 * @see entity_field_info_alter()

 * @see entity_entity_property_info()

 */

function entity_metadata_field_entity_property_info() {

  $info = array();

  // Loop over all field instances and add them as property.

  foreach (field_info_fields() as $field_name => $field) {

    $field += array('bundles' => array());

    if ($field_type = field_info_field_types($field['type'])) {

      // Add in our default callback as the first one.

      $field_type += array('property_callbacks' => array());

      array_unshift($field_type['property_callbacks'], 'entity_metadata_field_default_property_callback');

 

      foreach ($field['bundles'] as $entity_type => $bundles) {

        foreach ($bundles as $bundle) {

          $instance = field_info_instance($entity_type, $field_name, $bundle);

 

          if ($instance && empty($instance['deleted'])) {

            foreach ($field_type['property_callbacks'] as $callback) {

              $callback($info, $entity_type, $field, $instance, $field_type);

            }

          }

        }

      }

    }

  }

  return $info;

}

 

/**

 * Callback to add in property info defaults per field instance.

 * @see entity_metadata_field_entity_property_info().

 */

function entity_metadata_field_default_property_callback(&$info, $entity_type, $field, $instance, $field_type) {

  if (!empty($field_type['property_type'])) {

    if ($field['cardinality'] != 1) {

      $field_type['property_type'] = 'list<' . $field_type['property_type'] . '>';

    }

    // Add in instance specific property info, if given and apply defaults.

    $name = $field['field_name'];

    $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];

    $instance += array('property info' => array());

    $property = $instance['property info'] + array(

      'label' => $instance['label'],

      'type' => $field_type['property_type'],

      'description' => t('Field "@name".', array('@name' => $name)),

      'getter callback' => 'entity_metadata_field_property_get',

      'setter callback' => 'entity_metadata_field_property_set',

      'access callback' => 'entity_metadata_field_access_callback',

      'query callback' => 'entity_metadata_field_query',

      'translatable' => !empty($field['translatable']),

      // Specify that this property stems from a field.

      'field' => TRUE,

      'required' => !empty($instance['required']),

    );

    // For field types of the list module add in the options list callback.

    if (strpos($field['type'], 'list') === 0) {

      $property['options list'] = 'entity_metadata_field_options_list';

    }

  }

}

Drupal核心系统,默认是不支持这个钩子的,但是Entity API模块把核心系统里面的所有涉及到的部分,都实现出来了。这里是包括所有的Drupal核心的字段类型的。我们在这里,前期不需要完全明白这些代码的含义,大致看一下,知道有这么回事就可以了,我们只需要记住,这里面,在属性数组信息里面,包含哪些常用的键就可以了。'label''description''getter callback''setter callback''sanitize''required''access callback''schema field'。有兴趣的话,可以沿着我们提示,把sites\all\modules\entity\modules下面,所有inc文件里面的代码都阅读一遍。注意,verbatim的中文意思为完全)照字面的(地),逐字的(地),里面的代码会遇到这个单词。

来自 http://www.thinkindrupal.com/node/5743

 

6.2 entity_metadata_wrapper

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

Entity API模块,还提供了一个元数据封装器函数,entity_metadata_wrapper,通过这个函数的封装,我们可以更加方便的访问实体及其属性。这个函数给我们带来了哪些便利呢?我们通过代码示例来了解一下。

 

比如,获取节点作者的电子邮件地址,可以使用下面的代码:

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

$wrapper->author->mail->value();

我们在这里看到,封装过后的这个对象,在它上面,可以采用链式调用,此外它能够直接的建立关联关系。对于上面的功能,如果不使用封装器的话,我们通常采用这样的代码:

$uid = $node->uid;

$user = user_load($uid);

$mail = $user->mail;

这里,我们的代码也并不复杂,看不出来entity_metadata_wrapper的优越性。据文档里面说,$wrapper->author->mail->value();这段代码,可以省去user_load这个操作,直接通过关联关系获取到用户的电子邮件地址,这样可能会有一点小的性能提升。但是这也是有代价的,代价就是调用了entity_metadata_wrapper通过这个例子,我们可以看到这个函数的基本用法。我们在前面让大家阅读代码的原因,就是为这里做准备的,为什么用的是$wrapper->author?而没有用$wrapper->uid,也没有用$wrapper->user?因为在hook_entity_property_info里面定义的是author

 

为了更新用户的电子邮件地址,我们可以使用下面的代码:

$wrapper->author->mail->set('test@test.com');

或者

$wrapper->author->mail = 'test@test.com';

 

换成我们熟悉的代码,则是:

$user->mail =  'test@test.com';

user_save($user);

 

为了获取电子邮件的属性信息,可以使用下面的代码:

$mail_info = $wrapper->author->mail->info();

 

获取过滤了的节点标题:

$wrapper->title->value(array('sanitize' => TRUE));

 

获取原始数据:

$wrapper->body->value->raw();

 

上面的这几个例子都比较简单,我们看一个复杂的:

$wrapper->author->profile->field_name->value();

$wrapper->author->profile->field_name->set('新名字');

在这里,从节点,到节点的作者,再到作者的profile,再到profile上面的字段field_name,上面的一句,用来获取field_name字段的值,下面一句用来为该字段设置一个新值。如果我们自己写代码的话,需要加载用户对象,需要加载profile2对象,访问一个字段的值时,需要知道使用哪个语言来访问,代码写起来就比较复杂了。但是,在这里,一行代码就搞定了,非常方便。最后,我们再多看几个示例用法:

$wrapper->language('de')->body->summary->value();

 

$wrapper->author->mail->access('edit') ? TRUE : FALSE;

$wrapper->author->roles->optionsList();

 

$wrapper->field_files[0]->description = 'The first file';

 

$wrapper->save();

$node = $wrapper->value();

来自 http://www.thinkindrupal.com/node/5744

 

6.3 简化了实体类型的定义

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

这个模块还提供了一个Entity对象,如果我们要定义一个新的实体类型时,只需要继承这个对象就可以了,这个对象帮助我们实现了完整的CRUD操作。

Entity API里面,所有的bundle,这里说的是Bundle本身,也都被处理成为了实体,这是一种可以导入、导出的实体。在Drupal核心中,内容类型的添加、编辑,是没有采用实体的形式的。Entity API模块,在这里走的更远,Bundle本身也处理成为了实体。我们看Profile2模块,在profile2_entity_info里面,可以找到这段代码:

  $return['profile2_type'] = array(

    'label' => t('Profile type'),

    'plural label' => t('Profile types'),

    'description' => t('Profiles types of Profile2 user profiles.'),

    'entity class' => 'ProfileType',

    'controller class' => 'EntityAPIControllerExportable',

    'base table' => 'profile_type',

    'fieldable' => FALSE,

    'bundle of' => 'profile2',

    'exportable' => TRUE,

    'entity keys' => array(

      'id' => 'id',

      'name' => 'type',

      'label' => 'label',

    ),

    'access callback' => 'profile2_type_access',

    'module' => 'profile2',

    // Enable the entity API's admin UI.

    'admin ui' => array(

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

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

      'controller class' => 'Profile2TypeUIController',

    ),

  );

通过这段代码,把Profile类型也处理成为实体了。对于这种包括Bundle类型的实体,Entity API还提供了一个管理界面,我们只需要对这个默认的管理界面进行扩展,就可以了。这里使用EntityDefaultUIController来管理默认的界面,Profile2TypeUIController则继承了EntityDefaultUIController

如果我们基于Entity API来定义实体类型的话,此时与ViewsRules的集成都比较友好。Entity API模块提供了Views的基本集成,而Rules模块则是Fago的另一个杰作,本身就是基于Entity API模块的。

来自 http://www.thinkindrupal.com/node/5745

普通分类: