Jul 05 2014
Jul 05

How to Build a Drupal 8 Module

Even though Drupal 7 core fell short of a proper way of handling its brand new entity system (we currently rely on the great Entity module for that), it did give us EntityFieldQuery. For those of you who don’t know, EntityFieldQuery is a very powerful querying class used to search Drupal entities programatically (nodes, users, etc).

It provides a number of methods that make it easy to query entities based on conditions such as field values or class properties. If you don’t know how it works, feel free to check out this documentation page or this great tutorial on the subject.

In this article I am going to talk about what we have in Drupal 8 for querying entities. There is no more EntityFieldQuery, but there’s an entity.query service that will instantiate a query object for a given entity type (and that implements the \Drupal\Core\Entity\Query\QueryInterface). We can access this service statically through the \Drupal namespace or using dependency injection.

First up, we’ll look at querying node entities and then we’ll see how to load them. The same techniques will work with other content entities as well (users, comments etc), but also with configuration entities, and that’s really cool.

The entity query service

As mentioned, there are two ways we can access the entity.query service that we use for querying entities. Statically, we can do this:

$query = \Drupal::entityQuery('node');

Instead of node, we can specify any other entity type machine name and what we get inside the $query variable is the query object for our entity type. The entityQuery() static method on the \Drupal namespace is a shortcut for doing so using the entity.query service.

Alternatively (and the highly recommended approach) is to use dependency injection.

If you have access to the container, you can load the service from there and then get the right query object:

$entity_query_service = $container->get('entity.query');
$query = $entity_query_service->get('node');

As you can see, we use the get() method on the entity_query service to instantiate a query object for the entity type with the machine name passed as a parameter.

Querying entities

Let’s illustrate a couple of examples of querying for node entities using this object.

A very simple query that returns the published nodes:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1);
    
$nids = $query->execute();

