Writing robust code that uses fields, in Drupal 7

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_…
There is also field language:

http://api.drupal.org/api/drupal/modules—field—field.multilingual.inc/…

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 field_get_items 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.

Comments

I think you misunderstood field_get_items() - it gives you an array with all values.

$items = field_get_items('node', $node, 'field_yourfield');
$second_value = $items[1];

24 August, 2011

Why do you think I misunderstood the function? I’m not saying you can’t access second value. I’m saying you can’t access it with single string of code - you have to put it in some variable and use this variable to access the child element.

24 August, 2011

I thought field_get_items gives you the field in the appropriate language?

25 October, 2011

The entity_metadata_wrapper approach is really nice, thanks for pointing this out!
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.

24 August, 2011

That is fair point! thanks.

25 August, 2011

Can you please show some examples of property info hooks? I am building my own fields (for a social field module) and I'd love to have them properly declared...

04 September, 2011

I think the best way to find info on hooks is to dig into entity api module code.

entity/entity.api.php is a good place to start.

06 September, 2011

A good point of reference on this is AddressField. It defines properties for the street address, locality, country, etc.

26 September, 2011

haven’t built any d7 Multilanguage yet hence I am not aware of the fact you had mentioned here but as you are a senior to this field you have a very good experience that supports my knowledge far better than what I know

05 February, 2014

very nice info!!

Thanks for shearing this

23 November, 2011

I thought field_get_items gives you the field in the appropriate language?

23 November, 2011

but for normal single speech websites this approach just add some headache to the developers.

04 January, 2012

How would you get for instance the timezone on a date field?


<?php
$field = $obj->field_date->value();
?>

and the uri value for a picture?


<?php
$field = $obj->field_picture->value();
?>

09 February, 2012

I am not sure about timezone, because I never configure my dates to worry about that. I always just go by server time. Not a wise practice, I know..

As for files, I believe it would be $obj->field_picture->uri->value(), but that is a guess. What I always do when trouble shooting a field is using the getPropertyInfo() method. This will give you a listing of what values you can call. I usually will use a kpr() of that or dsm().

So kpr($obj->field_picture->getPropertyInfo());

Hope this helps.

21 May, 2012

It ends up being
$wrapper->field_image->file->value()->uri

08 November, 2013

Very nice article, I come back often, well every time I forget how to use it. On a related note, in the last example, it should be :

$wrapper = entity_metadata_wrapper('node', $node);
instead of
$wrapper = entity_metadata_wrapper('node', $wrapper);

Thanks again

06 November, 2012

Thanks Pierre. the example was fixed.

06 November, 2012

Hey, how to work with empty fields with wrappers?

I'm always getting the exceptio when the field is empty:

EntityMetadataWrapperException:
Unable to get the data property [prop name (amount)] as the parent data structure is not set.

Here is my code:
$order_wrapper->owner->field_discount_limit->amount->value() // field_discount_limit is empty

18 December, 2013

I found that a check using 'empty()' was sufficient, except where accessing an entity reference field where I was looking for an nid or uid. For nid or uid, I'd use 'is_numeric()', otherwise I'd get the error you mention (ie 'parent data structure is not set...')

Here's example code:

$public_email = $wrapper->field_public_email_address->value();
if(!empty($public_email)) { // only need to used 'empty()' here
// $public email exists, so do something with it...
}

if(is_numeric($wrapper->field_director->uid)) { //is_numeric() needed to check entity ref. uid
$director = $wrapper->field_director->uid->value();

if(!empty($director)) {
$tbl_project['project_director_user_code'] = $director;
}
}

05 March, 2014

Post new comment

Private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

Note for potential spammers: all links in your comment will not be indexed by search engines.

Anton Sidashin

Anton Sidashin senior developer, Pixeljets co-founder

I'm a web developer specializing in PHP and Javascript, and Drupal, of course. I'm building Drupal projects since 2005, and I was working as full-time senior engineer in CS-Cart for a while, building revolutionary e-commerce software. In my free time, I enjoy playing soccer, building my body in gym, and playing guitar.

Drupal.org ID: restyler
Drupal association member