Writing robust code that uses fields, in Drupal 7

Table of Contents

In Drupal 7, the direct access to entity fields (CCK in d6) is different. In Drupal 6 you write:

<?php  
$field_val = $node->field_yourfield[0]['value'];
?>

in Drupal 7 you have to write:

<?php  
$field_val = $node->field_yourfield[LANGUAGE_NONE][0]['value'];
?>

(this is a way suggested by core docs). So, we have different languages here now.

I haven't built any d7 multilanguage websites yet, so I don't know if that approach makes it really easy to create language-aware code for i18n websites (I hope it does!), but for regular single language websites this approach just adds some headaches to the developers.

The problem here is that you can't rely on LANGUAGE_NONE! if website admin enables locale module, and English language is active, you'll have to change your code to something like:

<?php  
$field_val = $node->field_yourfield['en'][0]['value'];
?>

Obviously, we need some general way to access fields, with locale languages enabled.

First approach: field_language()

First approach that I've tried is to detect active content language of the node.
http://api.drupal.org/api/drupal/developer--globals.php/global/language_content/7
There is also field language:

http://api.drupal.org/api/drupal/modules--field--field.multilingual.inc/function/field_language/7

But it didn't work in the way I expected - when nodes are just created, if the node is created in English language, you still have to use LANGUAGE_NONE constant. (I can't give 100% guarantee that this works exactly like I say, since I don't use the language variable for a while already)

<?php  
$language = field_language('node', $node, 'field_yourfield'); 
$field_val = $node->field_yourfield[$language][0]['value'];
?>

I googled and I found

Second approach: field_get_items()

http://www.davereid.net/content/hlkd7fotw-field-get-items

<?php  
$field_val = field_get_items('node', $node, 'field_yourfield');
?>

which is fine. but this approach is not perfectly functional, since you can't access, say, second element in multi-valued field, in one string (I mean, you can't do field_get_items('node', $node, 'field_yourfield')[1] - well, until php5.4) .
also, if you need quick access to five fields, you have to call fieldgetitems five times, so your code looks like crap.

and here is the third approach I found, which looks like the best way for field work:

Third approach: entity_metadata_wrapper

entity_metadata_wrapper is a helper object from great Entity module (fago is my hero)

here is how you use it:

<?php  
$obj = entity_metadata_wrapper('node', $node);
$field = $obj->field_yourfield->value();
?>

well, that is clean enough, but not so exciting. What IS exciting, is how you can use entity_metadata_wrapper to load referenced objects on the fly:

<?php  
$involved_users = array();
//grab usernames from user reference field of a node
$project = entity_metadata_wrapper('node', $node);
// field_users is user reference field
foreach ($project->field_users as $acc) {  
  $involved_users[] = $acc->value()->name;
}

var_dump($involved_users);  
?>

when you call value() method here, entity_metadata_wrapper knows that the field is a user reference field, and loads appropriate user account on the fly. In Drupal 6, the same code looks like this:

<?php  
// Drupal6 code
$involved_users = array();
//grab usernames from user reference field of a node

// field_users is user reference field, $project is node
foreach ($project->field_users as $acc) {  
  $acc_object = user_load($acc['uid']);
  $involved_users[] = $acc_object->name;
}

var_dump($involved_users);  
?>

Also, what is interesting, is that $project->field_users is not a regular array, it is an object, so for example you can call $project->field_users->value() to get an array of all users accounts from the field.
At the same time, this object supports array access, and you can do $project->field_users[0] or you can user foreach like in the example above. Talking in PHP5 language, this field object class implements IteratorAggregate, ArrayAccess and Countable interfaces.

More examples from Entity module readme:

<?php  
$wrapper->author->mail = 'sepp@example.com'; 
?>
In order to force getting a textual value sanitized for output one can use,       e.g.
$wrapper->title->value(array('sanitize' => TRUE));

to get the sanitized node title. When a property is already returned     sanitized by default, like the node body, one possibly wants to get the     not-sanitized data as it would appear in a browser for other use-cases.     To do so one can enable the 'decode' option, which ensures for any sanitized     data the tags are stripped and HTML entities are decoded before the property     is returned:

$wrapper->body->value->value(array('decode' => TRUE));

That way one always gets the data as shown to the user. However if you     really want to get the raw, unprocessed value, even for sanitized textual     data, you can do so via:        $wrapper->body->value->raw();

You can also save the modified node:

<?php  
$node = node_load(323);
$wrapper = entity_metadata_wrapper('node', $node);
$wrapper->title = 'New title for the node';
$wrapper->save();
?>

This approach works for nodes and all other entities.

For entities creation (which is a separate story) you can try to use entity_create() or shortcut - entity_property_values_create_entity

I made my choice and I use third approach (entity_metadata_wrapper) in my d7 field-related code.

UPD from Tom Nightingale:
It should be noted though that it will only work with fields that correctly describe themselves to Entity API via its property_info hooks.
A lot of contrib field modules still need to implement this.

UPD from author (summer, 2012):
We have a lot of experience building internationalized projects in Drupal 7 now :)

It seems like latest versions of Drupal core now use LANGUAGE_NONE in fields even if you have several content languages active in your website. At least, if you use node-driven translation approach from i18n (and you have separate node to hold each language translation).

so, from now on, you have to deal with this kind of syntax (with 'en' key instead of LANGUAGE_NONE):

<?php  
$field_val = $node->field_yourfield['en'][0]['value'];
?>

ONLY if you use field-translation approach (when single node holds translations of fields for all languages). It is done via Entity Translation module.
In other cases (for all single-language projects, and internationalized projects with i18n translation, but without entity translation module), it looks like you're safe using LANGUAGE_NONE syntax in your code.