EntityFieldQuery API 2

论坛: 

上一篇文章介绍了entityfieldquery只用几行代码查询是如此的简单。在本文的第2部分我们将采取一些更实际的方式把entityfieldquery使用看看.
我们的实例模块
本文的目的,任何创建一个简单的模块,entityfieldquery实例演示一些使用entityfieldquery。这些托管在GitHub上你可以下载源代码了解更多的细节,你也可以安装此模块看到实际的输出代码.

关于模块包含了一些快速笔记:除了回调和块代码下面描述的模块,安装三的节点类型,efq_article,efq_page,和efq_photo。这些都是很简单的节点类型:efq_article和efq_page每个包含文本的本体领域,而efq_photo包含一个图像场。此外所有三个内容类型包含一个US States字段,我们将使用构建我们的示例查询.

我建议使用开发的内容产生快速生成你的实例的内容,虽然你当然可以手动创建它如果你喜欢。在我的例子的目的,我创建了200个节点,使用下面的命令:   

drush genc --types="efq_page,efq_article,efq_photo" 200

当你卸载这个模块,它将清理后,去除所有创建的内容。我想这对我的cats教学方法。

entityfieldquery扩展

的第一件事就是要记住entityfieldquery是一个适当的PHP class。你可以扩展它的行为以适合你的需要的手段。例如,使用entityfieldquery时,你可能会发现你经常试图找到节点。如果你想发现你会在公共页面和区块清单的内容,你可能想让它被公布于众。此外,列出的内容往往是发表的最新内容第一.

让我们创造我们自己的entityfieldquerynodeentityfieldquery版本,这些属性已设置:

class NodeEntityFieldQuery extends EntityFieldQuery {
  // define some defaults for the class
  public function __construct() {
    // now we don't need to define these over and over anymore
    $this
      ->entityCondition('entity_type', 'node')
      ->propertyCondition('status', 1)
      ->propertyOrderBy('created', 'DESC');
    // define a pager
    $this->pager();
  }
  public function excludeNode($nid) {
    // code snip; we'll come back to this.
  }
 
}

这是非常简单的。每一次我们创建一个新的nodeentityfieldquery,一些东西会为我们已经预先设定的,即我们正在寻找发表节点和我们希望他们在反向顺序返回。此外,我们实例化一个寻呼机,因为我们会想要一个。如果我们发现自己重复使用nodeentityfieldquery,我们将通过所有这些价值预设保存自己一些时间.

Also note that we can extend the class with our own methods. We’ll come back to that method later.
一个简单的列表页
让我们创建一个简单的列表页开始。这是去倒序列出我们的三例内容类型的所有节点,以及提供一个寻呼机。首先,我们需要定义我们的网页菜单项和回调:

/**
 * Implements hook_menu().
 */
function efq_example_menu() {
  $items['efq'] = array(
    'title' => 'EntityFieldQuery example: recently published content',
    'page callback' => 'efq_example_listing',
    'access arguments' => array('access content'),
  );
  return $items;
}

现在我们可以开始我们的回调函数来为我们提供的清单。我们将继续这一开始非常基本的:我们刚刚上市的三种类型的所有节点,已公布的,按时间倒序.

function efq_example_listing() {
  // instantiate the query using our extended query object
  $query = new NodeEntityFieldQuery();
  // let our query know which node types we want
  $query
    ->entityCondition('bundle', array('efq_article', 'efq_page', 'efq_photo'));
  // execute the query
  $result = $query->execute();
  $output = array();
  // return the nodes as teasers
  if (!empty($result['node'])) {
    $output['nodes'] = node_view_multiple(node_load_multiple(array_keys($result['node'])), 'teaser');
    $output['pager']['#markup'] = theme('pager', $query->pager);
  }
  else {
    $output['status']['#markup'] = t('No results were returned.');
  }
  return $output;
}

这就是我们所需要做的一切给我们提供一个输出列表页。这不完全是Drupal的傻瓜但它真的不是难,特别是在非常少的代码得到的结果.
添加参数
当然如果我们能过滤该列表的一些有趣的 Let’s add some arguments to our callback. Our current URL is at efq. Let’s set it up so that we can use URLs in the form efq/%state/%node_type as well. We’ll have to set up our callback such that both state and node_type are optional, and to be aware of the arguments if they’re present.

Our new callback isn’t substantially different:

function efq_example_listing($state = NULL, $node_types = array('efq_article', 'efq_page', 'efq_photo')) {
  // instantiate the query using our extended query object
  $query = new NodeEntityFieldQuery();
  // set up some basic parameters
  $query
    ->entityCondition('bundle', $node_types);
  // if $state is defined, add that fieldCondition to the query
  if (!empty($state)) {
    $query->fieldCondition('field_us_state', 'value', $state);
  }
  // execute the query
  $result = $query->execute();
  $output = array();
  // return the nodes as teasers
  if (!empty($result['node'])) {
    $output['nodes'] = node_view_multiple(node_load_multiple(array_keys($result['node'])), 'teaser');
    $output['pager']['#markup'] = theme('pager', $query->pager);
  }
  else {
    $output['status']['#markup'] = t('No results were returned.');
  }
  return $output;
}