$nids will be an array of entity ids (in our case node ids) keyed by the revision ids (if there is revisioning enabled for the entity type) or the entity ids if not. Let’s see an example in which we add more property conditions as well as field conditions:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('changed', REQUEST_TIME, 'condition('title', 'cat', 'CONTAINS')
    ->condition('field_tags.entity.name', 'cats');

$nids = $query->execute();

In this query, we retrieve the node ids of all the published nodes that have been last updated before the current time, that have the word cat inside their title and that have a taxonomy term called cats as a reference in the field_tags.

As you can see, there is no more distinction between propertyCondition and fieldCondition (as there is in D7 with EntityFieldQuery). Additionally, we can include conditions based on referenced entities tacking on the entity.(column) to the entity reference field name.

An important thing to note is that we also have the langcode parameter in the condition() method by which we can specify what translation of the node should be included in the query. For instance, we can retrieve node IDs that contain a specific value inside of a field in one language but another value inside the same field for another language.

For more information on the condition() method you should consult the API documentation.

The next thing we are going to look at is using condition groups (both AND and OR) for more powerful queries:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('changed', REQUEST_TIME, 'orConditionGroup()
    ->condition('title', 'cat', 'CONTAINS')
    ->condition('field_tags.entity.name', 'cats');

$nids = $query->condition($group)->execute();

Above, we altered our previous query so as to retrieve nodes that either have the cat string in their title or have a reference to the term called cats in their field_tags field. And we did so by creating an orConditionGroup object that we then pass to the query as a condition. And we can group together multiple conditions within a andConditionGroup as well.

There are many other methods on the QueryInterface that can extend the query (such as for sorting, range, etc). I encourage you to check them out in the documentation and experiment with them. For now, though, let’s take a quick look at what to do with the result set.

Loading entities

As I mentioned above, the execute() method on the query object we’ve been working with returns an array of entity IDs. Supposedly we now have to load those entity objects and work with them. How do we do that?

In Drupal 7 we had the entity_load() function to which we passed an array of IDs and that would return an array of objects. In Drupal 8, this helper function is maintained and you can use it pretty much in the same way, except only for one entity at a time:

$node = entity_load('node', $nids[1]);

And the return value is a node object. To load multiple nodes, you can use the entity_load_multiple() function:

$nodes = entity_load_multiple('node', $nids);

Which then returns an array of entity objects keyed by their ids.

A bonus nugget of information is that both of these functions are wrappers for the storage manager of the entities in question. They basically retrieve the storage manager statically and then call the load() and loadMultiple() methods, respectively, on it:

Statically, you could do similarly:

$node_storage = \Drupal::entityManager()->getStorage('node');

// Load multiple nodes
$node_storage->loadMultiple($ids);
// Load a single node
$node_storage->load($id);

But better yet, you could use dependency injection and retrieve the storage class from the container:

$node_storage = $container->get('entity.manager')->getStorage('node');

And then proceed with the loading. Using dependency injection is usually the recommended way to go when it’s possible, i.e. when working within a class. This makes it easier to test your class and better decouples it from the rest of the application.

Conclusion

In this article we’ve seen how to work with querying and loading entities in Drupal 8. There has been an overhaul of the D7 EntityFieldQuery class that turned into a robust API for querying both content and configuration entities. We’ve looked at querying content entities but the system works just the same with config entities. And that is a bit of a win for the new Drupal 8 entity system.

We’ve also seen how to load entities based on the IDs resulted in these queries and what is actually behind the wrapper functions that perform these operations. Next up, we are going to look at defining our own content entity type in Drupal 8. For a refresher on how we do it in Drupal 7, you can check out these Sitepoint articles on the subject.

Feb 28 2012
Feb 28

In the first part of this introduction to EntityFieldQuery, we looked at how simple it was to quickly construct a query with only a few lines of code. In this second part of the series, we’ll take a look at some more practical ways to put EntityFieldQuery to use.

Our Example Module

For the purposes of this series, I’ve created a simple module, EntityFieldQuery Example, that demonstrates some uses of EntityFieldQuery. It’s hosted on Github; you can grab the source code if you’d like to see the code in more detail or if you’d like to install it yourself.

Some quick notes about what the module contains: in addition to the callbacks and block code described below, the module installs three node types, efq_article, efq_page, and efq_photo. These are very simple node types: efq_article and efq_page each contain a body field for text, while efq_photo contains a single image field. In addition, all three content types contain a US States field, which we’ll be using to construct our example queries.

I recommend using Devel content generate to quickly generate content for your examples, although you can of course create it manually if you like. For the purposes of my examples, I created 200 nodes using the following command: drush genc --types="efq_page,efq_article,efq_photo" 200.

When you uninstall this module, it will clean up after itself, removing all created content. I’m thinking of teaching this technique to my cats.

Extending EntityFieldQuery

One of the first things to to remember about EntityFieldQuery is that it is a proper PHP class. The means that you can extend its behavior to suit your needs. For example, when using EntityFieldQuery, you might find that you are often trying to find nodes. If you are trying to find content that you will be listing in public pages and blocks, you probably want it to all be published. Additionally, lists of content tend to be published with the most recent content first.

Let’s create our own version of EntityFieldQuery, NodeEntityFieldQuery, with these properties already set:

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.
  }

}

This is fairly straightforward. Each time we create a new NodeEntityFieldQuery, a number of things will be already pre-set for us, namely that we’re searching for published nodes and we want them returned in a reverse chronological order. Additionally, we instantiated a pager, since we’ll most likely want one. If we find ourselves using NodeEntityFieldQuery over and over again, we will save ourselves some time by having all these values pre-set.

Also note that we can extend the class with our own methods. We’ll come back to that method later.

A Simple Listing Page

Let’s start by creating a simple listing page. This is going to list all nodes of our three example content types in reverse chronological order, as well as providing a pager. First, we’ll need to define a menu item and callback for our page:

