4

entity.query service is very handy when it comes to finding nodes with specific field values. Unfortunately all it returns are entity IDs and revision IDs.

In case in which result set is big it's really inefficient to load whole nodes if only data from specific fields is needed.

In D7 it was possible for example to use field_attach_load() function which (thankfully) has been removed in D8, as detailed in Fields are no longer handled individually and are bound to an entity type. The problem is that Drupal change log references loadFieldItems() and doLoadFieldItems() of FieldableEntityStorageControllerBase class neither of which exists in current release of D8.

What is the Drupal 8 way of loading data for specific node fields only?

  • Can you clarify your question? Your title says, "load only some node fields" but then your last comment says, "loading data for specific node fields only." Does the latter mean you want to extract parts of the data array from a field? – Danny Englander Apr 26 '16 at 13:51
  • @DannyEnglander I want to load only specific node fields. I don't want the whole node with all fields populated with data. For example: node has fields: body, image, color, date. And I want to know the value of color field only. – SiliconMind Apr 26 '16 at 13:57
  • Assuming it's a node template for a specific content type, you can simply load the fields on their own. So instead of {{ content }}, you can do {{ content.field_tags }}. If you do {{ kint(content) }}, you'll see the array of what is available to you. – Danny Englander Apr 26 '16 at 14:02
  • I'm not talking about templating - his is PHP level. Let's assume that I have array with IDs of nodes and that I need to get values of color and image fields for those nodes. – SiliconMind Apr 26 '16 at 14:08
  • If you need to use PHP, you can do kint($vars); in a preprocess function (node for example) in your theme's .theme file to examine the array and then write code for what you need. – Danny Englander Apr 26 '16 at 14:11
2
+300

According to Acquia articles and specifically this:

In Drupal 7, we often accessed fields directly, using hooks to modify fields when they were loaded, changed, or saved. In Drupal 8, there is a focus on entities instead of fields. Because of this, if we want to modify fields we need to get the fields out of the entities.

So in Drupal 8 you can't directly access any fields without first loading the whole entity as the following example (taken from the Acquia article) demonstrates:

/**
* @var $entity \Drupal\Core\Entity\Entity
*/
$entity = $entity_storage->load($entity_id);


/**
* @var $field \Drupal\Core\Field\FieldItemList
*/
$field = $entity->get($field_name);

// We get the string value from the field.
$value = $field->value;

// Get the summary from a body field.
$summary = $body_field->summary;

$field->value = 'changed value';

Unfortunately the specific thing you want doesn't exist in the API. All field data is loaded in bulk via ContentEntityStorageBase::doLoadRevisionFieldItems, and the standard SqlContentEntityStorage doesn't contain any methods for extracting individual fields. If you haven't already done so, it might be worth benchmarking this to see if there's a real problem. A lot of work was done on the cache system for D8, things might not be as slow as you think.

Implementing this manually would be pretty easy, just re-implement SqlContentEntityStorage::buildQuery in a new service class and change it to only load data for the field(s) passed in as an arguments from the table mapping. But you don't get any cache goodness with that, which I suspect is at least part of the reason loading individual fields was removed in the first place.

  • Not really helpful. I'm not looking for answer why it's not possible :) I'm looking for an answer how to tap into drupal core to load those fields manually. Fields inherit from Map which inherits from TypedData. There must be a way to create instance of a field class and load field data into it without having to load the whole node. – SiliconMind May 13 '16 at 15:42
  • Fair enough. But what's the point in trying to do something that is not recommended or not even possible or violates the Drupal's standards, performance ? Keep in mind that everything depends on the way that Drupal stores data for each field - entity, the layers behind that mechanism and the provided API. – mchar May 13 '16 at 17:36
  • Why do think it violates anything? I'm trying to work around performance issues. It doesn't make sense to load 100 fully blown node objects just check values of one field for each of those nodes. And I do want to use API for that. If I wouldn't, I would write SQL queries to get that data, but that defeats the purpose of using drupal in the first place. – SiliconMind May 13 '16 at 18:06
  • Unfortunately what you want doesn't exist in the API @SiliconMind, I just spent a little bit checking. All field data is loaded in bulk via ContentEntityStorageBase::doLoadRevisionFieldItems, and the standard SqlContentEntityStorage doesn't contain any methods for extracting individual fields. If you haven't already done so, it might be worth benchmarking this to see if there's a real problem. A lot of work was done on the cache system for D8, things might not be as slow as you think – Clive May 13 '16 at 21:06
  • @Clive thanks for the explanation. Although it's a sad truth, your comment would be the answer to accept as it explains how fields are loaded today and why there is no way to load them individually. – SiliconMind May 13 '16 at 21:18
3

I did some profiling with a query of 100,000 nodes, fetching the field myfield from all nodes of type mybundle.

Good: A dynamic query with addField()

$connection = \Drupal::database();
$query = $connection->select('node__field_myfield');
$query->condition('node__field_myfield.bundle', 'mybundle');
$query->addField('node__field_myfield', 'field_myfield_value');
$result = $query->execute();
$values = $result->fetchAll();

This runs in about 250 milliseconds.

Bad: An entity query with loadMultiple()

$query = \Drupal::entityQuery('node');
$query->condition('type', 'mybundle');
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$values = [];
$nids = $query->execute();
$nodes = $node_storage->loadMultiple($nids);
foreach($nodes as $node) {
  $values[] = $node->get('field_myfield')->getValue();
}

This took 3 minutes after a few repeated attempts when the cache was fully built. After a cache rebuild, it took more than half an hour.

  • Small improvement to your "Good" - add language as part of the filtering. – Cristian Romanescu Aug 8 '19 at 15:33
0

In case in which result set is big it's really inefficient to load whole nodes if only data from specific fields is needed.

What is the Drupal 8 way of loading data for specific node fields only?

uh...ummmm....Views with a filter and/or fixed contextual filter with allow multiple values checkmarked...

Note: Drupal 8 comes with views by default.

-2

Please create View/Reference/Block/Attachment with selected field. And Call that using contextual filter.

For example : http://localhost/project/admin/structure/views/view/view_machine_name/edit/your_block

            $nameshipped = 'view_machine_name'; // please check the url
            $viewshipped = views_get_view($nameshipped);
            $display_id_shipped = 'your_block';
            $viewshipped->set_display($display_id_shipped);
            //$viewshipped->set_arguments(array($all_con_id)); // if we need to use contextual filter.
            echo $html = $viewshipped->render();

Please use the above code where you want to implement.

  • I think the question makes it pretty clear what version of Drupal is being asked about - this is for Drupal 7, right? – Clive May 18 '16 at 12:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.