In our new callback, we look for a value for $state, and if it is present, we add a fieldCondition to our query. Additionally, we set up $node_types with a default value of an array of all of our content types, and look for a changed value. Note that the condition code is flexible:

  $query
    ->entityCondition('bundle', $node_types);

$node_types can be an array, as is the default, or it can be a string, which is how it will come in from the URL. That’s fine; the condition will adjust to the type of argument you pass.
Building a Content-sensitive Block
So now we have a listing of our content, and it can accept arguments. It would be nice also if, on our individual nodes, we could find other content for our given state. Let’s construct a block that shows the five most recent items from that node’s state. Additionally, we should probably provide some links back to the listing pages, again based on state.
Constructing the framework for the block itself is basic Drupal:

/**
 * Implements hook_block_info().
 */
function efq_example_block_info() {
  $blocks['content_by_state'] = array(
    'info' => t('Other Content In This State'),
    'cache' => DRUPAL_NO_CACHE
  );
  return $blocks;
}
/**
 * Implements hook_block_view().
 */
function efq_example_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'content_by_state':
      $block = efq_example_content_by_state();
      break;
  }
  return $block;
}

Now of course we need to populate that block. Here’s the function that will do that for us (don’t worry, we’re going to break this down):

/**
 * Produces content for a block, based on the state of the host node.
 */