/**
 * 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;
}

Now we can start our callback to provide the listing for us. We’re going to keep this very basic to start with: we’re just listing all the nodes of our three types, that are published, in reverse chronological order.

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;
}

That’s all we need to do to provide us with a listing page with a pager. It’s not exactly Drupal For Dummies, but it really isn’t terribly difficult, and in particular it’s very few lines of code to get that result.

Adding Arguments

Of course, it would be more interesting if we could filter this list some. 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:

/<

strong

>
*

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;
} /</

strong

>
*

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:

  1. 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.

  2. 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.

  3. 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.

Feb 21 2012
Feb 21

As we outlined in a previous post about building the Energy.gov platform without Views, extending EntityFieldQuery (EFQ) is essential to our development philosophy.

One hurdle with using EFQ, however, is adding new fields to existing content. Querying for nodes on an existing platform when a new field is added conflicts with some of the assumptions made in EFQ. Archiving content among various organic groups on an existing site is a great example scenario to illustrate this point.

Let's say that in certain cases, you want users to be able to archive a story from appearing on the front page for one group, but still want the story to be published in another. In this case, the list of stories on the front page are controlled by EFQ.

For the user, you could simply add a new field that allows them to choose which group the story should be archived.

For the developer, EFQ presents a problem because existing nodes will not contain a value for the new archive field in the archive field table. Because EFQ uses INNER JOINs, it assumes the existence of values in all relevant tables, meaning content without an archive value will be excluded altogether.

The solution* is to use an 'OR' query.

Our goal is to either show content that is not in list of groups selected for the archive field OR to show content that does not have a value for the archive field.

But EFQ, as a class, does not work with 'OR' queries, right?

Well, technically 'no', but it does break down into the SelectQuery class upon execution, so our answer is to use an alter.

Altering by Tag

Since we're already extending EFQ, we create a method to tag the query so we can utilize hook_query_tag_alter in the module.

/**
 * @file
 * extends EntityFieldQuery, providing some useful added methods and some defaults
 */

class EnergyEntityFieldQuery extends EntityFieldQuery {
  /**
   * define some defaults for the class
   */

  public function __construct() {
    $this
      ->entityCondition('entity_type', 'node')
      ->propertyCondition('status', 1)
      ->propertyOrderBy('created', 'DESC');

    //archive a node for display
    $this->archive();
  }

  /* Other methods we discussed earlier are here */

  /**
   * Set archive tag to be manipulated by an alter later. Checking for null
   * values doesn't work in EFQ because of INNER JOIN
   *
   */

  public function archive() {
    $this->addTag('archive');
    return $this;
  }

/**
   * Unset archive tag
   */

public function

clearArchive

() {
unset($this->tags['archive']);
}
}

Within the EnergyEntityFieldQuery (EEFQ) class, we also like to add an 'undo' method as this allows us to remove an assumption in our extension where needed.

Without going into too much detail, part of the execution of EEFQ involves invoking the appropriate alter hooks.

We then define the alter in our module where we can apply a LEFT JOIN and OR query.

/**
 * Implements hook_query_TAG_alter
 *
 * Check if archive taxonomy is set for a group, but include entities
 * that may not have a value
 *
 * @param QueryAlterableInterface $query
 */

function energy_content_query_archive_alter(QueryAlterableInterface $query) {
  //grab the taxonomy by the existing og relationship
  $group = og_context_determine_context();
  $tid = $group->etid;
  $query
    ->leftJoin('field_data_field_archive', 'a', 'node.nid = a.entity_id');
  if ($tid) {
    $or = db_or()
          ->condition('a.field_archive_tid', array($tid), 'NOT IN')
          ->isNull('a.field_archive_tid');
    $query
      ->condition($or);
  }
}

The organic groups code is specific to the Energy.gov platform, so it can be ignored. It merely communicates how we grab the taxonomy id for comparison.

The key aspects are to LEFT JOIN the appropriate field table, then use db_or() to set your conditions.*

In this case, we're not only going to exclude nodes that may have the value of the 'archive' tid I've set, but we also want those nodes who may have a NULL value in that field table.

Hopefully this technique provides you with some ideas of how to extend EFQ even further. Additionally, it should provide a solution to those who wondered how to perform OR queries with EFQ in Drupal 7.

*Assuming you are using the SQL storage engine

Feb 16 2012
Feb 16

Drupal 7 introduced the EntityFieldQuery API. This class is a mechanism for quickly building queries of entities and fields – typically nodes, but of course not limited to that.

Drupal 7 features a much richer database abstraction layer than previous versions of Drupal, but in the end you are still more or less building SQL. EntityFieldQuery allows you to construct queries without much knowledge of SQL at all.

Drupal core itself mostly uses EntityFieldQuery as a utility function, in many cases simply to check that a given field has a value somewhere in the system or not. It’s strange that such a powerful mechanism is put to so little use by core, but no matter. Let’s see how we can use it.

Starting Your Query

Starting an entity field query looks like this:

$query = new EntityFieldQuery();

Extremely simple, right? That’s all it takes to get started. Let’s now add some conditions to our query.

