Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Entity queries

An entity query finds nodes by conditions: type, status, a field value. It returns only the numbers of the entities it found, and the storage then loads the entities themselves. With its help the /blog page finally shows real articles.

A simple query

The query is created by the storage from the previous lesson:

// web/modules/custom/blog/src/Controller/BlogController.php
$storage = $this->entityTypeManager()->getStorage('node');
 
$ids = $storage->getQuery()
    ->accessCheck(TRUE)
    ->condition('type', 'article')
    ->execute();
 
$nodes = $storage->loadMultiple($ids);

condition('type', 'article') keeps only articles, execute() runs the query and returns an array of numbers. The accessCheck() call is required: TRUE means "only what the current user is allowed to see".

Conditions, sorting, limiting

// web/modules/custom/blog/src/Controller/BlogController.php
$ids = $storage->getQuery()
    ->accessCheck(TRUE)
    ->condition('type', 'article')
    ->condition('status', 1)
    ->condition('created', strtotime('-30 days'), '>=')
    ->sort('created', 'DESC')
    ->range(0, 10)
    ->execute();

The query returns at most ten published articles from the last 30 days, newest first. The third argument of condition() is the comparison operator; without it equality is checked. range(0, 10) is the offset and the count.

EntryMeaning
condition('status', 1)Equals
condition('created', $time, '>=')Comparison: >, >=, <, <=, <>
condition('title', '%drupal%', 'LIKE')Contains a substring
condition('type', ['article', 'page'], 'IN')One of the values
condition('field_tags.entity.name', 'PHP')By a field of a referenced entity
exists('field_image')The field is filled in
count()Return the count instead of the numbers

The list of articles on the page

Let us put it all together in the list() method:

// web/modules/custom/blog/src/Controller/BlogController.php
public function list(): array
{
    $storage = $this->entityTypeManager()->getStorage('node');
 
    $ids = $storage->getQuery()
        ->accessCheck(TRUE)
        ->condition('type', 'article')
        ->condition('status', 1)
        ->sort('created', 'DESC')
        ->range(0, $this->config('blog.settings')->get('items_per_page'))
        ->execute();
 
    $nodes = $storage->loadMultiple($ids);
 
    return $this->entityTypeManager()
        ->getViewBuilder('node')
        ->viewMultiple($nodes, 'teaser');
}

The view builder getViewBuilder('node') turns nodes into a render array. The second argument is the view mode: teaser is the short view, full the complete one. The /blog page shows the latest articles the way the Manage display tab of the content type is set up, using the node--article.html.twig template from the theme.

Creating

// anywhere, for example in a Drush command
$node = \Drupal::entityTypeManager()->getStorage('node')->create([
    'type' => 'article',
    'title' => 'New article',
    'body' => ['value' => 'The text of the article.', 'format' => 'basic_html'],
    'uid' => 1,
    'status' => 1,
]);
 
$node->save();

create() only creates the object; save() writes it to the database. type and title are required; uid is the author number, status says whether the node is published. After saving, $node->id() returns the assigned number.

Deleting

// anywhere, for example in a Drush command
$node->delete();
 
$storage->delete($storage->loadMultiple($ids));

The first line deletes one node, the second several at once. Deletion cannot be undone.

Common mistake

A query without accessCheck() ends with an exception: Drupal requires an explicit decision on whether to respect access. accessCheck(TRUE) is needed for everything shown to a visitor. FALSE is acceptable only in internal tasks — cron, Drush commands, updates — where there is no user. Mixing them up makes it easy to show unpublished nodes to an anonymous visitor.

Thanks for reading!

Donate to the site — we work hard for you.