Views is an amazing module. The power it provides to build lists of content from within the UI is amazing. The plugin architecture is complicated but extraordinarily powerful. There are currently 125 Drupal 7 modules that extend the functionality of Views. So why didn't we use it for development of Energy.gov?
When we started development on Energy.gov, we took a step back. This would be our first Drupal 7 project. We took this opportunity to reconsider our development practices, and looked at every module we had been accustomed to using and asked two questions:
- Does it provide a robust API (i.e. can we use it without the UI)?
- Do we really need it? Are we using the module just because that's what you use when you create a Drupal site? Are we we pushing the module beyond it's original intent? Does this module try to solve too many problems?
Ok, so the second question is really 4 questions. In the case of Views, we could answer "yes" for the first question, we couldn't for the second set of questions. There was no specific requirement for a visual query builder. In past projects, we often spent as much time investigating quirks about how Views (or any other large module) works as we do in custom development.
In the end, one argument against using Views for our content queries overrode all others: we wanted our client to use Views. Our client had specified that once they received the sites, their own developers would be using Views to build blocks and pages themselves. We knew that if we worked in Views for our own work, the Views we created would eventually be exposed to them, which leads to possibilities of regression and error. We wanted our core querying functionality to continue to function without concern that it might be tampered with. We explained our concerns and our proposed approach to the client, and they agreed to it.
So, what would be used in its place? In short, the answer is EntityFieldQuery. EntityFieldQuery is a class, new to Drupal 7, that allows retrieval of a set of entities based on specified conditions. It allows finding of entities based on entity properties, field values, and other generic entity metadata. The syntax is really compact and easy to follow, as well. And, best of all, it's core Drupal; no additional modules are necessary to use it.
A typical EntityFieldQuery that looks up the 5 most recent nodes of types article, page, or blog created by the current node's author, that are published (a typical "more by this author" query) might look like the following:
$node = menu_get_object();
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->propertyCondition('status', 1)
->propertyCondition('type', array('article', 'page', 'blog'))
->propertyCondition('uid', $node->uid)
->propertyOrderBy('created', 'DESC')
->range(0, 5);
$result = $query->execute();
Note a few things here:
- All methods of the EntityFieldQuery class generally chain; that is, they return the modified EntityFieldQuery object itself. You may be familiar with this sort of construct from jQuery.
- Like Drupal 7's query building in general, the methods of EntityFieldQuery have default operators that they assume, and are fairly agnostic in terms of what sorts of values they'll accept. So for example, propertyCondition() has either '=' or 'IN' as its default operator, depending on whether you pass it a string/number or an array as a comparison value. Of course, if you want a different comparison, i.e. '<>' or 'NOT IN' or such, you can pass that in explicitly.
- Note that we're not querying fields just yet. EntityFieldQuery really starts to shine when you start querying field values, because it takes care of finding the appropriate field table and doing joins for you.
EntityFieldQuery is powerful, but it didn't do everything we wanted it to. We used Organic Groups on this site, and we wanted our queries to be aware of groups without adding that each time. Also, in almost all cases we were querying for nodes which were published, and we often wanted a reverse chronological ordering. Fortunately, EntityFieldQuery is a PHP class, so it is very easy to extend. We created EnergyEntityFieldQuery.
/**
* apply some defaults to all instances of this object
*/
public function __construct() {
$this
// we're interested in nodes
->entityCondition('entity_type', 'node')
// Default to published
->propertyCondition('status', 1)
// default to reverse chronological order
->propertyOrderBy('created', 'DESC');
/* make assumption that we want group content; see method below */
$this->setPrimaryAudienceCondition();
}
/**
* Helper function for querying by topic vocabulary terms.
* Will do lookup from term names as a convenience; tids are also recognized.
*
* @param $topics
* String, numeric or array; converts to array if necessary
*/
public function setTopicCondition($topics) {
$topics = !is_array($topics) ? array($topics) : $topics;
if (count($topics)) {
// if a term is not numeric, do a lookup for each term and replace it with its tid
foreach ($topics as $idx => $topic) {
// try to find a tid for non-numeric terms
if (!is_numeric($topic)) {
// look it up
$vocab = taxonomy_vocabulary_machine_name_load('topics');
$candidate_terms = taxonomy_get_term_by_name($topic);
foreach ($candidate_terms as $candidate) {
if ($candidate->vid == $vocab->vid) {
$topics[$idx] = $candidate->tid;
}
}
}
}
// field_topic_term is our term reference field for the Topics vocabulary
// once we have converted all our terms to tids, we set them as a field condition for our search
$this->fieldCondition('field_topic_term', 'tid', $topics);
}
return $this;
}
/**
* Add the field condition to search by the primary audience field.
* EnergyEntityFieldQuery makes the assumption that we want content that matches the current group.
* The class will provide an undo method
*
* @param $gid
* An array or integer for the gid(s) to search the primary audience field
* based on. If empty, will try to pull current group from the page context.
*/
public function setPrimaryAudienceCondition($gid = NULL) {
if (empty($gid)) {
$current_group = og_context_determine_context();
$gid = $current_group->gid;
}
if (!empty($gid)) {
$this->fieldCondition('group_audience', 'gid', $gid);
}
return $this;
}
/**
* Unset group content conditions
*
* Use this method if you do not want to filter by group content.
*/
public function clearAudienceConditions() {
foreach ($this->fieldConditions as $idx => $fieldCondition) {
$field_name = $fieldCondition['field']['field_name'];
if (($field_name === 'group_audience') || ($field_name === 'group_audience_other')) {
unset($this->fieldConditions[$idx]);
}
}
return $this;
}
/**
* If we're currently on a node, and if the entity_type is node, exclude the local node from the query.
* This prevents the node the user is viewing from showing up in queries.
*/
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;
}
}
Note that it is also possible to override the protected methods of EntityFieldQuery itself. This may be useful, for example, if you have more complex propertyCondition needs than EntityFieldQuery itself provides.
We found that we were able to solve around 90% of our content listing use cases with EnergyEntityFieldQuery. We build a simple UI to allow users to pass parameters to the class, which allowed them to easily create dynamic query.
Let's run through a simple example of displaying a list of nodes. Remember that this is not sample code. This is real code we are using on Energy.gov.
The easy part. Remember using EnergyEntityFieldQuery allows us to restrict our queries to OG group content and sets the default entity type to node.
We only want nodes that are rebates.
EntityFeildQuery has built in paging and table functionality which is trivial to add.
We are pull conditions from the query string to create taxonomy filters.
// This maps the vocabulary machine names to the field names for term references to that vocabulary.
$term_field_map = array(
'rebate_provider' => 'field_rebate_provider',
'rebate_savings_for' => 'field_rebate_savings_for_short',
'rebate_eligibility' => 'field_rebate_eligibility_short',
);
// Get the non 'q' parameters
$params = drupal_get_query_parameters();
$param_filters = array();
foreach (array_keys($term_field_map) as $vocab) {
if (isset($params[$vocab])) {
$param_filters[$vocab] = $params[$vocab];
}
}
$filters = array_merge($param_filters, $filters);
Set the condition for the terms if there are any
if ($value != 0) {
$query->fieldCondition($term_field_map[$filter], 'tid', $value);
}
}
Run the query
// process and theme the results, not part of this code example
}
This returns to us an array of entity ids (nids in this case) that match the conditions we've given it. Once we have those, we can process them any way we like. Our preferred method is to use Drupal 7's greatly expanded concept of view modes, which we will talk about in a future post in this series.
So, this is all well and good. But how do we get these to display? How do we place them? One of the advantages of Views is that it can provide blocks of your constructed query which become available to the system immediately.
The answer to that, for Energy.gov, is Beans. Bean is a contributed module we developed in the course of this project which creates blocks as entities. In the next post, you will learn how we used the Bean module to replace View blocks, and how Beans can be used to do much more.