function efq_example_content_by_state() {
  $block = array();
  // if we don't have a node to draw from, in our current setup, we won't have a state and can't continue
  if ($node = menu_get_object()) {
    // get the state value
    $field_name = "field_us_state";
    if (!empty($node->$field_name)) {
      $items = field_get_items('node', $node, $field_name);
      $state = $items[0]['value'];
    }
    // only continue if we have a state value
    if ($state) {
      // instantiate the query
      $query = new NodeEntityFieldQuery;
      // limit the query to our established node types
      $node_types = array('efq_article', 'efq_page', 'efq_photo');
      $query->entityCondition('bundle', $node_types);
      // add the state value
      $query->fieldCondition($field_name, 'value', $state);
      // add a small range
      $query->range(0, 5);
      // remove the current node from the query
      $query->excludeNode();
 
      // execute the query
      $result = $query->execute();
      $output = array();
      $block['subject'] = t('Other Content for @state', array('@state' => $state));
 
      if (!empty($result['node'])) {
        // return the nodes as teasers
        $nodes = node_view_multiple(node_load_multiple(array_keys($result['node'])), 'teaser');
        $block['content']['nodes'] = $nodes;
        // let's include links to the content listing for convenience
        $node_types = array(
          'efq_article' => 'Articles',
          'efq_page' => 'Pages',
          'efq_photo' => 'Photos',
        );
        $links = array(l(t('All Content for @state', array('@state' => $state)), "efq/$state"));
        foreach ($node_types as $node_type => $node_type_name) {
          $links[] = array(l(t('All @node_type_name for @state', array('@node_type_name' => $node_type_name, '@state' => $state)), "efq/$state/${node_type}"));
        }
        $item_list = array(
          '#items' => $links,
          '#type' => 'ul',
          '#theme' => 'item_list',
        );
        $block['content']['links'] = $item_list;
      }
      else {
        $block['content']['status']['#markup'] = t('No results.');
      }
    }
  }
  return $block;

Let’s break this down a bit.

  // if we don't have a node to draw from, in our current setup, we won't have a state and can't continue
 
  if ($node = menu_get_object()) {
    // get the state value
    $field_name = "field_us_state";
    if (!empty($node->$field_name)) {
      $items = field_get_items('node', $node, $field_name);
      $state = $items[0]['value'];
    }
    // only continue if we have a state value
    if ($state) {
 
    // ....

The beginning of this is quite straightforward. If we don’t have a node, there’s no point in continuing. Also, we need the node to have a state value, or else there isn’t any point in looking for state content for our block.

      // instantiate the query
      $query = new NodeEntityFieldQuery;
      // limit the query to our established node types
      $node_types = array('efq_article', 'efq_page', 'efq_photo');
      $query->entityCondition('bundle', $node_types);
      // add the state value
      $query->fieldCondition($field_name, 'value', $state);
      // add a small range
      $query->range(0, 5);

This looks very similar to our page listing query. We define a new query via NodeEntityFieldQuery, let the query know what kinds of nodes we’re looking for, and give it the value for the state field. Additionally, we’re limiting the number of results returned to the 5 most recent, since this is a block.

One thing you might want to consider is that you may not want the results listed in the block to include the node you’re currently on. This makes sense: if you see the same page you’re already reading listed in a sidebar, it can feel sloppy, or else like you’re having your time wasted.

Luckily, it is not hard to do this. Remember when we extended EntityFieldQuery to create NodeEntityFieldQuery? Remember this bit?

  public function excludeNode($nid) {
 
    // code snip; we'll come back to this.
  }

We can add our own methods to our new class. Let’s do that now.

  /**
   * If we're on a node, and if the entity_type is node, exclude the local node from the query
   */
  public function excludeNode($nid) {
    if (!$nid) {
      $object = menu_get_object();
      $nid = $object->nid;
    }
    if (!empty($nid) && $this->entityConditions['entity_type']['value'] === 'node') {
      $this->propertyCondition('nid', $nid, '<>');
    }
    return $this;
  }

This is a fairly simple method. If you pass in a node ID it will use it; otherwise it will attempt to use the node ID on the page you’re currently on. If you’re using EntityFieldQuery to search for nodes, and there is a node ID, this method will tell your query to exclude the current node. The key item is this line:

  $this->propertyCondition('nid', $nid, '<>');

Note the “<>” operator. Normally if you’re looking to match a value or set of values, you can leave the operator out of your propertyConditions, fieldConditions, etc., because they’re set to “=” or “IN” by default, depending on whether you have a single value or multiple that you’re matching against. If you want to use a different operator for your condition, you have to enter it explicitly.

Now that we have our method, we just need to add a quick method call to our block callback function, and we’re set:

      // remove the current node from the query
      $query->excludeNode();

Most of the rest of the callback looks quite similar to our page listing callback:

     // execute the query      $result = $query->execute();
      $output = array();
      $block['subject'] = t('Other Content for @state', array('@state' => $state));
      if (!empty($result['node'])) {
        // return the nodes as teasers
        $nodes = node_view_multiple(node_load_multiple(array_keys($result['node'])), 'teaser');
        $block['content']['nodes'] = $nodes;
We execute our query. We set the block title, since that is going to be the same whether we had content or not. If there are results from our query, we load them and then slot them into the block content.
We do generate a set of links to allow the user to get back to the page listings easily:
        // let's include links to the content listing for convenience
        $node_types = array(
          'efq_article' => 'Articles',
          'efq_page' => 'Pages',
          'efq_photo' => 'Photos',
        );
        $links = array(l(t('All Content for @state', array('@state' => $state)), "efq/$state"));
        foreach ($node_types as $node_type => $node_type_name) {
          $links[] = array(l(t('All @node_type_name for @state', array('@node_type_name' => $node_type_name, '@state' => $state)), "efq/$state/${node_type}"));
        }
        $item_list = array(
          '#items' => $links,
          '#type' => 'ul',
          '#theme' => 'item_list',
        );
        $block['content']['links'] = $item_list;


And finally, because we’re relatively thorough, we write a simple message to the block if not results are returned:

      else {
        $block['content']['status']['#markup'] = t('No results.');
      }

This Looks Kind Of Familiar…

If you’re an old Drupal hand, you might be wondering why I wouldn’t just build this in Views. Indeed, this might look a bit masochistic if you’re used to Views. However, there are a few things to consider:




    EntityFieldQuery is core. If you are interested in keeping your installation as lean as possible, this is something to consider. Views is a contributed module, and so it automatically adds size and overhead to your Drupal install.
    EntityFieldQuery is small. The amount of code required to generate an equivalent listing in Views, compared to what we built above, is much, much greater. To be fair, your Views code will almost certainly be exported and managed via Features, and so in effect you’re not writing the Views code anyway. But, that highlights a different point: writing code this way keeps you in code, rather than requiring you to work in a point-and-click interface. For the average developer, this is a very good thing.
    EntityFieldQuery is code. You might want to have certain things exposed to change and not others. For example, on a recent project, we were building a site for a client that had site builders on staff. These people were comfortable working with Views, and we wanted to make sure that they had the ability to create and modify views. However, we wanted to also make sure that there was some core functionality that was left untouched. If we had built that core functionality with Views, it would have been exposed to change. By building that core functionality with EntityFieldQuery, we kept it strictly in code, which protected it.

Summing Up and Coming Up

There is a lot of code in this post. If you are interested in understanding this better, I recommend installing the sample module, creating some test content, and playing with the code. I think much of the code is a lot easier to understand in context.

In our next post, we’ll look at some more advanced EntityFieldQuery techniques. We’ll also look at scenarios when EntityFieldQuery is a good candidate for the job, and some scenarios when EntityFieldQuery is not what you want to use.

相关文章: