Thursday, 17 March 2011

Getting field data out of entities

EDIT: download the module that encapsulates this behaviour and does a lot more besides: read this blog.

The new field structure in Drupal 7 is very clever but also quite irritating when you want to get data out of a field in an entity, so I have written this handy routine which extracts field data:


/**
 * Returns field values as actual entities where possible,
 * also allows selection of individual items to be returned
 */
function field_fetch_field_values($entity_type, $entity, $field_name, $get_delta = NULL, $get_key = NULL) {
  $values = array();
  if (isset($entity->$field_name) && !empty($entity->$field_name)) {
    foreach (field_get_items($entity_type, $entity, $field_name) as $delta => $item) {
      $value = $item;
      $keys = array_keys($item);
      if (count($keys)==1) {
        $key = $keys[0];
        switch ($key) {
          case 'nid':
            $value = array_shift(entity_load('node', array($item[$key])));
            break;
          case 'uid':
            $value = array_shift(entity_load('user', array($item[$key])));
            break;
          case 'tid':
            $value = array_shift(entity_load('taxonomy_term', array($item[$key])));
            break;
          case 'vid':
            $value = array_shift(entity_load('taxonomy_vocabulary', array($item[$key])));
            break;
          case 'value':
            $value = $item['value'];
            break;
        }
      }
      else {
        if ($get_key && isset($item[$get_key])) {
          $value = $item[$get_key];
        }
        elseif (array_key_exists('value', $item)) {
          $value = isset($item['safe_value']) ? $item['safe_value'] : $item['value'];
        }
      }
      $values[$delta] = $value;
    }
  }
  if (is_numeric($get_delta)) {
    return isset($values[$get_delta]) ? $values[$get_delta] : NULL;
  }
  return $values;
}


Use it like this:

$terms = field_fetch_field_values('node', $node, 'field_myterms');

Extracts all the tids from the field and returns the actual terms, not the tids.

$term = field_fetch_field_values('node', $node, 'field_myterms', 0);

Returns just the first term.

$summary = field_fetch_field_values('node', $node, 'body', 0, 'safe_summary');


Returns only the summary from the body field, but does not create a summary if there isn't one.


$info = field_fetch_field_values('node', $node, 'field_myfiles', 0);


If field_myfiles contains uploaded file data you get the full file info array back, in this example just the first one.


There are probably cleverer ways of doing this (and you could do multiple entity_loads to make it more efficient) but I find this works well enough for normal day-to-day use.

16 comments:

Dixon said...

Nice work! it's redonculously frustrating that you can't get a value using a node id and a field name with the field api. Maybe there is but this is the best solution I've found so far.

One thing to note is that you must pass the function a node object returned from something like node_load(nid) and not an entry returned from entity_load("node",$nidArray)

Adaddinsane said...

Thanks for that - of course, now looking at this with a fresh eye there is the nasty redundancy that if you just want a single entry from a list of terms you'll get a whole bunch of unnecessary DB accesses.

I'll fix it some other time.

E31 said...

For people that want to query entities for certain data, it is useful to use EntityFieldQuery (http://api.drupal.org/api/drupal/includes--entity.inc/class/EntityFieldQuery/7).

For example, querying a taxonomy for a specific term, can be done this way:

$query = new EntityFieldQuery();

$query
->entityCondition('entity_type', 'taxonomy_term', '=')
->propertyCondition('vid', 2)
->fieldCondition('field_your_textfield', 'value', array('value to search for'), '=');

dpm($query->execute());

Unknown said...

Thanks for the code :) I'm a newbie with this new Fields API stuff, and it's quite confusing to me still.

Using this I've been able to "see" an array that contains a field I want to render, but that's as far as I've got yet. I was wondering if you could show how to accomplish the following scenario (I think having even one functioning use-case within my own context should help me figure out how to apply it in general):

There's an image field on a taxonomy term which I'd like to render on a node which has that term applied to it. It's easy with the Manage Display tab on the Content Type to have the term name itself be displayed on the node, but I've not yet been able to get the fields "of" that term to also display and be properly rendered on the node.

Thanks in advance for your help!

Adaddinsane said...

There's no simple solution to this.

You could to take a look at Display Suite - this is my new BFF when it comes to customising displays. It now just needs to be able to add fields of attached entities (a la Search API). But you can add "DS Code" as cusotm fields which may help you to do this.

Donny said...

Hi you wrote a awesome module, but can you explain what I need to pass to $node variable?

$value = field_extract_value('node', $node, 'field_just_a_value');

For example I try to get content of field 'uc_catalog_image' from term 'brand'

Adaddinsane said...

Well, from what you say brand is a term not a node. So you use

field_extract_value('taxonomy_term', $brand)

You have to use the entity name with the entity itself so the code knows how it should be processed.

There are lots of examples on the project page.

The Opinionator said...

Would this work with Drupal 6 too?

Adaddinsane said...

No, while CCK fields have a similar structure it doesn't include language.

drewpal said...

I'm lost. I have a node reference I'm trying to extract:

$values = field_extract_values('node', $node, 'field_invitees');
I get the error:
Notice: Undefined variable: node in __lambda_func()

Not sure if this is a double post. If so, apologies.

Adaddinsane said...

Hi Drew

Sorry, been very busy recently (current project is a bit intense).

As I suspected when I saw it, that error message only occurs when using PHP's anonymous functions - which I don't use at all.

Which means the error is somewhere else. I can only assume that "field_invitees" is of a strange field type not supported by my module.

Kai Johnson said...

This function already exists in a much more powerful way in the entity API: entity_metadata_wrapper

For example:
$node = node_load(nid);
$node_wrapper = entity_metadata_wrapper('node', $node);
$your_field = $node_wrapper->your_field->value();

Enjoy!

Adaddinsane said...

Yes, it does exist in the entity wrapper functionality (I believe I said this in one of my other postings).

But, define "more powerful".

This is a quick and easy solution that works and has very little overhead.

It's "horses for courses". I use the entity wrapper when it's appropriate. Other times, I don't.

Kai Johnson said...

More powerful in that it's not limited to the core entities 'node', 'user', 'taxonomy_term', and 'taxonomy_vocabulary'. If you've added a custom entity or are using a module that creates its own entity, this function will not accomplish the goals it sets out to do.

If the goal is to return 'field values as actual entities', don't you think the entity api is exactly what the doctor ordered?

Also, I believe you'll get STRICT errors thrown by passing arguments by reference for array_shift.

Adaddinsane said...

Limited entity types? Not especially.

And yes, PHP 5.4 is more picky - I have a corrected version. But there are plenty of places in core and major 3rd party modules where that's a problem too.

Anyway what's your problem? It's well-behaved, extensible and developers don't have to use it if they don't want to.

You have my permission to ignore it.

Kai Johnson said...

I have no problem, I was just offering an alternative from a widely used and very powerful module, similar to what E31 posted earlier. I really meant no offense by it.

And you're right, I don't have to use your code. I recommend that people use entity_metadata_wrapper for this functionality.