Note that not every EntityFieldQuery method chains; it is worth reading the documentation.
Really???? I wrote EFQ and I am unaware. DBTNG SelectQuery methods not always chain but EFQ always do. What did I miss??
Thanks Chx for writing EFQ. I love it. I stand correct and am updating the post. Thanks for the information.
I too have developed a fairly complex site without views with a very similar approach using Entities, efq, and view modes. Many people have shown disbelief when I have mentioned my approach, so it is good to see that I am not insane. Actually, last night I started writing a UI for efq, are you planning on making that part of the project available to the community? I think a well layout efq UI will have a lot of the power views provides in a much more intuitive manner.
Amen brother. Views is an amazing visual query builder, which is not what we typically need as developers working on complex projects. I've found non-Views development often leads to simpler maintenance and enhancements. E.g., if you need to adjust a query, the choice is to review your 20 or so lines of code and make the needed changes, or to dig through the Views UI, point and click and repeat, test, export to code in some way (Features, CTools, etc), then revert the affected Featuer(s). We've also found that updating Views as new releases come out can lead to unexpected consequences.
I am NOT suggesting Views is a bad approach, it's just not the best one in every situation.
Great to see this! Views has always been a problematic part of Drupal from a developer's standpoint. Views 1 was a handy, if funky tool, in the Drupal 5 developer's toolbox. Views 2 for Drupal 6 was a golden age for Views, because it was so dang useful and unified several concepts from the community (in particular, field formatters).
In Drupal 7, with fields in core and EFQ, Views may be less important. It doesn't help that chronic Views problems like bugginess, complexity, lack of documentation, and a frustrating development process seem to be getting worse.
Thanks for pursuing and documenting your approach. I'm also working on a US Dep't of Energy project (not energy.gov) and we'll be using EFQ and Bean when we transition to Drupal 7. I have long worried that Views would become the X Windows of the Drupal world -- a necessary yet deeply flawed part of the Drupal ecosystem. This work gives me hope there will be good alternatives to Views in D7 and beyond.
Thanks for the comment. It's good to know that others think like we go. @fmizzell, I'd love to see what you have for the UI. Can you put it on github and we can maybe work together?
Don't put it on github! Put it on drupal.org! Every time you host Drupal code off drupal.org , you are hurting Drupal.
Also note that dmitrig01 and bojanz were indispensible to get EFQ out the door.
Also? EFQ really shines not just in JOINs but because it works with other field storage engines not just SQL.
Your mollom / theme is kinda broken I get http://treehouseagency.com/http://xmlrpc2.mollom.com:80/rest/captcha/ima... when i try to submit.
I am writing too many comments. An UI IMO is pointless as you are on your way to rebuild views + efq_views.
Chx, you have earned the right to comment as much as you want :) I general UI around EFQ is kind of pointless. When my next post gets published about beans (it's being reviewed now) then I will show you how we did our UI for EFQ.
Yes, our comment input filter seems to be behaving oddly. :)
chx++
dmitrig01++
bojanz++
I had this conversation in irc and even though there were a few in favor of an efq UI, most raised the point that chx is raising right now against it. So I am a little torn because of the simplicity of what an efq UI represents in my mind. I am thinking of two actions: filter and sort. You can do both on properties and fields, and probably a range and pager option. That’s it! I don’t want to be able to create pages or blocks, or any of the other things that views does, if I did I would have used views instead of just efq. I see the efq UI more like a developers tool, not as an end user product like views is. So if I am developing my own entity, I could call a few efq UI functions and have an administration page for my entity with all of the options to organize my entities given by efq, and if someone DID want to recreate views from the ground up, they could build a module that uses efq UI, stores the queries, creates pages, and uses the stored queries to be displayed on a page. It is possible that under hood views is modular enough so that I could rip out everything I don’t want, and save me some time and code in the process, but given the complexity of the Views UI, my guess is that it would take me longer to understand Views code than to simply write an efq UI. My view could be completely misguided and that is why I appreciate any input.
fmizzell. Make sure you look at the entity module on D.O. it's doing that kind of now. Might be a good place to start.
I made a module called Entity Construction Kit (eck) that uses all of the api capabilities offered by the entity module to create entities from a UI, I did not use the ui capabilities offered by entity, but just having a quick glance, it does not offer any of the sorting, or filtering capabilities that I mentioned in the last post. what I am talking about is functionality like that one seen at admin/content where you can filter your nodes. I would like that but powered by efq. That would be useful to anyone creating custom entities, on top of what the entity module offers.
hello please send your details again ive missplaced them
p beartil
Post new comment