欢迎各位兄弟 发布技术文章
这里的技术是共享的
在没有使用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();
这个模块提供了更多的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
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
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
作者:老葛,北京亚艾元软件有限责任公司,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来定义实体类型的话,此时与Views、Rules的集成都比较友好。Entity API模块提供了Views的基本集成,而Rules模块则是Fago的另一个杰作,本身就是基于Entity API模块的。
来自 http://www.thinkindrupal.com/node/5745