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');
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
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.
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
As you can see, there is no more distinction between
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
OR) for more powerful queries:
$query = \Drupal::entityQuery('node') ->condition('status', 1) ->condition('changed', REQUEST_TIME, '<'); $group = $query->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.
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);
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.
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.