Please wait...
Drupal 7, EFQ

Override EntityFieldQuery's executeCallback Property

I've inherited an API in Drupal 7 using Services module.  I won't go into the specifics of using Services module to build an API here as it's taken me a week or longer to go through all of the pre-existing code and figure it out for myself.  I'll only say that there is a response configured for each content type that we have defined on our Drupal 7 site.

The main "engine" that packages and returns the response is an Entity Field Query.  This makes it easy to filter on taxonomy tags, or Drupal fields that are attached to each content type by sending values for those tags or fields in the request via the query string.

The issue that I ran into is that due to architectural constraints in the client's data, some of these content types have data that is not stored as Drupal fields, but is instead spread across several custom tables.  Since I can't join to those custom tables using an EFQ (not in Drupal 7 anyway...), and I don't want to refactor the entire API to use SelectQuerys instead, I needed to come up with a way to get at that data and include it in the response from the API.  Enter hook_entity_query_alter().

By implementing this hook, one can override the function that is called by the Entity Field Query object's execute() method.  This means that we can change the behavior of Entity Field Query entirely.  This lets me alter the behavior of Entity Field Query ONLY for certain content bundles leaving the default behavior in tact for everything else.  For this example, let's say I only want to change the behavior of Entity Field Query for the content bundle named 'widget'.

In the above implementation of hook_entity_query_alter I have saved the bundle that the EFQ is operating on with a call to the addMetaData() method. This isn't strictly necessary, as one could easily pull that information from the $query object itself.  However, I've also saved the filter keys and values that were passed in with the request via the query string in the same way.  This will come in handy in a minute.  Having the bundle stored as metadata in the query allows for easily changing the callback function that an Entity Field Query will call when the EFQ's execute() method is called.  In our case the 'widget_query_callback' function will be called.  Let's take a look at that function's implementation:

As you can see here, I'm going to create a SelectQuery object to query these widget results, so that I can use JOIN conditions in order to pull data from tables outside of Drupal's field mechanism. I'm pulling all the query string parameters that came with the request into the $options variable. I can do this because, as I mentioned above, I've stored them all in the EFQ's metadata prior to calling the execute() method on the EFQ. In this example I'm filtering to return only widgets that have a customParamValue that matches the one sent in with the request via the query string. Since I'm now using a SelectQuery instead of an EFQ, I can join the table that contains the customParamValue data for a widget (customParamTable) to the node table and then add a where condition to force the results to contain a customParamValue that matches whatever was sent on the query string with the API request.

If someone sends a request like so:

Then the query that gets executed is going to look something like this:

Now I get all the data and filtering capibilities that I need for the widget content bundle without having to refactor the entire API to use SelectQuery objects instead of Entity Field Query objects. 

This is a pretty stripped-down example when compared to my real-world use of this hook, but hopefully this will get the wheels turning and get you thinking about the things that are possible by implementing hook_entity_query_alter()