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 recommended way in 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', $wrapper);
$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.
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
Комментарии
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];
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.
I thought field_get_items gives you the field in the appropriate language?
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.
That is fair point! thanks.
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...
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.
A good point of reference on this is AddressField. It defines properties for the street address, locality, country, etc.
very nice info!!
Thanks for shearing this
I thought field_get_items gives you the field in the appropriate language?
but for normal single speech websites this approach just add some headache to the developers.
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();
?>
Комментировать