$query
 ->entityCondition(‘entity_type’, ‘node’)
 ->entityCondition(‘bundle’, ‘article’)
 ->propertyCondition(‘status’, 1)
 ->propertyOrderBy(‘created’, ‘DESC’);

Let’s take a look at what’s going on here. First of all, notice the lack of semicolons between method calls. Each EntityFieldQuery method returns the same EntityFieldQuery object the method is called on. This allows us to chain method calls together, which speeds up coding and makes it easier to read. This will look familiar to anyone who uses jQuery. This could just as easily be written like so:

$query->entityCondition(‘entity_type’, ‘node’)->entityCondition(‘bundle’, ‘article’)->propertyCondition(‘status’, 1)->propertyOrderBy(‘created’, ‘DESC’);

But, that is really not easy at all to read. I recommend putting each method on its own line. Future generations of coders will thank you.

Now let’s look at each method and see what’s happening.

->entityCondition(‘entity_type’, ‘node’)

The first method uses an entityCondition to tell the query to look for node entities. It may seem like specifying this is redundant, but as many other things in Drupal 7 are also entities – users, taxonomy terms, etc – you need to restrict your set.

->entityCondition(‘bundle’, ‘article’)

The second method tells the query which node types to restrict the set to. This can be left out if you would like to query all available nodes, but in most cases you will be looking for specific node types. Note also that the second argument, ‘article’ in this case, can be either a string or an array; the query will adjust automatically. So if you were looking for both article and page node types, you could rewrite that part of the chain as follows:

->entityCondition(‘bundle’, array(‘article’, ‘page’))

As you can see, it’s extremely easy to expand your query.

->propertyCondition(‘status’, 1)

The third method is a propertyCondition. A property in this case is any column in the base table for the given entity type. In the case of nodes, this could be whether it is published, the user that created the node, time of creation, etc. In our example above, we are modifying our query to only return published values.

->propertyOrderBy(‘created’, ‘DESC’)

The last method in our example above uses propertyOrderBy to set an order. In this case, we’re simply asking for the results to be returned in reverse chronological order.

Querying Fields and Limiting

Now let’s add a query on a field value using a fieldCondition. Let’s assume each of the node types in our example has a field ‘field_us_state’ assigned to it. We’re going to find nodes that are associated with the New York Tri-state area, which also includes Connecticut and New Jersey. This would look like this:

$query->fieldCondition(‘field_us_state’, ‘value’, array(‘CT’, ‘NJ’, ‘NY’));

We can order by field values as well, if that is useful to us. Note that ordering conditions are processed in the order that they are added to the query.

Perhaps for our purposes, we want to limit our query to the 10 most recent items. Let’s add a range:

$query->range(0,10);

Et Voilà

Finally, we execute the query and assign that to a variable:

$result = $query->execute();

This returns us an array of entity ids that match the conditions specified for the query. If we are querying nodes, the information will be under $result['node']. In most cases, what you actually want are the nids that have been returned. This is easily accomplished:

$nids = array_keys($result[‘node’]);

Putting that all together, we have:

$query = new EntityFieldQuery();
$query
  ->entityCondition(‘entity_type’, ‘node’)
  ->entityCondition(‘bundle’, ‘article’)
  ->propertyCondition(‘status’, 1)
  ->propertyOrderBy(‘created’, ‘DESC’)
  ->fieldCondition(‘field_us_state’, ‘value’, array(‘CT’, ‘NJ’, ‘NY’))
  ->range(0,10);
$result = $query->execute();
$nids = array_keys($result[‘node’]);

This is not very much code at all for this result. Moreover, we don’t need to know anything about SQL or even about the underlying storage engine being used. We simply write the query in this simple syntax, and EntityFieldQuery API does the job of translating that into a query that Drupal’s database abstraction layer understands and executes it for us.

What would we do with this list of nids? Pretty much anything. We might load them, for starters:

$nodes = node_load_multiple($nids);

We might want to display these 10 nodes in a teaser style. That’s also easily done. Let’s generate a Drupal render array:

$output = node_view_multiple($nodes);

More To Come

This is just the beginning. Look for part 2 of this post, where we will show more concrete examples of putting EntityFieldQuery to use. Also look out for posts from other Treehouse engineers explaining more advanced topics related to EntityFieldQuery.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web